Compare commits
349 Commits
v0.7.0-bet
...
v0.8.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
765efd19da | ||
|
|
ac59e28fcd | ||
|
|
77dab70ff7 | ||
|
|
0b66e911b1 | ||
|
|
6388a798c9 | ||
|
|
c45149b664 | ||
|
|
ce0c1c39e2 | ||
|
|
52809a2783 | ||
|
|
be3909370e | ||
|
|
f79733e5df | ||
|
|
2d95de06d6 | ||
|
|
97502231a3 | ||
|
|
d20a6d2677 | ||
|
|
9f43c0ddc8 | ||
|
|
4cb1bf6a9f | ||
|
|
d96263fde9 | ||
|
|
ed8b2d9927 | ||
|
|
7b3be389d4 | ||
|
|
68755f4303 | ||
|
|
0e1f712dcc | ||
|
|
0ab1cfdeb6 | ||
|
|
8185ee991b | ||
|
|
1001d7462a | ||
|
|
f9929ebd61 | ||
|
|
aa69a7cad2 | ||
|
|
4de063de98 | ||
|
|
d765d0350d | ||
|
|
d2459a5837 | ||
|
|
e1f604d857 | ||
|
|
af4e860176 | ||
|
|
a3d665503f | ||
|
|
b47fbde479 | ||
|
|
115f46a4fa | ||
|
|
b0f8486ef2 | ||
|
|
57c27e6a4b | ||
|
|
f591939a6a | ||
|
|
2d41cce90f | ||
|
|
3b1aff1db1 | ||
|
|
3a64b43032 | ||
|
|
59f53760d1 | ||
|
|
2980c1afac | ||
|
|
39054a7c3d | ||
|
|
4b7e47e265 | ||
|
|
4e7824583d | ||
|
|
ba53c74130 | ||
|
|
bc263e7afb | ||
|
|
bc27412425 | ||
|
|
fa8086d525 | ||
|
|
04534c2008 | ||
|
|
780fffb88f | ||
|
|
1e72d3c270 | ||
|
|
1e38d36161 | ||
|
|
bb9908e1fa | ||
|
|
6bafa83cef | ||
|
|
2c249781a2 | ||
|
|
8334ac031b | ||
|
|
635ca081e4 | ||
|
|
10f879f29a | ||
|
|
521e505a01 | ||
|
|
f968587f6f | ||
|
|
e70f8e74ec | ||
|
|
32fd01ed33 | ||
|
|
00718f8c9a | ||
|
|
20ee9d485d | ||
|
|
e3f66d7e22 | ||
|
|
be81e63eed | ||
|
|
2cf4e8ebce | ||
|
|
e6e98975ed | ||
|
|
ccb0df10e4 | ||
|
|
dd31d1e8c6 | ||
|
|
a494bad543 | ||
|
|
363699a175 | ||
|
|
439ef1ba90 | ||
|
|
a4f60f22cf | ||
|
|
f05cd66368 | ||
|
|
869d98d019 | ||
|
|
cff741e9ba | ||
|
|
9f105b5806 | ||
|
|
cac609d36f | ||
|
|
c319e7e707 | ||
|
|
c2f6bb152c | ||
|
|
f4b3c70fd4 | ||
|
|
a9db82ea21 | ||
|
|
ecf6f98858 | ||
|
|
e3a6204f2d | ||
|
|
19055baa49 | ||
|
|
604b53d9a4 | ||
|
|
27edd7cd93 | ||
|
|
fbd5b36170 | ||
|
|
19925038ba | ||
|
|
ae182bfd78 | ||
|
|
710b34a13a | ||
|
|
8e9535dd27 | ||
|
|
f4aa249138 | ||
|
|
57bac5d36b | ||
|
|
b6e5618a2e | ||
|
|
e475aa4c99 | ||
|
|
4ced66c236 | ||
|
|
1abcdee2f0 | ||
|
|
47f12f77f2 | ||
|
|
79227a1e7c | ||
|
|
bf41b25988 | ||
|
|
41edacfc81 | ||
|
|
9b32db9f62 | ||
|
|
f21eb5f272 | ||
|
|
d4cd0e763d | ||
|
|
8f06854130 | ||
|
|
81bad608bc | ||
|
|
eeed398155 | ||
|
|
f173c8b183 | ||
|
|
071d582250 | ||
|
|
e8f8bd21cf | ||
|
|
c0749fbb9f | ||
|
|
b317a3e506 | ||
|
|
06184a765c | ||
|
|
a2dae0d592 | ||
|
|
202e9b8fe3 | ||
|
|
ce23817c11 | ||
|
|
c49cf1c53c | ||
|
|
1bc427e7a6 | ||
|
|
ea592eb150 | ||
|
|
5864f8cb9a | ||
|
|
2be0ae8906 | ||
|
|
9a85a14970 | ||
|
|
6b0d048156 | ||
|
|
3421c2803a | ||
|
|
af6807826b | ||
|
|
1336ec562e | ||
|
|
1f5995ffc7 | ||
|
|
23ac82f845 | ||
|
|
492852ba0c | ||
|
|
d18df12951 | ||
|
|
64cf297399 | ||
|
|
0230cea16e | ||
|
|
4f5ed30298 | ||
|
|
9c5d91a1d9 | ||
|
|
24a5b54b67 | ||
|
|
8be6676ddb | ||
|
|
6a2dbc0105 | ||
|
|
5dda7d83da | ||
|
|
c066224a95 | ||
|
|
e475a9cdbc | ||
|
|
73a47f31a7 | ||
|
|
4109490789 | ||
|
|
e813436af7 | ||
|
|
5b87d90ffe | ||
|
|
ccbae6f496 | ||
|
|
1ac1c33bb1 | ||
|
|
bd42380f8a | ||
|
|
30dee18835 | ||
|
|
b509302711 | ||
|
|
e51c98c1dd | ||
|
|
bbb1387469 | ||
|
|
4f88774999 | ||
|
|
3968deb6d4 | ||
|
|
37c8465af8 | ||
|
|
d88a21d24a | ||
|
|
6ad2d106bc | ||
|
|
8c1fcee135 | ||
|
|
0514da9759 | ||
|
|
b2fed03f30 | ||
|
|
f5e45573af | ||
|
|
ddb2931f38 | ||
|
|
acf17ebace | ||
|
|
7af3c05b8b | ||
|
|
01de2ae714 | ||
|
|
cfa18d1bc3 | ||
|
|
127c63601e | ||
|
|
f079b0b49a | ||
|
|
6caf934d47 | ||
|
|
2f910fbad0 | ||
|
|
dac4e390aa | ||
|
|
812e0e9c9a | ||
|
|
05291a8a36 | ||
|
|
8bcc4d6a57 | ||
|
|
e06d5e1c8d | ||
|
|
1c8895f23f | ||
|
|
8b5d997322 | ||
|
|
33644a68b2 | ||
|
|
bc85ad5b65 | ||
|
|
fe895905bd | ||
|
|
3c5ccd7231 | ||
|
|
da140b0b85 | ||
|
|
c4d53d59b5 | ||
|
|
a48726d088 | ||
|
|
b49306607b | ||
|
|
3d15c60cb1 | ||
|
|
283f0cd263 | ||
|
|
66152401be | ||
|
|
b12412a3c1 | ||
|
|
ce1e8d868c | ||
|
|
3294043180 | ||
|
|
152fbaabda | ||
|
|
5756bdf8d7 | ||
|
|
80ee33fd3e | ||
|
|
955d80e2c1 | ||
|
|
67fe7f04da | ||
|
|
6395521f09 | ||
|
|
822078e640 | ||
|
|
fafd93f7dc | ||
|
|
00ce086e79 | ||
|
|
28653d6892 | ||
|
|
e30c67482f | ||
|
|
bda28e0404 | ||
|
|
ce63364299 | ||
|
|
f468dff6aa | ||
|
|
fab03006e8 | ||
|
|
8a565b8633 | ||
|
|
e79a6a5d47 | ||
|
|
95c2e20cb5 | ||
|
|
2e0f410978 | ||
|
|
fa1cd87348 | ||
|
|
e95d28e136 | ||
|
|
87ba71e77e | ||
|
|
dec0c0d3d1 | ||
|
|
776172bc88 | ||
|
|
d582548ed8 | ||
|
|
70ac31b907 | ||
|
|
cff9fd1ead | ||
|
|
319febb00d | ||
|
|
72fa2da2d3 | ||
|
|
3084c427f1 | ||
|
|
9cd1f013f8 | ||
|
|
a3f58d4302 | ||
|
|
d4cb89eafc | ||
|
|
33ba034336 | ||
|
|
e158c09160 | ||
|
|
c6ccd6d5de | ||
|
|
ec87864c34 | ||
|
|
a06ba403d0 | ||
|
|
dfbec46ded | ||
|
|
24be73ef63 | ||
|
|
3976c37d41 | ||
|
|
2bc15665b9 | ||
|
|
e4539dfeb1 | ||
|
|
1070e17310 | ||
|
|
b4f7eb36ef | ||
|
|
000f802baa | ||
|
|
e871ffcba0 | ||
|
|
8d2ffe3936 | ||
|
|
9e253420d2 | ||
|
|
edb7847e95 | ||
|
|
3d70148e0f | ||
|
|
7f89b197da | ||
|
|
32692bd54a | ||
|
|
7b2acec7c3 | ||
|
|
f1adf23631 | ||
|
|
a5d2fafad6 | ||
|
|
3d0a907b49 | ||
|
|
bacd00655d | ||
|
|
08e003b0f6 | ||
|
|
0f1c5163a1 | ||
|
|
18874d0d1e | ||
|
|
7f0a74c694 | ||
|
|
901fc87716 | ||
|
|
ee2ab4086f | ||
|
|
af94674c18 | ||
|
|
262289a398 | ||
|
|
467eab4ddf | ||
|
|
63517e4912 | ||
|
|
6f9487deb7 | ||
|
|
8d0edd5255 | ||
|
|
bdea153c82 | ||
|
|
d447883b7d | ||
|
|
03ec51a96c | ||
|
|
0adf18f5e6 | ||
|
|
5e7dc9ff21 | ||
|
|
33097382c6 | ||
|
|
b9df2cdabb | ||
|
|
158338508a | ||
|
|
640967d9ae | ||
|
|
ec973395da | ||
|
|
b35d99d935 | ||
|
|
c0f6e751d2 | ||
|
|
6af454ceed | ||
|
|
ed829dd43b | ||
|
|
a9adb4dda2 | ||
|
|
54a7eeda37 | ||
|
|
711e683c6f | ||
|
|
81c5e6d3d2 | ||
|
|
7a5a5d503a | ||
|
|
b597dbd80f | ||
|
|
ebdf724012 | ||
|
|
14f63e91a9 | ||
|
|
ad218ec65d | ||
|
|
9fda82564b | ||
|
|
a52fc54d80 | ||
|
|
524c342b5e | ||
|
|
f4fc084a0a | ||
|
|
38a2aa9d17 | ||
|
|
9e90242ddb | ||
|
|
fd0c1da608 | ||
|
|
68c4fccf98 | ||
|
|
3c93f4162d | ||
|
|
b6c314e180 | ||
|
|
62b465a889 | ||
|
|
9d0db78f64 | ||
|
|
d3393cb0fc | ||
|
|
79cded302f | ||
|
|
53d90a11de | ||
|
|
271ad57160 | ||
|
|
4adbe64a54 | ||
|
|
50a8a147fd | ||
|
|
eaea8e9368 | ||
|
|
9873baae9f | ||
|
|
bc3ce7395e | ||
|
|
8a7908c692 | ||
|
|
8021efd81a | ||
|
|
d7fcad2d0d | ||
|
|
b1d2d77263 | ||
|
|
2c772bd81b | ||
|
|
7f00011542 | ||
|
|
f76d8b8818 | ||
|
|
1d6b39dec9 | ||
|
|
5cfdf6c7e2 | ||
|
|
8410d83744 | ||
|
|
8a2dac9718 | ||
|
|
5ad2908760 | ||
|
|
5b8771485e | ||
|
|
ed8480caf0 | ||
|
|
42ef3c0fc2 | ||
|
|
e08ee9b7ff | ||
|
|
2c95bfcc3d | ||
|
|
86616e152d | ||
|
|
b1f478ee5e | ||
|
|
6b0f9fbdad | ||
|
|
da3f2b784a | ||
|
|
acb140ab78 | ||
|
|
0b74bd9bfe | ||
|
|
acfc030d16 | ||
|
|
d0d04ce376 | ||
|
|
2250f42d2a | ||
|
|
887434fea4 | ||
|
|
9b817c4b79 | ||
|
|
ea03bbfb2d | ||
|
|
db40cd35c6 | ||
|
|
aabac9e921 | ||
|
|
0a91c41e0a | ||
|
|
d6addc0d0b | ||
|
|
91d3b76be5 | ||
|
|
3eed009270 | ||
|
|
bc14d54cfa | ||
|
|
5496969e58 | ||
|
|
80c2a78273 | ||
|
|
92f378aefc | ||
|
|
877ceee698 | ||
|
|
7960b6a22e | ||
|
|
fa45d8a718 | ||
|
|
87574c9993 |
@@ -10,7 +10,7 @@
|
||||
"tasks": {
|
||||
"start-web": {
|
||||
"name": "Start Web",
|
||||
"command": "yarn nx dev @affine/web --port 8080",
|
||||
"command": "yarn dev-core",
|
||||
"runAtStart": true,
|
||||
"preview": {
|
||||
"port": 8080
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
[
|
||||
"electron",
|
||||
"server",
|
||||
"web",
|
||||
"core",
|
||||
"docs",
|
||||
"storybook",
|
||||
"component",
|
||||
|
||||
11
.env.template
Normal file
@@ -0,0 +1,11 @@
|
||||
ENABLE_PLUGIN=
|
||||
ENABLE_TEST_PROPERTIES=
|
||||
ENABLE_BC_PROVIDER=
|
||||
CHANGELOG_URL=
|
||||
ENABLE_PRELOADING=
|
||||
ENABLE_NEW_SETTING_MODAL=
|
||||
ENABLE_SQLITE_PROVIDER=
|
||||
ENABLE_NEW_SETTING_UNSTABLE_API=
|
||||
ENABLE_NOTIFICATION_CENTER=
|
||||
ENABLE_CLOUD=
|
||||
ENABLE_MOVE_DATABASE=
|
||||
12
.eslintrc.js
@@ -22,10 +22,15 @@ const createPattern = packageName => [
|
||||
allowTypeImports: false,
|
||||
},
|
||||
{
|
||||
group: ['@blocksuite/store'],
|
||||
group: ['@blocksuite /store'],
|
||||
message: "Import from '@blocksuite/global/utils'",
|
||||
importNames: ['assertExists', 'assertEquals'],
|
||||
},
|
||||
{
|
||||
group: ['react-router-dom'],
|
||||
message: 'Use `useNavigateHelper` instead',
|
||||
importNames: ['useNavigate'],
|
||||
},
|
||||
];
|
||||
|
||||
const allPackages = [
|
||||
@@ -144,6 +149,11 @@ const config = {
|
||||
message: "Import from '@blocksuite/global/utils'",
|
||||
importNames: ['assertExists', 'assertEquals'],
|
||||
},
|
||||
{
|
||||
group: ['react-router-dom'],
|
||||
message: 'Use `useNavigateHelper` instead',
|
||||
importNames: ['useNavigate'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
@@ -21,8 +21,8 @@ body:
|
||||
label: Distribution version
|
||||
description: What version of AFFiNE are you using?
|
||||
options:
|
||||
- macOS x64
|
||||
- macOS ARM 64
|
||||
- macOS x64 (Intel)
|
||||
- macOS ARM 64 (Apple Silicon)
|
||||
- Windows x64
|
||||
- Linux
|
||||
- Web (app.affine.pro)
|
||||
|
||||
2
.github/deployment/front/Dockerfile
vendored
@@ -1,6 +1,6 @@
|
||||
FROM openresty/openresty:1.21.4.1-0-buster
|
||||
WORKDIR /app
|
||||
COPY ./apps/web/out ./dist
|
||||
COPY ./apps/core/dist ./dist
|
||||
COPY ./.github/deployment/front/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
|
||||
COPY ./.github/deployment/front/affine.nginx.conf /etc/nginx/conf.d/affine.nginx.conf
|
||||
|
||||
|
||||
2
.github/labeler.yml
vendored
@@ -53,7 +53,7 @@ rust:
|
||||
|
||||
package:y-indexeddb: 'packages/y-indexeddb/**/*'
|
||||
|
||||
app:web: 'apps/web/**/*'
|
||||
app:core: 'apps/core/**/*'
|
||||
|
||||
app:electron: 'apps/electron/**/*'
|
||||
|
||||
|
||||
92
.github/workflows/build.yml
vendored
@@ -101,8 +101,8 @@ jobs:
|
||||
path: ./apps/storybook/storybook-static
|
||||
if-no-files-found: error
|
||||
|
||||
build-web:
|
||||
name: Build @affine/web
|
||||
build-core:
|
||||
name: Build @affine/core
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
@@ -110,13 +110,15 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Web
|
||||
run: yarn nx build @affine/web
|
||||
- name: Upload artifact
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
- name: Build Core
|
||||
run: yarn nx build @affine/core
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
path: ./apps/web/out
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
server-test:
|
||||
@@ -207,6 +209,48 @@ jobs:
|
||||
run: |
|
||||
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
|
||||
|
||||
e2e-plugin-test:
|
||||
name: E2E Plugin Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: tests/affine-plugin
|
||||
env:
|
||||
COVERAGE: true
|
||||
- name: Collect code coverage report
|
||||
run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
|
||||
|
||||
- name: Upload e2e test coverage results
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./.coverage/lcov.info
|
||||
flags: e2e-plugin-test
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-plugin
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -215,7 +259,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
environment: development
|
||||
needs: build-web
|
||||
needs: build-core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -224,11 +268,11 @@ jobs:
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download artifact
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
path: ./apps/web/out
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
@@ -260,7 +304,7 @@ jobs:
|
||||
name: E2E Migration Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: [build-web]
|
||||
needs: build-core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -270,11 +314,11 @@ jobs:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
|
||||
- name: Download next static
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
path: ./apps/web/out
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
|
||||
- name: Unzip
|
||||
run: yarn unzip
|
||||
@@ -333,11 +377,12 @@ jobs:
|
||||
target: x86_64-pc-windows-msvc,
|
||||
test: true,
|
||||
}
|
||||
needs: [build-web]
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
playwright-install: true
|
||||
hard-link-nm: false
|
||||
@@ -349,14 +394,13 @@ jobs:
|
||||
- name: Run unit tests
|
||||
if: ${{ matrix.spec.test }}
|
||||
shell: bash
|
||||
run: yarn nx test @affine/monorepo
|
||||
env:
|
||||
NATIVE_TEST: 'true'
|
||||
run: yarn vitest
|
||||
working-directory: ./apps/electron
|
||||
|
||||
- name: Download static resource artifact
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
@@ -443,11 +487,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download next static
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: next-js-static
|
||||
path: ./apps/web/out
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
- name: Download server dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
|
||||
7
.github/workflows/nightly-build.yml
vendored
@@ -62,10 +62,10 @@ jobs:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }}
|
||||
|
||||
- name: Upload Artifact (web-static)
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: before-make-web-static
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
make-distribution:
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }}
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: before-make-web-static
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
@@ -159,6 +159,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv apps/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
7
.github/workflows/release-desktop-app.yml
vendored
@@ -66,10 +66,10 @@ jobs:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version || steps.get-canary-version.outputs.RELEASE_VERSION }}
|
||||
|
||||
- name: Upload Artifact (web-static)
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: before-make-web-static
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
make-distribution:
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: before-make-web-static
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
@@ -159,6 +159,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv apps/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
1
.gitignore
vendored
@@ -13,6 +13,7 @@
|
||||
/out-tsc
|
||||
.nyc_output
|
||||
.coverage
|
||||
.swc
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
@@ -12,3 +12,5 @@ tests/affine-legacy/0.7.0-canary.18/static
|
||||
.github/helm
|
||||
_next
|
||||
storybook-static
|
||||
web-static
|
||||
public
|
||||
|
||||
14
README.md
@@ -126,10 +126,10 @@ If you have questions, you are welcome to contact us. One of the best places to
|
||||
>
|
||||
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
|
||||
|
||||
| Name | |
|
||||
| ------------------------------------------------ | ----------------------------------------- |
|
||||
| [@affine/bookmark-block](plugins/bookmark-block) | A block for bookmarking a website |
|
||||
| [@affine/copilot](plugins/copilot) | AI Copilot that help you document writing |
|
||||
| Name | |
|
||||
| ------------------------------------ | ----------------------------------------- |
|
||||
| [@affine/bookmark](plugins/bookmark) | A block for bookmarking a website |
|
||||
| [@affine/copilot](plugins/copilot) | AI Copilot that help you document writing |
|
||||
|
||||
## Thanks
|
||||
|
||||
@@ -197,10 +197,10 @@ See [LICENSE] for details.
|
||||
[jobs available]: ./docs/jobs.md
|
||||
[latest packages]: https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted
|
||||
[contributor license agreement]: https://github.com/toeverything/affine/edit/master/.github/CLA.md
|
||||
[rust-version-icon]: https://img.shields.io/badge/Rust-1.70.0-dea584
|
||||
[rust-version-icon]: https://img.shields.io/badge/Rust-1.71.0-dea584
|
||||
[stars-icon]: https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars
|
||||
[codecov]: https://codecov.io/gh/toeverything/affine/branch/master/graphs/badge.svg?branch=master
|
||||
[node-version-icon]: https://img.shields.io/badge/node-%3E=18.16.1-success
|
||||
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fweb%2Fpackage.json&color=rgb(97%2C228%2C251)
|
||||
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fweb%2Fpackage.json&label=blocksuite
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
|
||||
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fcore%2Fpackage.json&label=blocksuite
|
||||
|
||||
@@ -20,6 +20,6 @@ Server using [Nest.js](https://nestjs.com/).
|
||||
|
||||
Storybook using [Storybook](https://storybook.js.org/).
|
||||
|
||||
## web
|
||||
## Core
|
||||
|
||||
AFFiNE Core Application using [React.js](https://reactjs.org/).
|
||||
|
||||
82
apps/core/.webpack/cache-group.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
function testPackageName(regexp: RegExp): (module: any) => boolean {
|
||||
return (module: any) =>
|
||||
module.nameForCondition && regexp.test(module.nameForCondition());
|
||||
}
|
||||
|
||||
export const productionCacheGroups = {
|
||||
asyncVendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module: any) {
|
||||
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
|
||||
const name =
|
||||
module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)?.[1] ??
|
||||
'unknown';
|
||||
return `npm-async-${name}`;
|
||||
},
|
||||
priority: Number.MAX_SAFE_INTEGER,
|
||||
chunks: 'async' as const,
|
||||
},
|
||||
mui: {
|
||||
name: `npm-mui`,
|
||||
test: testPackageName(/[\\/]node_modules[\\/](mui|@mui)[\\/]/),
|
||||
priority: 200,
|
||||
enforce: true,
|
||||
},
|
||||
blocksuite: {
|
||||
name: `npm-blocksuite`,
|
||||
test: testPackageName(/[\\/]node_modules[\\/](@blocksuite)[\\/]/),
|
||||
priority: 200,
|
||||
enforce: true,
|
||||
},
|
||||
react: {
|
||||
name: `npm-react`,
|
||||
test: testPackageName(
|
||||
/[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/
|
||||
),
|
||||
priority: 200,
|
||||
enforce: true,
|
||||
},
|
||||
jotai: {
|
||||
name: `npm-jotai`,
|
||||
test: testPackageName(/[\\/]node_modules[\\/](jotai)[\\/]/),
|
||||
priority: 200,
|
||||
enforce: true,
|
||||
},
|
||||
rxjs: {
|
||||
name: `npm-rxjs`,
|
||||
test: testPackageName(/[\\/]node_modules[\\/]rxjs[\\/]/),
|
||||
priority: 200,
|
||||
enforce: true,
|
||||
},
|
||||
lodash: {
|
||||
name: `npm-lodash`,
|
||||
test: testPackageName(/[\\/]node_modules[\\/]lodash[\\/]/),
|
||||
priority: 200,
|
||||
enforce: true,
|
||||
},
|
||||
emotion: {
|
||||
name: `npm-emotion`,
|
||||
test: testPackageName(/[\\/]node_modules[\\/](@emotion)[\\/]/),
|
||||
priority: 200,
|
||||
enforce: true,
|
||||
},
|
||||
vendor: {
|
||||
name: 'vendor',
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: 190,
|
||||
enforce: true,
|
||||
},
|
||||
styles: {
|
||||
name: 'styles',
|
||||
test: (module: any) =>
|
||||
module.nameForCondition &&
|
||||
/\.css$/.test(module.nameForCondition()) &&
|
||||
!/^javascript/.test(module.type),
|
||||
chunks: 'all' as const,
|
||||
minSize: 1,
|
||||
minChunks: 1,
|
||||
reuseExistingChunk: true,
|
||||
priority: 1000,
|
||||
enforce: true,
|
||||
},
|
||||
};
|
||||
350
apps/core/.webpack/config.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
import { join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createRequire } from 'node:module';
|
||||
import HTMLPlugin from 'html-webpack-plugin';
|
||||
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
||||
import { PerfseePlugin } from '@perfsee/webpack';
|
||||
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
|
||||
|
||||
import CopyPlugin from 'copy-webpack-plugin';
|
||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
|
||||
import { productionCacheGroups } from './cache-group.js';
|
||||
import type { BuildFlags } from '@affine/cli/config';
|
||||
import { projectRoot } from '@affine/cli/config';
|
||||
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
|
||||
import { computeCacheKey } from './utils.js';
|
||||
import type { RuntimeConfig } from '@affine/env/global';
|
||||
|
||||
const IN_CI = !!process.env.CI;
|
||||
|
||||
export const rootPath = fileURLToPath(new URL('..', import.meta.url));
|
||||
|
||||
const require = createRequire(rootPath);
|
||||
|
||||
const OptimizeOptionOptions: (
|
||||
buildFlags: BuildFlags
|
||||
) => webpack.Configuration['optimization'] = buildFlags => ({
|
||||
minimize: buildFlags.mode === 'production',
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
exclude: [/\.min\.js$/, /plugins\/.+\/.+\.mjs$/],
|
||||
parallel: true,
|
||||
extractComments: true,
|
||||
terserOptions: {
|
||||
ecma: 2020,
|
||||
compress: {
|
||||
unused: true,
|
||||
},
|
||||
mangle: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
removeEmptyChunks: true,
|
||||
providedExports: true,
|
||||
usedExports: true,
|
||||
sideEffects: true,
|
||||
removeAvailableModules: true,
|
||||
runtimeChunk: {
|
||||
name: 'runtime',
|
||||
},
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
minSize: 1,
|
||||
minChunks: 1,
|
||||
maxInitialRequests: Number.MAX_SAFE_INTEGER,
|
||||
maxAsyncRequests: Number.MAX_SAFE_INTEGER,
|
||||
cacheGroups:
|
||||
buildFlags.mode === 'production'
|
||||
? productionCacheGroups
|
||||
: {
|
||||
default: false,
|
||||
vendors: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const createConfiguration: (
|
||||
buildFlags: BuildFlags,
|
||||
runtimeConfig: RuntimeConfig
|
||||
) => webpack.Configuration = (buildFlags, runtimeConfig) => {
|
||||
let publicPath = process.env.PUBLIC_PATH ?? '/';
|
||||
|
||||
const cacheKey = computeCacheKey(buildFlags);
|
||||
|
||||
const config = {
|
||||
name: 'affine',
|
||||
// to set a correct base path for the source map
|
||||
context: projectRoot,
|
||||
output: {
|
||||
environment: {
|
||||
module: true,
|
||||
dynamicImport: true,
|
||||
},
|
||||
filename:
|
||||
buildFlags.mode === 'production'
|
||||
? 'js/[name]-[contenthash:8].js'
|
||||
: 'js/[name].js',
|
||||
// In some cases webpack will emit files starts with "_" which is reserved in web extension.
|
||||
chunkFilename: 'js/chunk.[name].js',
|
||||
assetModuleFilename: 'assets/[contenthash:8][ext][query]',
|
||||
devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
|
||||
hotUpdateChunkFilename: 'hot/[id].[fullhash].js',
|
||||
hotUpdateMainFilename: 'hot/[runtime].[fullhash].json',
|
||||
path: join(rootPath, 'dist'),
|
||||
clean: buildFlags.mode === 'production',
|
||||
globalObject: 'globalThis',
|
||||
publicPath,
|
||||
},
|
||||
target: ['web', 'es2022'],
|
||||
|
||||
mode: buildFlags.mode,
|
||||
|
||||
devtool:
|
||||
buildFlags.mode === 'production'
|
||||
? buildFlags.distribution === 'desktop'
|
||||
? 'nosources-source-map'
|
||||
: 'source-map'
|
||||
: 'eval-cheap-module-source-map',
|
||||
|
||||
resolve: {
|
||||
symlinks: true,
|
||||
extensionAlias: {
|
||||
'.js': ['.js', '.tsx', '.ts'],
|
||||
'.mjs': ['.mjs', '.mts'],
|
||||
},
|
||||
extensions: ['.js', '.ts', '.tsx'],
|
||||
},
|
||||
|
||||
cache: {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
config: [fileURLToPath(import.meta.url)],
|
||||
},
|
||||
version: cacheKey,
|
||||
},
|
||||
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
// Treat as missing export as error
|
||||
strictExportPresence: true,
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js?$/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
// Compile all ts files in the workspace
|
||||
include: resolve(rootPath, '..', '..'),
|
||||
loader: require.resolve('swc-loader'),
|
||||
options: {
|
||||
// https://swc.rs/docs/configuring-swc/
|
||||
jsc: {
|
||||
preserveAllComments: true,
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
dynamicImport: true,
|
||||
topLevelAwait: false,
|
||||
tsx: true,
|
||||
},
|
||||
target: 'es2022',
|
||||
externalHelpers: true,
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
refresh: buildFlags.mode === 'development' && {
|
||||
refreshReg: '$RefreshReg$',
|
||||
refreshSig: '$RefreshSig$',
|
||||
emitFullSignatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
experimental: {
|
||||
keepImportAssertions: true,
|
||||
plugins: [
|
||||
buildFlags.coverage && [
|
||||
'swc-plugin-coverage-instrument',
|
||||
{},
|
||||
],
|
||||
].filter(Boolean),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
'thread-loader',
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
exclude: [/node_modules/],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg|webp)$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
test: /\.(ttf|eot|woff|woff2)$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
test: /\.txt$/,
|
||||
loader: 'raw-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
buildFlags.mode === 'development'
|
||||
? 'style-loader'
|
||||
: MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
url: false,
|
||||
sourceMap: false,
|
||||
modules: false,
|
||||
import: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
config: resolve(
|
||||
rootPath,
|
||||
'.webpack',
|
||||
'postcss.config.cjs'
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
...(IN_CI ? [] : [new webpack.ProgressPlugin({ percentBy: 'entries' })]),
|
||||
...(buildFlags.mode === 'development'
|
||||
? [new ReactRefreshWebpackPlugin({ overlay: false, esModule: true })]
|
||||
: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: `[name].[contenthash:8].css`,
|
||||
ignoreOrder: true,
|
||||
}),
|
||||
]),
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, '.webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'defer',
|
||||
minify: false,
|
||||
chunks: ['index', 'plugin'],
|
||||
filename: 'index.html',
|
||||
}),
|
||||
new VanillaExtractPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify({}),
|
||||
'process.env.COVERAGE': JSON.stringify(!!buildFlags.coverage),
|
||||
'process.env.NODE_ENV': JSON.stringify(buildFlags.mode),
|
||||
runtimeConfig: JSON.stringify(runtimeConfig),
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: resolve(rootPath, 'public'),
|
||||
to: resolve(rootPath, 'dist'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
||||
optimization: OptimizeOptionOptions(buildFlags),
|
||||
|
||||
devServer: {
|
||||
hot: 'only',
|
||||
liveReload: true,
|
||||
client: undefined,
|
||||
historyApiFallback: true,
|
||||
static: {
|
||||
directory: resolve(rootPath, 'public'),
|
||||
publicPath: '/',
|
||||
watch: true,
|
||||
},
|
||||
} as DevServerConfiguration,
|
||||
} satisfies webpack.Configuration;
|
||||
|
||||
if (buildFlags.mode === 'production' && process.env.PERFSEE_TOKEN) {
|
||||
config.devtool = 'hidden-nosources-source-map';
|
||||
config.plugins.push(
|
||||
new PerfseePlugin({
|
||||
project: 'affine-toeverything',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (buildFlags.mode === 'development') {
|
||||
config.optimization = {
|
||||
...config.optimization,
|
||||
minimize: false,
|
||||
runtimeChunk: false,
|
||||
splitChunks: {
|
||||
maxInitialRequests: Infinity,
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
defaultVendors: {
|
||||
test: `[\\/]node_modules[\\/](?!.*vanilla-extract)`,
|
||||
priority: -10,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
default: {
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
styles: {
|
||||
name: 'styles',
|
||||
type: 'css/mini-extract',
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.SENTRY_AUTH_TOKEN &&
|
||||
process.env.SENTRY_ORG &&
|
||||
process.env.SENTRY_PROJECT
|
||||
) {
|
||||
config.plugins.push(
|
||||
sentryWebpackPlugin({
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
20
apps/core/.webpack/postcss.config.cjs
Normal file
@@ -0,0 +1,20 @@
|
||||
const cssnano = require('cssnano');
|
||||
|
||||
module.exports = function (context) {
|
||||
const plugins = [
|
||||
cssnano({
|
||||
preset: [
|
||||
'default',
|
||||
{
|
||||
convertValues: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
return {
|
||||
from: context.from,
|
||||
plugins,
|
||||
to: context.to,
|
||||
};
|
||||
};
|
||||
115
apps/core/.webpack/runtime-config.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { BlockSuiteFeatureFlags, RuntimeConfig } from '@affine/env/global';
|
||||
import type { BuildFlags } from '@affine/cli/config';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
const editorFlags: BlockSuiteFeatureFlags = {
|
||||
enable_database: true,
|
||||
enable_slash_menu: true,
|
||||
enable_edgeless_toolbar: true,
|
||||
enable_block_hub: true,
|
||||
enable_drag_handle: true,
|
||||
enable_surface: true,
|
||||
enable_linked_page: true,
|
||||
enable_bookmark_operation: false,
|
||||
};
|
||||
|
||||
export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
const buildPreset: Record<string, RuntimeConfig> = {
|
||||
stable: {
|
||||
enablePlugin: false,
|
||||
enableTestProperties: false,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0728',
|
||||
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
enableNewSettingUnstableApi: false,
|
||||
enableSQLiteProvider: true,
|
||||
enableMoveDatabase: false,
|
||||
enableNotificationCenter: false,
|
||||
enableCloud: false,
|
||||
serverAPI: 'https://localhost:3010',
|
||||
editorFlags,
|
||||
appVersion: packageJson.version,
|
||||
editorVersion: packageJson.dependencies['@blocksuite/editor'],
|
||||
},
|
||||
// canary will be aggressive and enable all features
|
||||
canary: {
|
||||
enablePlugin: true,
|
||||
enableTestProperties: true,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
enableNewSettingUnstableApi: false,
|
||||
enableSQLiteProvider: true,
|
||||
enableMoveDatabase: false,
|
||||
enableNotificationCenter: true,
|
||||
enableCloud: false,
|
||||
serverAPI: 'https://localhost:3010',
|
||||
editorFlags,
|
||||
appVersion: packageJson.version,
|
||||
editorVersion: packageJson.dependencies['@blocksuite/editor'],
|
||||
},
|
||||
};
|
||||
|
||||
// beta and internal versions are the same as stable
|
||||
buildPreset.beta = buildPreset.stable;
|
||||
buildPreset.internal = buildPreset.stable;
|
||||
|
||||
const currentBuild = buildFlags.channel;
|
||||
|
||||
if (!(currentBuild in buildPreset)) {
|
||||
throw new Error(`BUILD_TYPE ${currentBuild} is not supported`);
|
||||
}
|
||||
|
||||
const currentBuildPreset = buildPreset[currentBuild];
|
||||
|
||||
const environmentPreset = {
|
||||
enablePlugin: process.env.ENABLE_PLUGIN
|
||||
? process.env.ENABLE_PLUGIN === 'true'
|
||||
: currentBuildPreset.enablePlugin,
|
||||
enableTestProperties: process.env.ENABLE_TEST_PROPERTIES
|
||||
? process.env.ENABLE_TEST_PROPERTIES === 'true'
|
||||
: currentBuildPreset.enableTestProperties,
|
||||
enableBroadcastChannelProvider: process.env.ENABLE_BC_PROVIDER
|
||||
? process.env.ENABLE_BC_PROVIDER !== 'false'
|
||||
: currentBuildPreset.enableBroadcastChannelProvider,
|
||||
changelogUrl: process.env.CHANGELOG_URL ?? currentBuildPreset.changelogUrl,
|
||||
enablePreloading: process.env.ENABLE_PRELOADING
|
||||
? process.env.ENABLE_PRELOADING === 'true'
|
||||
: currentBuildPreset.enablePreloading,
|
||||
enableNewSettingModal: process.env.ENABLE_NEW_SETTING_MODAL
|
||||
? process.env.ENABLE_NEW_SETTING_MODAL === 'true'
|
||||
: currentBuildPreset.enableNewSettingModal,
|
||||
enableSQLiteProvider: process.env.ENABLE_SQLITE_PROVIDER
|
||||
? process.env.ENABLE_SQLITE_PROVIDER === 'true'
|
||||
: currentBuildPreset.enableSQLiteProvider,
|
||||
enableNewSettingUnstableApi: process.env.ENABLE_NEW_SETTING_UNSTABLE_API
|
||||
? process.env.ENABLE_NEW_SETTING_UNSTABLE_API === 'true'
|
||||
: currentBuildPreset.enableNewSettingUnstableApi,
|
||||
enableNotificationCenter: process.env.ENABLE_NOTIFICATION_CENTER
|
||||
? process.env.ENABLE_NOTIFICATION_CENTER === 'true'
|
||||
: currentBuildPreset.enableNotificationCenter,
|
||||
enableCloud: process.env.ENABLE_CLOUD
|
||||
? process.env.ENABLE_CLOUD === 'true'
|
||||
: currentBuildPreset.enableCloud,
|
||||
enableMoveDatabase: process.env.ENABLE_MOVE_DATABASE
|
||||
? process.env.ENABLE_MOVE_DATABASE === 'true'
|
||||
: currentBuildPreset.enableMoveDatabase,
|
||||
};
|
||||
|
||||
return {
|
||||
...currentBuildPreset,
|
||||
// environment preset will overwrite current build preset
|
||||
// this environment variable is for debug proposes only
|
||||
// do not put them into CI
|
||||
...(process.env.CI ? {} : environmentPreset),
|
||||
};
|
||||
}
|
||||
45
apps/core/.webpack/template.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<title>AFFiNE</title>
|
||||
<meta name="theme-color" content="#fafafa" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" sizes="192x192" href="/chrome-192x192.png" />
|
||||
<meta name="emotion-insertion-point" content="" />
|
||||
<meta property="description" content="{description}" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:url" content="https://app.affine.pro/" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="AFFiNE:There can be more than Notion and Miro."
|
||||
/>
|
||||
<meta name="twitter:description" content="{description}" />
|
||||
<meta name="twitter:site" content="@AffineOfficial" />
|
||||
<meta name="twitter:image" content="https://affine.pro/og.jpeg" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="AFFiNE:There can be more than Notion and Miro."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together."
|
||||
/>
|
||||
<meta property="og:url" content="https://app.affine.pro/" />
|
||||
<meta property="og:image" content="https://affine.pro/og.jpeg" />
|
||||
<link
|
||||
data-react-helmet="true"
|
||||
rel="shortcut icon"
|
||||
href="https://affine.pro/favicon.ico"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
11
apps/core/.webpack/utils.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { BuildFlags } from '@affine/cli/config';
|
||||
|
||||
export function computeCacheKey(buildFlags: BuildFlags) {
|
||||
return [
|
||||
'1',
|
||||
'node' + process.version,
|
||||
buildFlags.mode,
|
||||
buildFlags.distribution,
|
||||
buildFlags.channel,
|
||||
].join('-');
|
||||
}
|
||||
28
apps/core/.webpack/webpack.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { createConfiguration, rootPath } from './config.js';
|
||||
import { merge } from 'webpack-merge';
|
||||
import { resolve } from 'node:path';
|
||||
import type { BuildFlags } from '@affine/cli/config';
|
||||
import { getRuntimeConfig } from './runtime-config.js';
|
||||
|
||||
export default async function (cli_env: any, _: any) {
|
||||
const flags: BuildFlags = JSON.parse(
|
||||
Buffer.from(cli_env.flags, 'hex').toString('utf-8')
|
||||
);
|
||||
console.log('build flags', flags);
|
||||
const runtimeConfig = getRuntimeConfig(flags);
|
||||
console.log('runtime config', runtimeConfig);
|
||||
const config = createConfiguration(flags, runtimeConfig);
|
||||
return merge(config, {
|
||||
entry: {
|
||||
index: {
|
||||
asyncChunks: false,
|
||||
import: resolve(rootPath, 'src/index.tsx'),
|
||||
},
|
||||
plugin: {
|
||||
dependOn: ['index'],
|
||||
asyncChunks: true,
|
||||
import: resolve(rootPath, 'src/bootstrap/register-plugins.ts'),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
82
apps/core/package.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.2",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
"static-server": "ts-node-esm ./server.mts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/debug": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/graphql": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/icons": "^2.1.27",
|
||||
"@blocksuite/lit": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230729011742-613f3782-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.2",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"graphql": "^16.7.1",
|
||||
"jotai": "^2.2.2",
|
||||
"jotai-devtools": "^0.6.0",
|
||||
"lit": "^2.7.6",
|
||||
"lottie-web": "^5.12.2",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss-loader": "^7.3.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-is": "18.2.0",
|
||||
"react-resizable-panels": "^0.0.54",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"ses": "^0.18.5",
|
||||
"swr": "2.1.5",
|
||||
"y-protocols": "^1.0.5",
|
||||
"yjs": "^13.6.7",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@perfsee/webpack": "^1.8.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@sentry/webpack-plugin": "^2.5.0",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
"@swc/core": "^1.3.71",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"express": "^4.18.2",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
"style-loader": "^3.3.3",
|
||||
"swc-loader": "^0.2.3",
|
||||
"swc-plugin-coverage-instrument": "^0.0.19",
|
||||
"thread-loader": "^4.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-merge": "^5.9.0"
|
||||
}
|
||||
}
|
||||
51
apps/core/project.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@affine/core",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"root": "apps/core",
|
||||
"sourceRoot": "apps/core/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-script",
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": [
|
||||
"{projectRoot}/**/*",
|
||||
"{workspaceRoot}/packages/component/src/**/*",
|
||||
"{workspaceRoot}/packages/debug/src/**/*",
|
||||
"{workspaceRoot}/packages/graphql/src/**/*",
|
||||
"{workspaceRoot}/packages/hooks/src/**/*",
|
||||
"{workspaceRoot}/packages/jotai/src/**/*",
|
||||
"{workspaceRoot}/packages/templates/src/**/*",
|
||||
"{workspaceRoot}/packages/workspace/src/**/*",
|
||||
{
|
||||
"env": "BUILD_TYPE"
|
||||
},
|
||||
{
|
||||
"env": "PERFSEE_TOKEN"
|
||||
},
|
||||
{
|
||||
"env": "SENTRY_ORG"
|
||||
},
|
||||
{
|
||||
"env": "SENTRY_PROJECT"
|
||||
},
|
||||
{
|
||||
"env": "SENTRY_AUTH_TOKEN"
|
||||
},
|
||||
{
|
||||
"env": "NEXT_PUBLIC_SENTRY_DSN"
|
||||
},
|
||||
{
|
||||
"env": "DISTRIBUTION"
|
||||
},
|
||||
{
|
||||
"env": "COVERAGE"
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"script": "build"
|
||||
},
|
||||
"outputs": ["{projectRoot}/dist", "{projectRoot}/public/plugins"]
|
||||
}
|
||||
}
|
||||
}
|
||||
4
apps/core/public/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.js
|
||||
*.map
|
||||
|
||||
plugins
|
||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 710 KiB After Width: | Height: | Size: 710 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 822 KiB After Width: | Height: | Size: 822 KiB |
|
Before Width: | Height: | Size: 363 KiB After Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 945 KiB After Width: | Height: | Size: 945 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 380 KiB After Width: | Height: | Size: 380 KiB |
|
Before Width: | Height: | Size: 6.8 MiB After Width: | Height: | Size: 6.8 MiB |
15
apps/core/server.mts
Normal file
@@ -0,0 +1,15 @@
|
||||
// static server for web app
|
||||
import express from 'express';
|
||||
const app = express();
|
||||
|
||||
const PORT = process.env.PORT || 8080;
|
||||
|
||||
app.use('/', express.static('dist'));
|
||||
|
||||
app.get('/*', (req, res) => {
|
||||
res.sendFile('index.html', { root: 'dist' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
CRUD,
|
||||
saveWorkspaceToLocalStorage,
|
||||
} from '@affine/workspace/local/crud';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/plugin-infra/__internal__/react';
|
||||
|
||||
@@ -36,7 +36,7 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
loadPriority: LoadPriority.LOW,
|
||||
Events: {
|
||||
'app:init': () => {
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
const blockSuiteWorkspace = getOrCreateWorkspace(
|
||||
nanoid(),
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
56
apps/core/src/app.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import '@affine/component/theme/global.css';
|
||||
import '@affine/component/theme/theme.css';
|
||||
|
||||
import { AffineContext } from '@affine/component/context';
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { createI18n, setUpLanguage } from '@affine/i18n';
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, memo, Suspense, useEffect } from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import { router } from './router';
|
||||
import createEmotionCache from './utils/create-emotion-cache';
|
||||
|
||||
const i18n = createI18n();
|
||||
const cache = createEmotionCache();
|
||||
|
||||
const DevTools = lazy(() =>
|
||||
import('jotai-devtools').then(m => ({ default: m.DevTools }))
|
||||
);
|
||||
|
||||
const DebugProvider = ({ children }: PropsWithChildren): ReactElement => {
|
||||
return (
|
||||
<>
|
||||
<Suspense>{process.env.DEBUG_JOTAI === 'true' && <DevTools />}</Suspense>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const future = {
|
||||
v7_startTransition: true,
|
||||
} as const;
|
||||
|
||||
export const App = memo(function App() {
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = i18n.language;
|
||||
// todo(himself65): this is a hack, we should use a better way to set the language
|
||||
setUpLanguage(i18n)?.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<CacheProvider value={cache}>
|
||||
<AffineContext>
|
||||
<DebugProvider>
|
||||
<RouterProvider
|
||||
fallbackElement={<WorkspaceFallback key="RouterFallback" />}
|
||||
router={router}
|
||||
future={future}
|
||||
/>
|
||||
</DebugProvider>
|
||||
</AffineContext>
|
||||
</CacheProvider>
|
||||
);
|
||||
});
|
||||
106
apps/core/src/atoms/history.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
|
||||
import { useCallback } from 'react';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { router } from '../router';
|
||||
|
||||
export type History = {
|
||||
stack: string[];
|
||||
current: number;
|
||||
skip: boolean;
|
||||
};
|
||||
|
||||
export const MAX_HISTORY = 50;
|
||||
|
||||
const historyBaseAtom = atomWithStorage<History>(
|
||||
'router-history',
|
||||
{
|
||||
stack: [],
|
||||
current: 0,
|
||||
skip: false,
|
||||
},
|
||||
createJSONStorage(() => sessionStorage)
|
||||
);
|
||||
|
||||
historyBaseAtom.onMount = set => {
|
||||
const unsubscribe = router.subscribe(state => {
|
||||
set(prev => {
|
||||
const url = state.location.pathname;
|
||||
|
||||
// if stack top is the same as current, skip
|
||||
if (prev.stack[prev.current] === url) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
if (prev.skip) {
|
||||
return {
|
||||
stack: [...prev.stack],
|
||||
current: prev.current,
|
||||
skip: false,
|
||||
};
|
||||
} else {
|
||||
if (prev.current < prev.stack.length - 1) {
|
||||
const newStack = prev.stack.slice(0, prev.current);
|
||||
newStack.push(url);
|
||||
if (newStack.length > MAX_HISTORY) {
|
||||
newStack.shift();
|
||||
}
|
||||
return {
|
||||
stack: newStack,
|
||||
current: newStack.length - 1,
|
||||
skip: false,
|
||||
};
|
||||
} else {
|
||||
const newStack = [...prev.stack, url];
|
||||
if (newStack.length > MAX_HISTORY) {
|
||||
newStack.shift();
|
||||
}
|
||||
return {
|
||||
stack: newStack,
|
||||
current: newStack.length - 1,
|
||||
skip: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
};
|
||||
|
||||
export function useHistoryAtom() {
|
||||
const navigate = useNavigate();
|
||||
const [base, setBase] = useAtom(historyBaseAtom);
|
||||
return [
|
||||
base,
|
||||
useCallback(
|
||||
(forward: boolean) => {
|
||||
setBase(prev => {
|
||||
if (forward) {
|
||||
const target = Math.min(prev.stack.length - 1, prev.current + 1);
|
||||
const url = prev.stack[target];
|
||||
navigate(url);
|
||||
return {
|
||||
...prev,
|
||||
current: target,
|
||||
skip: true,
|
||||
};
|
||||
} else {
|
||||
const target = Math.max(0, prev.current - 1);
|
||||
const url = prev.stack[target];
|
||||
navigate(url);
|
||||
return {
|
||||
...prev,
|
||||
current: target,
|
||||
skip: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
[setBase, navigate]
|
||||
),
|
||||
] as const;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/manager';
|
||||
import { currentPageIdAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { atom } from 'jotai/vanilla';
|
||||
|
||||
import { pageSettingFamily } from './index';
|
||||
133
apps/core/src/bootstrap/before-app.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import type {
|
||||
LocalIndexedDBDownloadProvider,
|
||||
WorkspaceAdapter,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
type RootWorkspaceMetadataV2,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import {
|
||||
migrateLocalBlobStorage,
|
||||
upgradeV1ToV2,
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { rootStore } from '@toeverything/plugin-infra/atom';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
console.log('setup global');
|
||||
setupGlobal();
|
||||
|
||||
rootStore.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
WorkspaceAdapter<WorkspaceFlavour>
|
||||
>
|
||||
);
|
||||
|
||||
const value = localStorage.getItem('jotai-workspaces');
|
||||
if (value) {
|
||||
try {
|
||||
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
||||
const promises: Promise<void>[] = [];
|
||||
const newMetadata = [...metadata];
|
||||
metadata.forEach(oldMeta => {
|
||||
if (!('version' in oldMeta)) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
const upgrade = async () => {
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (!workspace) {
|
||||
console.warn('cannot find workspace', oldMeta.id);
|
||||
return;
|
||||
}
|
||||
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const doc = workspace.blockSuiteWorkspace.doc;
|
||||
const provider = createIndexedDBDownloadProvider(workspace.id, doc, {
|
||||
awareness: workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
if (doc === newDoc) {
|
||||
console.log('doc not changed');
|
||||
return;
|
||||
}
|
||||
const newWorkspace = upgradeV1ToV2(workspace);
|
||||
|
||||
const newId = await adapter.CRUD.create(
|
||||
newWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
await adapter.CRUD.delete(workspace as any);
|
||||
console.log('migrated', oldMeta.id, newId);
|
||||
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
};
|
||||
await migrateLocalBlobStorage(workspace.id, newId);
|
||||
};
|
||||
|
||||
// create a new workspace and push it to metadata
|
||||
promises.push(upgrade());
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises)
|
||||
.then(() => {
|
||||
console.log('migration done');
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('migration failed');
|
||||
})
|
||||
.finally(() => {
|
||||
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
||||
window.dispatchEvent(new CustomEvent('migration-done'));
|
||||
window.$migrationDone = true;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('error when migrating data', e);
|
||||
}
|
||||
}
|
||||
|
||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
|
||||
return Plugins.flatMap(Plugin => {
|
||||
return Plugin.Events['app:init']?.().map(
|
||||
id =>
|
||||
({
|
||||
id,
|
||||
flavour: Plugin.flavour,
|
||||
// new workspace should all support sub-doc feature
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
}) satisfies RootWorkspaceMetadataV2
|
||||
);
|
||||
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
||||
};
|
||||
|
||||
await rootStore
|
||||
.get(rootWorkspacesMetadataAtom)
|
||||
.then(meta => {
|
||||
if (meta.length === 0 && localStorage.getItem('is-first-open') === null) {
|
||||
const result = createFirst();
|
||||
console.info('create first workspace', result);
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
rootStore.set(rootWorkspacesMetadataAtom, result).catch(console.error);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
86
apps/core/src/bootstrap/plugins/endowments/fercher.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
export interface FetchOptions {
|
||||
fetch?: typeof fetch;
|
||||
signal?: AbortSignal;
|
||||
|
||||
normalizeURL?(url: string): string;
|
||||
|
||||
/**
|
||||
* Virtualize a url
|
||||
* @param url URL to be rewrite
|
||||
* @param direction Direction of this rewrite.
|
||||
* 'in' means the url is from the outside world and should be virtualized.
|
||||
* 'out' means the url is from the inside world and should be de-virtualized to fetch the real target.
|
||||
*/
|
||||
rewriteURL?(url: string, direction: 'in' | 'out'): string;
|
||||
|
||||
replaceRequest?(request: Request): Request | PromiseLike<Request>;
|
||||
|
||||
replaceResponse?(response: Response): Response | PromiseLike<Response>;
|
||||
|
||||
canConnect?(url: string): boolean | PromiseLike<boolean>;
|
||||
}
|
||||
|
||||
export function createFetch(options: FetchOptions) {
|
||||
const {
|
||||
fetch: _fetch = fetch,
|
||||
signal,
|
||||
rewriteURL,
|
||||
replaceRequest,
|
||||
replaceResponse,
|
||||
canConnect,
|
||||
normalizeURL,
|
||||
} = options;
|
||||
|
||||
return async function fetch(input: RequestInfo, init?: RequestInit) {
|
||||
let request = new Request(input, {
|
||||
...init,
|
||||
signal: getMergedSignal(init?.signal, signal) || null,
|
||||
});
|
||||
|
||||
if (normalizeURL) request = new Request(normalizeURL(request.url), request);
|
||||
if (canConnect && !(await canConnect(request.url)))
|
||||
throw new TypeError('Failed to fetch');
|
||||
if (rewriteURL)
|
||||
request = new Request(rewriteURL(request.url, 'out'), request);
|
||||
if (replaceRequest) request = await replaceRequest(request);
|
||||
|
||||
let response = await _fetch(request);
|
||||
|
||||
if (rewriteURL) {
|
||||
const { url, redirected, type } = response;
|
||||
// Note: Response constructor does not allow us to set the url of a response.
|
||||
// we have to define the own property on it. This is not a good simulation.
|
||||
// To prevent get the original url by Response.prototype.[[get url]].call(response)
|
||||
// we copy a response and set it's url to empty.
|
||||
response = new Response(response.body, response);
|
||||
Object.defineProperties(response, {
|
||||
url: { value: url, configurable: true },
|
||||
redirected: { value: redirected, configurable: true },
|
||||
type: { value: type, configurable: true },
|
||||
});
|
||||
Object.defineProperty(response, 'url', {
|
||||
configurable: true,
|
||||
value: rewriteURL(url, 'in'),
|
||||
});
|
||||
}
|
||||
if (replaceResponse) response = await replaceResponse(response);
|
||||
return response;
|
||||
};
|
||||
}
|
||||
|
||||
function getMergedSignal(
|
||||
signal: AbortSignal | undefined | null,
|
||||
signal2: AbortSignal | undefined | null
|
||||
) {
|
||||
if (!signal) return signal2;
|
||||
if (!signal2) return signal;
|
||||
|
||||
const abortController = new AbortController();
|
||||
signal.addEventListener('abort', () => abortController.abort(), {
|
||||
once: true,
|
||||
});
|
||||
signal2.addEventListener('abort', () => abortController.abort(), {
|
||||
once: true,
|
||||
});
|
||||
return abortController.signal;
|
||||
}
|
||||
110
apps/core/src/bootstrap/plugins/endowments/timer.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
type Handler = (...args: any[]) => void;
|
||||
|
||||
export interface Timers {
|
||||
setTimeout: (handler: Handler, timeout?: number, ...args: any[]) => number;
|
||||
clearTimeout: (handle: number) => void;
|
||||
setInterval: (handler: Handler, timeout?: number, ...args: any[]) => number;
|
||||
clearInterval: (handle: number) => void;
|
||||
requestAnimationFrame: (callback: Handler) => number;
|
||||
cancelAnimationFrame: (handle: number) => void;
|
||||
requestIdleCallback?: typeof window.requestIdleCallback | undefined;
|
||||
cancelIdleCallback?: typeof window.cancelIdleCallback | undefined;
|
||||
queueMicrotask: typeof window.queueMicrotask;
|
||||
}
|
||||
|
||||
export function createTimers(
|
||||
abortSignal: AbortSignal,
|
||||
originalTimes: Timers = {
|
||||
requestAnimationFrame,
|
||||
cancelAnimationFrame,
|
||||
requestIdleCallback:
|
||||
typeof requestIdleCallback === 'function'
|
||||
? requestIdleCallback
|
||||
: undefined,
|
||||
cancelIdleCallback:
|
||||
typeof cancelIdleCallback === 'function' ? cancelIdleCallback : undefined,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
queueMicrotask,
|
||||
}
|
||||
): Timers {
|
||||
const {
|
||||
requestAnimationFrame: _requestAnimationFrame,
|
||||
cancelAnimationFrame: _cancelAnimationFrame,
|
||||
setInterval: _setInterval,
|
||||
clearInterval: _clearInterval,
|
||||
setTimeout: _setTimeout,
|
||||
clearTimeout: _clearTimeout,
|
||||
cancelIdleCallback: _cancelIdleCallback,
|
||||
requestIdleCallback: _requestIdleCallback,
|
||||
queueMicrotask: _queueMicrotask,
|
||||
} = originalTimes;
|
||||
|
||||
const interval_timer_id: number[] = [];
|
||||
const idle_id: number[] = [];
|
||||
const raf_id: number[] = [];
|
||||
|
||||
abortSignal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
raf_id.forEach(_cancelAnimationFrame);
|
||||
interval_timer_id.forEach(_clearInterval);
|
||||
_cancelIdleCallback && idle_id.forEach(_cancelIdleCallback);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
return {
|
||||
// id is a positive number, it never repeats.
|
||||
requestAnimationFrame(callback) {
|
||||
raf_id[raf_id.length] = _requestAnimationFrame(callback);
|
||||
return raf_id.length;
|
||||
},
|
||||
cancelAnimationFrame(handle) {
|
||||
const id = raf_id[handle - 1];
|
||||
if (!id) return;
|
||||
_cancelAnimationFrame(id);
|
||||
},
|
||||
setInterval(handler, timeout) {
|
||||
interval_timer_id[interval_timer_id.length] = (_setInterval as any)(
|
||||
handler,
|
||||
timeout
|
||||
);
|
||||
return interval_timer_id.length;
|
||||
},
|
||||
clearInterval(id) {
|
||||
if (!id) return;
|
||||
const handle = interval_timer_id[id - 1];
|
||||
if (!handle) return;
|
||||
_clearInterval(handle);
|
||||
},
|
||||
setTimeout(handler, timeout) {
|
||||
idle_id[idle_id.length] = (_setTimeout as any)(handler, timeout);
|
||||
return idle_id.length;
|
||||
},
|
||||
clearTimeout(id) {
|
||||
if (!id) return;
|
||||
const handle = idle_id[id - 1];
|
||||
if (!handle) return;
|
||||
_clearTimeout(handle);
|
||||
},
|
||||
requestIdleCallback: _requestIdleCallback
|
||||
? function requestIdleCallback(callback, options) {
|
||||
idle_id[idle_id.length] = _requestIdleCallback(callback, options);
|
||||
return idle_id.length;
|
||||
}
|
||||
: undefined,
|
||||
cancelIdleCallback: _cancelIdleCallback
|
||||
? function cancelIdleCallback(handle) {
|
||||
const id = idle_id[handle - 1];
|
||||
if (!id) return;
|
||||
_cancelIdleCallback(id);
|
||||
}
|
||||
: undefined,
|
||||
queueMicrotask(callback) {
|
||||
_queueMicrotask(() => abortSignal.aborted || callback());
|
||||
},
|
||||
};
|
||||
}
|
||||
141
apps/core/src/bootstrap/plugins/setup.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import * as AFFiNEComponent from '@affine/component';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
|
||||
import * as BlockSuiteGlobalUtils from '@blocksuite/global/utils';
|
||||
import * as Icons from '@blocksuite/icons';
|
||||
import * as Atom from '@toeverything/plugin-infra/atom';
|
||||
import * as Jotai from 'jotai/index';
|
||||
import * as JotaiUtils from 'jotai/utils';
|
||||
import * as React from 'react';
|
||||
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import * as ReactDomClient from 'react-dom/client';
|
||||
import * as SWR from 'swr';
|
||||
|
||||
import { createFetch } from './endowments/fercher';
|
||||
import { createTimers } from './endowments/timer';
|
||||
|
||||
const logger = new DebugLogger('plugins:permission');
|
||||
|
||||
const setupImportsMap = () => {
|
||||
importsMap.set('react', new Map(Object.entries(React)));
|
||||
importsMap.set('react/jsx-runtime', new Map(Object.entries(ReactJSXRuntime)));
|
||||
importsMap.set('react-dom', new Map(Object.entries(ReactDom)));
|
||||
importsMap.set('react-dom/client', new Map(Object.entries(ReactDomClient)));
|
||||
importsMap.set('@blocksuite/icons', new Map(Object.entries(Icons)));
|
||||
importsMap.set('@affine/component', new Map(Object.entries(AFFiNEComponent)));
|
||||
importsMap.set(
|
||||
'@blocksuite/blocks/std',
|
||||
new Map(Object.entries(BlockSuiteBlocksStd))
|
||||
);
|
||||
importsMap.set(
|
||||
'@blocksuite/global/utils',
|
||||
new Map(Object.entries(BlockSuiteGlobalUtils))
|
||||
);
|
||||
importsMap.set('jotai', new Map(Object.entries(Jotai)));
|
||||
importsMap.set('jotai/utils', new Map(Object.entries(JotaiUtils)));
|
||||
importsMap.set(
|
||||
'@toeverything/plugin-infra/atom',
|
||||
new Map(Object.entries(Atom))
|
||||
);
|
||||
importsMap.set('swr', new Map(Object.entries(SWR)));
|
||||
};
|
||||
|
||||
const importsMap = new Map<string, Map<string, any>>();
|
||||
setupImportsMap();
|
||||
export { importsMap };
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const pluginFetch = createFetch({});
|
||||
const timer = createTimers(abortController.signal);
|
||||
|
||||
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
|
||||
fetch: pluginFetch,
|
||||
});
|
||||
|
||||
export const createGlobalThis = (name: string) => {
|
||||
return Object.assign(Object.create(null), sharedGlobalThis, {
|
||||
process: Object.freeze({
|
||||
env: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
},
|
||||
}),
|
||||
// UNSAFE: React will read `window` and `document`
|
||||
window: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
logger.debug(`${name} is accessing window`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(window, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
logger.debug(`${name} is calling window`, key, args);
|
||||
return result.apply(window, args);
|
||||
};
|
||||
}
|
||||
logger.debug('window', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
document: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
logger.debug(`${name} is accessing document`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(document, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
logger.debug(`${name} is calling window`, key, args);
|
||||
return result.apply(document, args);
|
||||
};
|
||||
}
|
||||
logger.debug('document', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
navigator: {
|
||||
userAgent: navigator.userAgent,
|
||||
},
|
||||
|
||||
// safe to use for all plugins
|
||||
Error: globalThis.Error,
|
||||
TypeError: globalThis.TypeError,
|
||||
RangeError: globalThis.RangeError,
|
||||
console: globalThis.console,
|
||||
crypto: globalThis.crypto,
|
||||
|
||||
// copilot uses these
|
||||
CustomEvent: globalThis.CustomEvent,
|
||||
Date: globalThis.Date,
|
||||
Math: globalThis.Math,
|
||||
URL: globalThis.URL,
|
||||
URLSearchParams: globalThis.URLSearchParams,
|
||||
Headers: globalThis.Headers,
|
||||
TextEncoder: globalThis.TextEncoder,
|
||||
TextDecoder: globalThis.TextDecoder,
|
||||
Request: globalThis.Request,
|
||||
|
||||
// image-preview uses these
|
||||
Blob: globalThis.Blob,
|
||||
ClipboardItem: globalThis.ClipboardItem,
|
||||
|
||||
// fixme: use our own db api
|
||||
indexedDB: globalThis.indexedDB,
|
||||
IDBRequest: globalThis.IDBRequest,
|
||||
IDBDatabase: globalThis.IDBDatabase,
|
||||
IDBCursorWithValue: globalThis.IDBCursorWithValue,
|
||||
IDBFactory: globalThis.IDBFactory,
|
||||
IDBKeyRange: globalThis.IDBKeyRange,
|
||||
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
|
||||
IDBTransaction: globalThis.IDBTransaction,
|
||||
IDBObjectStore: globalThis.IDBObjectStore,
|
||||
IDBIndex: globalThis.IDBIndex,
|
||||
IDBCursor: globalThis.IDBCursor,
|
||||
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
|
||||
});
|
||||
};
|
||||
203
apps/core/src/bootstrap/register-plugins.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
/// <reference types="@types/webpack-env" />
|
||||
import 'ses';
|
||||
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { FormatQuickBar } from '@blocksuite/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import {
|
||||
editorItemsAtom,
|
||||
headerItemsAtom,
|
||||
registeredPluginAtom,
|
||||
rootStore,
|
||||
settingItemsAtom,
|
||||
windowItemsAtom,
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import type {
|
||||
CallbackMap,
|
||||
PluginContext,
|
||||
} from '@toeverything/plugin-infra/entry';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { createGlobalThis, importsMap } from './plugins/setup';
|
||||
|
||||
if (!process.env.COVERAGE) {
|
||||
lockdown({
|
||||
evalTaming: 'unsafeEval',
|
||||
overrideTaming: 'severe',
|
||||
consoleTaming: 'unsafe',
|
||||
errorTaming: 'unsafe',
|
||||
errorTrapping: 'platform',
|
||||
unhandledRejectionTrapping: 'report',
|
||||
});
|
||||
}
|
||||
|
||||
const imports = (
|
||||
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
|
||||
) => {
|
||||
for (const [module, moduleUpdaters] of newUpdaters) {
|
||||
const moduleImports = importsMap.get(module);
|
||||
if (moduleImports) {
|
||||
for (const [importName, importUpdaters] of moduleUpdaters) {
|
||||
const updateImport = (value: any) => {
|
||||
for (const importUpdater of importUpdaters) {
|
||||
importUpdater(value);
|
||||
}
|
||||
};
|
||||
if (moduleImports.has(importName)) {
|
||||
const val = moduleImports.get(importName);
|
||||
updateImport(val);
|
||||
} else {
|
||||
console.log('import not found', importName, module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const builtinPluginUrl = new Set([
|
||||
'/plugins/bookmark',
|
||||
'/plugins/copilot',
|
||||
'/plugins/hello-world',
|
||||
'/plugins/image-preview',
|
||||
]);
|
||||
|
||||
const logger = new DebugLogger('register-plugins');
|
||||
|
||||
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
createElement(
|
||||
Provider,
|
||||
{
|
||||
store: rootStore,
|
||||
},
|
||||
children
|
||||
);
|
||||
|
||||
const group = new DisposableGroup();
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __pluginPackageJson__: unknown[];
|
||||
}
|
||||
|
||||
globalThis.__pluginPackageJson__ = [];
|
||||
|
||||
await Promise.all(
|
||||
[...builtinPluginUrl].map(url => {
|
||||
return fetch(`${url}/package.json`)
|
||||
.then(async res => {
|
||||
const packageJson = await res.json();
|
||||
const {
|
||||
name: pluginName,
|
||||
affinePlugin: {
|
||||
release,
|
||||
entry: { core },
|
||||
assets,
|
||||
},
|
||||
} = packageJson;
|
||||
globalThis.__pluginPackageJson__.push(packageJson);
|
||||
logger.debug(`registering plugin ${pluginName}`);
|
||||
logger.debug(`package.json: ${packageJson}`);
|
||||
if (!release && process.env.NODE_ENV === 'production') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const pluginCompartment = new Compartment(createGlobalThis(pluginName));
|
||||
const baseURL = url;
|
||||
const entryURL = `${baseURL}/${core}`;
|
||||
rootStore.set(registeredPluginAtom, prev => [...prev, pluginName]);
|
||||
await fetch(entryURL).then(async res => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async (asset: string) => {
|
||||
if (asset.endsWith('.css')) {
|
||||
const res = await fetch(`${baseURL}/${asset}`);
|
||||
if (res.ok) {
|
||||
// todo: how to put css file into sandbox?
|
||||
return res.text().then(text => {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('plugin-id', pluginName);
|
||||
style.textContent = text;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
const codeText = await res.text();
|
||||
try {
|
||||
const entryPoint = pluginCompartment.evaluate(codeText, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
entryPoint({
|
||||
imports,
|
||||
onceVar: {
|
||||
entry: (
|
||||
entryFunction: (context: PluginContext) => () => void
|
||||
) => {
|
||||
const cleanup = entryFunction({
|
||||
register: (part, callback) => {
|
||||
logger.info(`Registering ${pluginName} to ${part}`);
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(headerItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(editorItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
} else if (part === 'window') {
|
||||
rootStore.set(windowItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['window'],
|
||||
}));
|
||||
} else if (part === 'setting') {
|
||||
rootStore.set(settingItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['setting'],
|
||||
}));
|
||||
} else if (part === 'formatBar') {
|
||||
FormatQuickBar.customElements.push(
|
||||
(page, getBlockRange) => {
|
||||
const div = document.createElement('div');
|
||||
(callback as CallbackMap['formatBar'])(
|
||||
div,
|
||||
page,
|
||||
getBlockRange
|
||||
);
|
||||
return div;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
});
|
||||
if (typeof cleanup !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
group.add(cleanup);
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(pluginName, e);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(`error when fetch plugin from ${url}`, e);
|
||||
});
|
||||
})
|
||||
).then(() => {
|
||||
console.info('All plugins loaded');
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import type React from 'react';
|
||||
@@ -8,7 +8,7 @@ import { useCallback } from 'react';
|
||||
|
||||
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
|
||||
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
const blockSuiteWorkspace = getOrCreateWorkspace(
|
||||
'test',
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
@@ -9,17 +9,14 @@ import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
} from '@toeverything/plugin-infra/manager';
|
||||
} from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
|
||||
import type React from 'react';
|
||||
import { Component } from 'react';
|
||||
|
||||
export type AffineErrorBoundaryProps = React.PropsWithChildren<{
|
||||
router: NextRouter;
|
||||
}>;
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
export type AffineErrorBoundaryProps = React.PropsWithChildren;
|
||||
|
||||
type AffineError =
|
||||
| QueryParamError
|
||||
@@ -32,13 +29,13 @@ interface AffineErrorBoundaryState {
|
||||
error: AffineError | null;
|
||||
}
|
||||
|
||||
export const DumpInfo = (props: Pick<AffineErrorBoundaryProps, 'router'>) => {
|
||||
const router = props.router;
|
||||
export const DumpInfo = () => {
|
||||
const location = useLocation();
|
||||
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const path = router.asPath;
|
||||
const query = router.query;
|
||||
const path = location.pathname;
|
||||
const query = useParams();
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
@@ -91,24 +88,6 @@ export class AffineErrorBoundary extends Component<
|
||||
Cannot find page {error.pageId} in workspace{' '}
|
||||
{error.workspace.id}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
this.props.router
|
||||
.replace({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
workspaceId: error.workspace.id,
|
||||
pageId: error.workspace.meta.pageMetas[0].id,
|
||||
},
|
||||
})
|
||||
.finally(() => {
|
||||
this.setState({ error: null });
|
||||
});
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
refresh{' '}
|
||||
</button>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
@@ -124,7 +103,7 @@ export class AffineErrorBoundary extends Component<
|
||||
<>
|
||||
{errorDetail}
|
||||
<Provider key="JotaiProvider" store={rootStore}>
|
||||
<DumpInfo router={this.props.router} />
|
||||
<DumpInfo />
|
||||
</Provider>
|
||||
</>
|
||||
);
|
||||
@@ -79,7 +79,7 @@ const NameWorkspaceContent = ({
|
||||
<div className={style.buttonGroup}>
|
||||
<Button
|
||||
data-testid="create-workspace-close-button"
|
||||
type="light"
|
||||
type="primary"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t.Cancel()}
|
||||
@@ -155,7 +155,7 @@ const SetDBLocationContent = ({
|
||||
<Button
|
||||
disabled={opening}
|
||||
data-testid="create-workspace-customize-button"
|
||||
type="light"
|
||||
type="primary"
|
||||
onClick={handleSelectDBFileLocation}
|
||||
>
|
||||
{t['Customize']()}
|
||||
@@ -47,7 +47,7 @@ const LanguageMenuContent: FC<{
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu: FC<{ triggerProps: ButtonProps }> = ({
|
||||
export const LanguageMenu: FC<{ triggerProps?: ButtonProps }> = ({
|
||||
triggerProps,
|
||||
}) => {
|
||||
const i18n = useI18N();
|
||||
@@ -92,15 +92,15 @@ export const WorkspaceDeleteModal = ({
|
||||
/>
|
||||
</StyledInputContent>
|
||||
<StyledButtonContent>
|
||||
<Button shape="circle" onClick={onClose}>
|
||||
<Button onClick={onClose} size="large">
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="delete-workspace-confirm-button"
|
||||
disabled={!allowDelete}
|
||||
onClick={handleDelete}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
size="large"
|
||||
type="error"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t['Delete']()}
|
||||