Compare commits

..

139 Commits

Author SHA1 Message Date
Alex Yang
4109490789 v0.7.0-canary.42 2023-07-12 23:01:11 +08:00
Peng Xiao
e813436af7 fix: iconUrl for windows build (#3194) 2023-07-12 14:35:45 +00:00
Alex Yang
5b87d90ffe fix: first page id conflict (#3192) 2023-07-12 10:43:52 +00:00
Alex Yang
ccbae6f496 fix: unexpected jump 404 page (#3190) 2023-07-12 10:18:02 +00:00
JimmFly
1ac1c33bb1 style: update delete button style (#3180) 2023-07-12 09:23:39 +00:00
Peng Xiao
bd42380f8a fix: add default fonts (#3185) 2023-07-12 08:43:25 +00:00
xiaodong zuo
30dee18835 fix: enhancing the security of image proxy (#3176) 2023-07-12 08:35:46 +00:00
Alex Yang
b509302711 v0.7.0-canary.41 2023-07-12 14:49:08 +08:00
Alex Yang
e51c98c1dd chore: bump version (#3179) 2023-07-12 06:21:11 +00:00
Alex Yang
bbb1387469 feat: display app version in setting panel (#3170) 2023-07-12 02:39:00 +00:00
xiaodong zuo
4f88774999 fix: the image lost after exporting (#3150)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-12 02:21:23 +00:00
Alex Yang
3968deb6d4 feat: add suspense to workspace settings (#3167)
Co-authored-by: Qi <474021214@qq.com>
2023-07-11 15:50:30 +00:00
Alex Yang
37c8465af8 fix: jump to index page after deletion (#3169) 2023-07-11 15:44:00 +00:00
Peng Xiao
d88a21d24a fix: settings style update (#3161) 2023-07-11 12:55:28 +00:00
3720
6ad2d106bc fix: some typo and i18n (#3155) 2023-07-11 11:04:45 +00:00
Alex Yang
8c1fcee135 refactor: remove unused code (#3149) 2023-07-11 08:53:01 +00:00
Peng Xiao
0514da9759 fix: updater not working (#3144) 2023-07-11 07:06:04 +00:00
JimmFly
b2fed03f30 style: modify the style of community item (#3143) 2023-07-11 06:44:06 +00:00
Alex Yang
f5e45573af v0.7.0-canary.40 2023-07-11 12:59:12 +08:00
Alex Yang
ddb2931f38 fix: remove workspace not working (#3140) 2023-07-11 04:37:47 +00:00
Alex Yang
acf17ebace chore: bump version (#3138) 2023-07-11 04:28:01 +00:00
Alex Yang
7af3c05b8b v0.7.0-canary.39 2023-07-10 21:00:06 +08:00
Alex Yang
01de2ae714 revert: restrict node version 2023-07-10 20:51:49 +08:00
Qi
cfa18d1bc3 fix: font style setting only control editor's font (#3117)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-10 11:58:53 +00:00
Alex Yang
127c63601e chore: bump version (#3131) 2023-07-10 11:34:43 +00:00
LongYinan
f079b0b49a fix: add semver into server dependencies 2023-07-10 19:32:39 +08:00
Alex Yang
6caf934d47 refactor: follow correct react rules (#3119) 2023-07-10 10:32:15 +00:00
Qi
2f910fbad0 feat: modify setting modal entry in quick search modal (#3089) 2023-07-10 09:28:14 +00:00
Peng Xiao
dac4e390aa fix: add DB migration to add workspace (#3115) 2023-07-10 08:03:18 +00:00
JimmFly
812e0e9c9a style: change switch tip color (#3123) 2023-07-10 07:00:23 +00:00
Alex Yang
05291a8a36 chore: restrict node version (#3120) 2023-07-10 06:19:59 +00:00
JimmFly
8bcc4d6a57 test: fix incorrect day suffix (#3121) 2023-07-10 05:56:12 +00:00
danielchim
e06d5e1c8d fix: page mode shortcut (#3097) 2023-07-09 18:37:49 +00:00
Alex Yang
1c8895f23f feat: improve error log message (#3112) 2023-07-09 05:54:53 +00:00
Alex Yang
8b5d997322 refactor(hooks): reduce null types (#3111) 2023-07-09 05:01:09 +00:00
Peng Xiao
33644a68b2 fix: disable move db by default (#3105)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-09 03:37:39 +00:00
mon-jai
bc85ad5b65 fix: sidebar noise background on Windows (#3107) 2023-07-08 16:41:07 +00:00
Alex Yang
fe895905bd v0.7.0-canary.38 2023-07-08 15:57:31 +08:00
Alex Yang
3c5ccd7231 fix: init workspace before loaded (#3104) 2023-07-08 07:42:30 +00:00
Alex Yang
da140b0b85 chore: remove unused code (#3102) 2023-07-08 06:49:11 +00:00
Alex Yang
c4d53d59b5 test: fix flaky (#3100) 2023-07-08 06:30:17 +00:00
boomlion8
a48726d088 fix: color of UI in dark mode (#3081)
Co-authored-by: boomlion8 <201116201@manit.ac.in>
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-08 06:00:03 +00:00
Alex Yang
b49306607b feat: improve workspace hook (#3099) 2023-07-08 05:43:39 +00:00
Alex Yang
3d15c60cb1 v0.7.0-canary.37 2023-07-08 02:55:18 +08:00
Alex Yang
283f0cd263 refactor: lazy load workspaces (#3091) 2023-07-07 14:15:27 +00:00
JimmFly
66152401be chore: add new item for share component (#3084) 2023-07-07 13:16:49 +00:00
Qi
b12412a3c1 feat: add font style setting (#3092) 2023-07-07 11:59:38 +00:00
Peng Xiao
ce1e8d868c fix: a possible issue on electron flaky test (#3094) 2023-07-07 11:02:58 +00:00
Alex Yang
3294043180 perf: reduce unused provider connection (#3090) 2023-07-07 08:13:32 +00:00
Alex Yang
152fbaabda ci: fix nx.yml (#3086) 2023-07-07 05:37:40 +00:00
JimmFly
5756bdf8d7 style: adjust settings style (#3083) 2023-07-07 05:36:27 +00:00
Alex Yang
80ee33fd3e chore: bump version (#3078) 2023-07-07 01:55:11 +00:00
Alex Yang
955d80e2c1 test: image preview e2e (#3080)
Co-authored-by: danielchim <kahungchim@gmail.com>
2023-07-06 23:24:03 +00:00
Alex Yang
67fe7f04da build: fix nx inputs (#3079) 2023-07-07 01:15:04 +08:00
Alex Yang
6395521f09 test: upgrade playwright (#3077) 2023-07-06 16:15:18 +00:00
Alex Yang
822078e640 fix: cleanup workspace when switch setting panel (#3072) 2023-07-06 15:27:09 +00:00
Alex Yang
fafd93f7dc refactor: block-hub in tool wrapper (#3073) 2023-07-06 15:18:58 +00:00
Peng Xiao
00ce086e79 fix: workspace storage settings issues (#3055) 2023-07-06 12:48:20 +00:00
Alex Yang
28653d6892 fix(web): setting panel refresh (#3070) 2023-07-06 11:24:26 +00:00
Alex Yang
e30c67482f fix(web): fetch hello-world from local (#3062) 2023-07-06 09:46:17 +00:00
Pratik Kumar
bda28e0404 fix(component): new page button in all page (#3053) 2023-07-06 09:40:37 +00:00
Alex Yang
ce63364299 fix(component): image preview fallback (#3058) 2023-07-06 09:22:23 +00:00
JimmFly
f468dff6aa chore: update communities link and icon (#3052) 2023-07-06 07:24:36 +00:00
Peng Xiao
fab03006e8 fix: menu item click area (#3051) 2023-07-06 06:53:50 +00:00
JimmFly
8a565b8633 fix: date-picker hidden in update collection (#3045) 2023-07-06 06:17:19 +00:00
Alex Yang
e79a6a5d47 v0.7.0-canary.36 2023-07-06 14:07:27 +08:00
Pratik Kumar
95c2e20cb5 fix(component): all page list UI padding (#3046)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-06 05:56:08 +00:00
JimmFly
2e0f410978 chore: temporary fix sync script error (#3044) 2023-07-06 12:30:01 +08:00
Alex Yang
fa1cd87348 chore: bump version (#3041) 2023-07-06 03:49:17 +00:00
Alex Yang
e95d28e136 fix: workspace name should change in the setting panel (#3039) 2023-07-06 02:19:06 +00:00
Qi
87ba71e77e fix: a series of setting issues (#3032) 2023-07-05 14:11:42 +00:00
Peng Xiao
dec0c0d3d1 fix: delete workspace in settings (#3030) 2023-07-05 10:31:11 +00:00
Peng Xiao
776172bc88 fix: updater issues (#3027) 2023-07-05 09:29:11 +00:00
Alex Yang
d582548ed8 v0.7.0-canary.35 2023-07-05 16:02:31 +08:00
Alex Yang
70ac31b907 build: remove legacy cloud config (#3024) 2023-07-05 06:57:56 +00:00
Alex Yang
cff9fd1ead chore: bump version (#3023) 2023-07-05 06:54:09 +00:00
Alex Yang
319febb00d docs: update README.md 2023-07-05 14:31:47 +08:00
3720
72fa2da2d3 fix: tags does not exist (#3020) 2023-07-05 04:06:44 +00:00
Alex Yang
3084c427f1 feat: update server login feature (#3004)
Co-authored-by: LongYinan <lynweklm@gmail.com>
2023-07-05 03:13:20 +00:00
3720
9cd1f013f8 fix: flaky tests (#3019) 2023-07-05 02:50:43 +00:00
Alex Yang
a3f58d4302 v0.7.0-canary.34 2023-07-05 02:23:53 +08:00
Alex Yang
d4cb89eafc chore: bump version (#3016) 2023-07-04 17:52:40 +00:00
Peng Xiao
33ba034336 fix: sqlite provider import sub doc db file (#2991)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-04 17:47:42 +00:00
Alex Yang
e158c09160 chore: update pre-commit (#3017) 2023-07-04 17:42:14 +00:00
JimmFly
c6ccd6d5de chore: update setting text (#3000) 2023-07-04 17:40:58 +00:00
Alex Yang
ec87864c34 refactor: simplify code (#3015) 2023-07-04 17:34:22 +00:00
Alex Yang
a06ba403d0 ci: check macOS arm64 bundle output (#3012) 2023-07-04 16:59:00 +00:00
Alex Yang
dfbec46ded feat(electron): move preload to infra (#3011) 2023-07-04 16:43:30 +00:00
Alex Yang
24be73ef63 chore: bump nx (#3014) 2023-07-04 16:26:43 +00:00
Alex Yang
3976c37d41 v0.7.0-canary.33 2023-07-04 21:52:04 +08:00
Fangdun Tsai
2bc15665b9 chore(electron): renaming clipboard api (#3005) 2023-07-04 12:51:59 +00:00
Alex Yang
e4539dfeb1 fix: bookmark block output missing (#3010) 2023-07-04 12:48:47 +00:00
Qi
1070e17310 feat: modify setting modal (#3008) 2023-07-04 12:37:46 +00:00
Alex Yang
b4f7eb36ef v0.7.0-canary.32 2023-07-04 16:12:09 +08:00
3720
000f802baa feat: add tags support (#2988)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-04 07:32:11 +00:00
Alex Yang
e871ffcba0 refactor: input component (#2999) 2023-07-04 06:52:46 +00:00
Alex Yang
8d2ffe3936 chore: bump version (#2998) 2023-07-04 06:47:35 +00:00
ShortCipher5
9e253420d2 docs: update README.md (#2997) 2023-07-04 14:13:25 +08:00
Alex Yang
edb7847e95 test: use static server (#2996) 2023-07-04 05:37:06 +00:00
Alex Yang
3d70148e0f chore: add circular check (#2995) 2023-07-04 04:54:08 +00:00
Alex Yang
7f89b197da build: enable next server (#2992) 2023-07-04 01:59:06 +00:00
danielchim
32692bd54a feat: page mode shortcut (#2985) 2023-07-03 16:23:53 +00:00
Alex Yang
7b2acec7c3 v0.7.0-canary.31 2023-07-03 23:14:38 +08:00
Alex Yang
f1adf23631 chore: bump version (#2989) 2023-07-03 14:51:49 +00:00
Alex Yang
a5d2fafad6 refactor: remove legacy cloud (#2987) 2023-07-03 14:29:37 +00:00
xiaodong zuo
3d0a907b49 fix: dark mode export PDF leaves margin and notification (#2978) 2023-07-03 12:11:07 +00:00
LongYinan
bacd00655d ci: reduce yarn cache (#2983) 2023-07-03 11:09:17 +00:00
Peng Xiao
08e003b0f6 fix: potential updater issue (#2973) 2023-07-03 11:04:45 +00:00
Alex Yang
0f1c5163a1 feat: remove old setting page by default (#2980) 2023-07-03 10:59:23 +00:00
JimmFly
18874d0d1e chore: add import to sidebar (#2981) 2023-07-03 10:51:28 +00:00
Peng Xiao
7f0a74c694 fix: some potential tests issue (#2982) 2023-07-03 10:46:47 +00:00
Peng Xiao
901fc87716 fix: potential race condition on app load when migration (#2977)
Co-authored-by: Alex Yang <himself65@outlook.com>
2023-07-03 09:40:02 +00:00
Alex Yang
ee2ab4086f fix(web): hydration issue (#2974) 2023-07-03 09:06:12 +00:00
JimmFly
af94674c18 style: adjust icon button hover color (#2976) 2023-07-03 08:46:16 +00:00
Alex Yang
262289a398 chore: add affine-cloud build config (#2971) 2023-07-03 07:25:14 +00:00
Alex Yang
467eab4ddf build: update build config (#2967) 2023-07-03 06:17:13 +00:00
Alex Yang
63517e4912 chore: update 'lint-staged' rules (#2969) 2023-07-03 06:00:50 +00:00
JimmFly
6f9487deb7 style: adjust copilot chat style (#2915) 2023-07-03 05:57:30 +00:00
JimmFly
8d0edd5255 i18n: update translation resources (#2968) 2023-07-03 05:15:46 +00:00
Qi
bdea153c82 feat: modify preloading data (#2947) 2023-07-03 02:49:55 +00:00
Alex Yang
d447883b7d v0.7.0-canary.30 2023-07-02 14:18:33 +08:00
Alex Yang
03ec51a96c chore: bump version 2023-07-02 14:17:22 +08:00
Alex Yang
0adf18f5e6 v0.7.0-canary.29 2023-07-02 14:06:14 +08:00
Alex Yang
5e7dc9ff21 chore: bump version (#2960) 2023-07-02 05:26:47 +00:00
Alex Yang
33097382c6 chore: replace 'eslint-plugin-import' (#2957) 2023-07-01 16:35:16 +00:00
Alex Yang
b9df2cdabb ci: update labeler.yml 2023-07-01 23:13:35 +08:00
Alex Yang
158338508a ci: use yarn run test 2023-07-01 23:08:14 +08:00
Alex Yang
640967d9ae v0.7.0-canary.28 2023-07-01 21:35:05 +08:00
Alex Yang
ec973395da fix: remove export script 2023-07-01 21:34:24 +08:00
Alex Yang
b35d99d935 v0.7.0-canary.27 2023-07-01 21:29:11 +08:00
Alex Yang
c0f6e751d2 build: update nx.json 2023-07-01 18:58:14 +08:00
Alex Yang
6af454ceed chore: improve ci build speed (#2953) 2023-07-01 10:47:26 +00:00
Alex Yang
ed829dd43b build: update nx.json 2023-07-01 16:54:21 +08:00
Alex Yang
a9adb4dda2 build: fix nx.json (#2951) 2023-07-01 16:34:30 +08:00
Alex Yang
54a7eeda37 chore: bump version (#2950) 2023-07-01 16:26:43 +08:00
Alex Yang
711e683c6f build: skip type check in next.js build (#2952) 2023-07-01 16:22:21 +08:00
Alex Yang
81c5e6d3d2 build: enhance nx build (#2948) 2023-07-01 01:17:31 +08:00
3720
7a5a5d503a chore: adjust icon and style (#2949) 2023-06-30 23:38:47 +08:00
Alex Yang
b597dbd80f docs: update react badge 2023-06-30 18:03:20 +08:00
436 changed files with 9463 additions and 13681 deletions

View File

@@ -8,3 +8,4 @@ _next
lib
.eslintrc.js
packages/i18n/src/i18n-generated.ts
e2e-dist-*

View File

@@ -21,6 +21,11 @@ const createPattern = packageName => [
message: 'Do not import package itself',
allowTypeImports: false,
},
{
group: ['@blocksuite/store'],
message: "Import from '@blocksuite/global/utils'",
importNames: ['assertExists', 'assertEquals'],
},
];
const allPackages = [
@@ -83,7 +88,7 @@ const config = {
'@typescript-eslint',
'simple-import-sort',
'sonarjs',
'import',
'i',
'unused-imports',
'unicorn',
],
@@ -134,6 +139,11 @@ const config = {
message: "Don't import from src",
allowTypeImports: false,
},
{
group: ['@blocksuite/store'],
message: "Import from '@blocksuite/global/utils'",
importNames: ['assertExists', 'assertEquals'],
},
],
},
],
@@ -204,6 +214,7 @@ const config = {
'scripts/**/*',
'**/benchmark/**/*',
'**/__debug__/**/*',
'**/e2e/**/*',
],
rules: {
'@typescript-eslint/no-non-null-assertion': 0,

View File

@@ -13,10 +13,18 @@ inputs:
description: 'Run the install step for Playwright.'
required: false
default: 'false'
electron-install:
description: 'Download the Electron binary'
required: false
default: 'true'
npm-token:
description: 'The NPM token to use for private packages.'
required: false
default: ''
hard-link-nm:
description: 'set nmMode to hardlinks-local in .yarnrc.yml'
required: false
default: 'true'
runs:
using: 'composite'
@@ -29,33 +37,10 @@ runs:
scope: '@toeverything'
cache: 'yarn'
- name: Expose yarn config as "$GITHUB_OUTPUT"
id: yarn-config
- name: Set nmMode
if: ${{ inputs.hard-link-nm == 'true' }}
shell: bash
run: |
echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-download-cache
with:
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
restore-keys: |
yarn-download-cache-
- name: Restore node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Restore yarn install state
id: yarn-install-state-cache
uses: actions/cache@v3
with:
path: .yarn/ci-cache/
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
run: yarn config set nmMode hardlinks-local
- name: yarn install
if: ${{ inputs.package-install == 'true' }}
@@ -64,9 +49,9 @@ runs:
run: yarn install ${{ inputs.extra-flags }}
env:
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
YARN_ENABLE_GLOBAL_CACHE: 'false'
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
- name: yarn install (try again)
if: ${{ steps.install.outcome == 'failure' }}
@@ -74,9 +59,9 @@ runs:
run: yarn install ${{ inputs.extra-flags }}
env:
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
YARN_ENABLE_GLOBAL_CACHE: 'false'
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz
HUSKY: '0'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
- name: Get installed Playwright version
id: playwright-version
@@ -113,3 +98,30 @@ runs:
shell: bash
if: inputs.playwright-install == 'true' && steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
- name: Get installed Electron version
id: electron-version
if: ${{ inputs.electron-install == 'true' }}
shell: bash
run: |
echo "version=$(yarn why --json electron | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://' | head -n 1)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
id: electron-cache
if: ${{ inputs.electron-install == 'true' }}
with:
path: 'node_modules/.cache/electron'
key: '${{ runner.os }}-electron-${{ steps.electron-version.outputs.version }}'
restore-keys: |
${{ runner.os }}-electron-
- name: Install Electron binary
shell: bash
if: inputs.electron-install == 'true'
run: node apps/electron/node_modules/electron/install.js
env:
ELECTRON_OVERRIDE_DIST_PATH: ./node_modules/.cache/electron
- name: Build Infra
shell: bash
run: yarn run build:infra

16
.github/labeler.yml vendored
View File

@@ -8,14 +8,20 @@ test:
- '**/tests/**/*'
- '**/__tests__/**/*'
plugin:copilot:
- 'plugins/copilot/**/*'
mod:dev:
- 'scripts/**/*'
- 'packages/cli/**/*'
- 'packages/debug/**/*'
mod:plugin:
- 'plugins/**/*'
plugin:bookmark-block:
- 'plugins/bookmark-block/**/*'
plugin:copilot:
- 'plugins/copilot/**/*'
mod:plugin-infra:
- 'packages/plugin-infra/**/*'
@@ -29,6 +35,10 @@ mod:hooks: 'packages/hooks/**/*'
mod:component: 'packages/component/**/*'
mod:storage: 'packages/storage/**/*'
mod:native: 'packages/native/**/*'
mod:store:
- 'packages/jotai/**/*'
- '**/atoms/**/*'

View File

@@ -43,14 +43,19 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- name: Run i18n codegen
run: yarn i18n-codegen gen
- name: Run Type Check
run: yarn typecheck
- name: Run ESLint
run: yarn lint --max-warnings=0 --cache
run: yarn lint:eslint --max-warnings=0
- name: Run Prettier
run: yarn prettier . --ignore-unknown --cache --check
# Set nmMode in `actions/setup-node` will modify the .yarnrc.yml
run: |
git checkout .yarnrc.yml
yarn lint:prettier
- name: Run circular
run: yarn circular
- name: Upload server dist
@@ -69,6 +74,8 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- run: yarn nx build @affine/docs
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
@@ -82,6 +89,8 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- run: yarn nx build @affine/storybook
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
@@ -104,26 +113,6 @@ jobs:
- name: Build Web
run: yarn nx build @affine/web
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: next-js
path: ./apps/web/.next
if-no-files-found: error
build-web-desktop:
name: Build @affine/web (Desktop)
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Export static resources
run: yarn nx export @affine/web
- name: Upload static resources artifact
uses: actions/upload-artifact@v3
with:
name: next-js-static
@@ -173,8 +162,11 @@ jobs:
uses: ./.github/actions/setup-rust
with:
target: 'x86_64-unknown-linux-gnu'
- name: Build Storage
run: yarn build:storage
- name: Run server tests
run: yarn nx test:coverage @affine/server
run: yarn test:coverage
working-directory: apps/server
env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
@@ -204,6 +196,7 @@ jobs:
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download storybook artifact
uses: actions/download-artifact@v3
with:
@@ -220,18 +213,9 @@ jobs:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
shard: [1, 2, 3, 4, 5]
environment: development
needs: [build-web, build-storybook]
services:
octobase:
image: ghcr.io/toeverything/cloud-self-hosted:nightly-latest
ports:
- 3000:3000
env:
SIGN_KEY: 'test123'
RUST_LOG: 'debug'
JWST_DEV: '1'
needs: build-web
steps:
- uses: actions/checkout@v3
@@ -239,21 +223,12 @@ jobs:
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: next-js
path: ./apps/web/.next
- name: Download storybook artifact
uses: actions/download-artifact@v3
with:
name: storybook
path: ./apps/storybook/storybook-static
- name: Wait for Octobase Ready
run: |
node ./scripts/wait-3000-healthz.mjs
name: next-js-static
path: ./apps/web/out
- name: Run playwright tests
run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
@@ -292,6 +267,7 @@ jobs:
uses: ./.github/actions/setup-node
with:
playwright-install: true
electron-install: false
- name: Download next static
uses: actions/download-artifact@v3
@@ -356,13 +332,14 @@ jobs:
target: x86_64-pc-windows-msvc,
test: true,
}
needs: [build-web-desktop]
needs: [build-web]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: true
hard-link-nm: false
- name: Build AFFiNE native
uses: ./.github/actions/build-rust
with:
@@ -375,33 +352,41 @@ jobs:
env:
NATIVE_TEST: 'true'
- name: Build Infra
run: yarn run build:infra
- name: Download static resource artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
path: apps/electron/resources/web-static
- name: Build Plugins
run: yarn run build:plugins
- name: Build Desktop Layers
run: yarn nx build @affine/electron
- name: Download static resource artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
path: ./apps/electron/resources/web-static
run: yarn workspace @affine/electron build
- name: Run desktop tests
if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn nx test @affine/electron
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine/electron test
env:
COVERAGE: true
- name: Run desktop tests
if: ${{ matrix.spec.test && matrix.spec.os != 'ubuntu-latest' }}
run: yarn nx test @affine/electron
run: yarn workspace @affine/electron test
env:
COVERAGE: true
- name: Make bundle
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
run: yarn workspace @affine/electron make --platform=darwin --arch=arm64
- name: Bundle output check
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
run: |
./scripts/unzip-macos-arm64.sh
yarn ts-node-esm ./scripts/macos-arm64-output-check.mts
working-directory: apps/electron
- name: Collect code coverage report
if: ${{ matrix.spec.test }}
run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
@@ -428,20 +413,12 @@ jobs:
name: Unit Test
runs-on: ubuntu-latest
environment: development
services:
octobase:
image: ghcr.io/toeverything/cloud-self-hosted:nightly-latest
ports:
- 3000:3000
env:
SIGN_KEY: 'test123'
RUST_LOG: 'debug'
JWST_DEV: '1'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
- name: Unit Test
run: yarn nx test:coverage @affine/monorepo

View File

@@ -13,5 +13,6 @@ jobs:
steps:
- uses: styfle/cancel-workflow-action@0.11.0
with:
workflow_id: 44038251
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
workflow_id: 44038251, 61883931
access_token: ${{ github.token }}

View File

@@ -123,14 +123,11 @@ jobs:
name: before-make-web-static
path: apps/electron/resources/web-static
- name: Build Infra
run: yarn run build:infra
- name: Build Plugins
run: yarn run build:plugins
- name: Build Desktop Layers
run: yarn nx build @affine/electron
run: yarn workspace @affine/electron build
- name: Signing By Apple Developer ID
if: ${{ matrix.spec.platform == 'darwin' }}

54
.github/workflows/nx.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: NX
on:
push:
branches:
- master
- v[0-9]+.[0-9]+.x-staging
- v[0-9]+.[0-9]+.x
paths-ignore:
- README.md
- .github/**
- '!.github/workflows/nx.yml'
- '!.github/actions/build-rust/action.yml'
- '!.github/actions/setup-node/action.yml'
pull_request:
merge_group:
branches:
- master
- v[0-9]+.[0-9]+.x-staging
- v[0-9]+.[0-9]+.x
paths-ignore:
- README.md
- .github/**
- '!.github/workflows/nx.yml'
- '!.github/actions/build-rust/action.yml'
- '!.github/actions/setup-node/action.yml'
jobs:
main:
name: Nx Cloud - Main Job
uses: nrwl/ci/.github/workflows/nx-cloud-main.yml@v0.13.0
with:
runs-on: macos-latest
main-branch-name: master
number-of-agents: 5
init-commands: |
yarn exec nx-cloud start-ci-run --stop-agents-after="build" --agent-count=5
environment-variables: |
BUILD_TYPE=canary
# parallel-commands: |
# yarn exec nx-cloud record -- yarn exec nx format:check
parallel-commands-on-agents: |
yarn exec nx affected --target=build --parallel=5
timeout: 60
agents:
name: Nx Cloud - Agents
uses: nrwl/ci/.github/workflows/nx-cloud-agents.yml@v0.13.0
with:
runs-on: macos-latest
number-of-agents: 5
environment-variables: |
BUILD_TYPE=canary
timeout: 60

View File

@@ -123,14 +123,11 @@ jobs:
name: before-make-web-static
path: apps/electron/resources/web-static
- name: Build Infra
run: yarn run build:infra
- name: Build Plugins
run: yarn run build:plugins
- name: Build Desktop Layers
run: yarn nx build @affine/electron
run: yarn workspace @affine/electron build
- name: Signing By Apple Developer ID
if: ${{ matrix.spec.platform == 'darwin' }}

22
.github/workflows/workers.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Deploy Cloudflare Worker
on:
push:
branches:
- master
paths:
- packages/workers/**
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
environment: production
steps:
- uses: actions/checkout@v2
- name: Publish
uses: cloudflare/wrangler-action@2.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
workingDirectory: 'packages/workers'

View File

@@ -2,10 +2,22 @@
. "$(dirname -- "$0")/_/husky.sh"
# check lockfile is up to date
yarn install --mode=update-lockfile
yarn install --mode=skip-build --inline-builds --immutable
# build infra code
yarn -T run build:infra
# generate prisma client type
yarn workspace @affine/server prisma generate
# generate i18n
yarn i18n-codegen gen
# lint staged files
yarn exec lint-staged
# type check
yarn typecheck
# circular dependency check
yarn circular

1
.npmrc
View File

@@ -1,2 +1,3 @@
shell-emulator=true
electron_mirror="https://cdn.npmmirror.com/binaries/electron/"
engine-strict=true

View File

@@ -10,3 +10,5 @@ dist
.yarn
tests/affine-legacy/0.7.0-canary.18/static
.github/helm
_next
storybook-static

View File

@@ -2,13 +2,13 @@
<h1 style="border-bottom: none">
<b><a href="https://affine.pro">AFFiNE.PRO</a></b><br />
The Next-Gen Collaborative Knowledge Base
Write, Draw and Plan All at Once
<br>
</h1>
<p>
AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together.<br />
Privacy first, open-source, customizable and ready to use - a free replacement for Notion & Miro. <br />
One hyper-fused platform for wildly creative minds. <br />
A privacy-focussed, local-first, open-source, and ready-to-use alternative for Notion & Miro.
</p>
</div>
@@ -60,7 +60,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
<br />
<div align="center">
<em>See docs, canvas and tables are hyper merged with AFFiNE - just like the word affine (əˈɪn | a-fine).</em>
<em>Docs, canvas and tables are hyper-merged with AFFiNE - just like the word affine (əˈɪn | a-fine).</em>
</div>
<br />
@@ -123,6 +123,8 @@ If you have questions, you are welcome to contact us. One of the best places to
## Plugins
> Plugins are a way to extend the functionality of AFFiNE.
>
> (Currently, plugins are under heavy development, and the SDK is not yet available.)
| Name | |
| ------------------------------------------------ | ----------------------------------------- |
@@ -135,7 +137,7 @@ We would also like to give thanks to open-source projects that make AFFiNE possi
- [BlockSuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE.
- [OctoBase](https://github.com/toeverything/OctoBase) - 🐙 OctoBase is the open-source database behind AFFiNE, local-first, yet collaborative. A light-weight, scalable, data engine written in Rust.
- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) - Fundamental support of CRDTs for our implementation on state management and data sync.
- [Yjs](https://github.com/yjs/yjs) - Fundamental support of CRDTs for our implementation on state management and data sync.
- [Electron](https://github.com/electron/electron) - Build cross-platform desktop apps with JavaScript, HTML, and CSS.
- [React](https://github.com/facebook/react) - View layer support and web GUI framework.
- [Rust](https://github.com/rust-lang/rust) - High performance language that extends the ability and availability of our real-time backend, OctoBase.
@@ -156,7 +158,8 @@ We would like to express our gratitude to all the individuals who have already c
## Self-Host
Get started with Docker and deploy your own feature-rich, restriction-free deployment of AFFiNE - check the [latest packages].
Get started with Docker and deploy your own feature-rich, restriction-free deployment of AFFiNE.
We are working hard to get this updated to the latest version, you can keep an eye on the [latest packages].
## Hiring
@@ -199,5 +202,5 @@ See [LICENSE] for details.
[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/dev/react?color=rgb%2897%2C%20218%2C%20251%29
[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

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />

View File

@@ -1,6 +1,6 @@
{
"name": "@affine/docs",
"version": "0.7.0-canary.26",
"version": "0.7.0-canary.42",
"type": "module",
"private": true,
"scripts": {
@@ -10,26 +10,26 @@
},
"dependencies": {
"@affine/component": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/blocks": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/editor": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/global": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/lit": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/store": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/block-std": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/blocks": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/editor": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/global": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/lit": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/store": "0.0.0-20230711103520-ce18dd84-nightly",
"express": "^4.18.2",
"jotai": "^2.2.1",
"react": "18.3.0-canary-8ec962d82-20230623",
"react-dom": "18.3.0-canary-8ec962d82-20230623",
"react-server-dom-webpack": "18.3.0-canary-8ec962d82-20230623",
"jotai": "^2.2.2",
"react": "18.3.0-canary-1fdacbefd-20230630",
"react-dom": "18.3.0-canary-1fdacbefd-20230630",
"react-server-dom-webpack": "18.3.0-canary-1fdacbefd-20230630",
"waku": "0.12.1"
},
"devDependencies": {
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vanilla-extract/css": "^1.11.1",
"@vanilla-extract/css": "^1.12.0",
"@vanilla-extract/vite-plugin": "^3.8.2",
"autoprefixer": "^10.4.14",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.5"
"typescript": "^5.1.6"
}
}

View File

@@ -13,3 +13,5 @@ resources/web-static
!.yarn/sdks
!.yarn/versions
dev.json
zip-out

View File

@@ -149,3 +149,35 @@ test('windows only check', async ({ page }) => {
await expect(windowOnlyUI).not.toBeVisible();
}
});
test('delete workspace', async ({ page }) => {
await page.getByTestId('current-workspace').click();
await page.getByTestId('add-or-new-workspace').click();
await page.getByTestId('new-workspace').click();
await page.getByTestId('create-workspace-default-location-button').click();
await page.getByTestId('create-workspace-input').type('Delete Me');
await page.getByTestId('create-workspace-create-button').click();
await page.getByTestId('create-workspace-continue-button').click();
await page.getByTestId('slider-bar-workspace-setting-button').click();
await page.getByTestId('current-workspace-label').click();
expect(await page.getByTestId('workspace-name-input').inputValue()).toBe(
'Delete Me'
);
const contentElement = await page.getByTestId('setting-modal-content');
const boundingBox = await contentElement.boundingBox();
if (!boundingBox) {
throw new Error('boundingBox is null');
}
await page.mouse.move(
boundingBox.x + boundingBox.width / 2,
boundingBox.y + boundingBox.height / 2
);
await page.mouse.wheel(0, 500);
await page.getByTestId('delete-workspace-button').click();
await page.getByTestId('delete-workspace-input').type('Delete Me');
await page.getByTestId('delete-workspace-confirm-button').click();
await page.waitForTimeout(1000);
expect(await page.getByTestId('workspace-name').textContent()).toBe(
'Demo Workspace'
);
});

View File

@@ -16,6 +16,8 @@ function generateUUID() {
return crypto.randomUUID();
}
type RoutePath = 'setting';
export const test = base.extend<{
page: Page;
electronApp: ElectronApplication;
@@ -28,6 +30,9 @@ export const test = base.extend<{
// get current workspace
current: () => Promise<any>; // todo: type
};
router: {
goto: (path: RoutePath) => Promise<void>;
};
}>({
page: async ({ electronApp }, use) => {
const page = await electronApp.firstWindow();
@@ -41,10 +46,6 @@ export const test = base.extend<{
});
});
}
const logFilePath = await page.evaluate(async () => {
// @ts-expect-error
return window.apis?.debug.logFilePath();
});
// wat for blocksuite to be loaded
await page.waitForSelector('v-line');
if (enableCoverage) {
@@ -71,10 +72,6 @@ export const test = base.extend<{
);
}
await page.close();
if (logFilePath) {
const logs = await fs.readFile(logFilePath, 'utf-8');
console.log(logs);
}
},
electronApp: async ({}, use) => {
// a random id to avoid conflicts between tests

View File

@@ -5,7 +5,7 @@ import fs from 'fs-extra';
import { test } from './fixture';
test.skip('check workspace has a DB file', async ({ appInfo, workspace }) => {
test('check workspace has a DB file', async ({ appInfo, workspace }) => {
const w = await workspace.current();
const dbPath = path.join(
appInfo.sessionData,
@@ -19,9 +19,11 @@ test.skip('check workspace has a DB file', async ({ appInfo, workspace }) => {
test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
const w = await workspace.current();
const settingButton = page.getByTestId('slider-bar-workspace-setting-button');
// goto settings
await settingButton.click();
await page.getByTestId('slider-bar-workspace-setting-button').click();
await expect(page.getByTestId('setting-modal')).toBeVisible();
// goto workspace setting
await page.getByTestId('workspace-list-item').click();
const tmpPath = path.join(appInfo.sessionData, w.id + '-tmp-dir');
@@ -42,21 +44,26 @@ test.skip('move workspace db file', async ({ page, appInfo, workspace }) => {
expect(files.some(f => f.endsWith('.affine'))).toBe(true);
});
test.skip('export then add', async ({ page, appInfo, workspace }) => {
test('export then add', async ({ page, appInfo, workspace }) => {
const w = await workspace.current();
const settingButton = page.getByTestId('slider-bar-workspace-setting-button');
// goto settings
await settingButton.click();
await page.getByTestId('slider-bar-workspace-setting-button').click();
await expect(page.getByTestId('setting-modal')).toBeVisible();
const originalId = w.id;
const newWorkspaceName = 'new-test-name';
// goto workspace setting
await page.getByTestId('workspace-list-item').click();
await page.waitForTimeout(500);
// change workspace name
await page.getByTestId('workspace-name-input').fill(newWorkspaceName);
await page.getByTestId('save-workspace-name').click();
await page.waitForSelector('text="Update workspace name success"');
await page.click('[data-tab-key="export"]');
await page.waitForTimeout(500);
const tmpPath = path.join(appInfo.sessionData, w.id + '-tmp.db');
@@ -73,10 +80,11 @@ test.skip('export then add', async ({ page, appInfo, workspace }) => {
expect(await fs.exists(tmpPath)).toBe(true);
await page.getByTestId('modal-close-button').click();
// add workspace
// we are reusing the same db file so that we don't need to maintain one
// in the codebase
await page.getByTestId('current-workspace').click();
await page.getByTestId('add-or-new-workspace').click();

View File

@@ -26,6 +26,8 @@ const arch =
? process.argv[process.argv.indexOf('--arch') + 1]
: process.arch;
const windowsIconUrl = `https://cdn.affine.pro/app-icons/icon_${buildType}.ico`;
/**
* @type {import('@electron-forge/shared-types').ForgeConfig}
*/
@@ -95,6 +97,7 @@ module.exports = {
config: {
name: 'AFFiNE',
setupIcon: icoPath,
iconUrl: windowsIconUrl,
loadingGif: './resources/icons/affine_installing.gif',
},
},

View File

@@ -1,7 +1,7 @@
{
"name": "@affine/electron",
"private": true,
"version": "0.7.0-canary.26",
"version": "0.7.0-canary.42",
"author": "affine",
"repository": {
"url": "https://github.com/toeverything/AFFiNE",
@@ -12,11 +12,11 @@
"scripts": {
"dev": "yarn cross-env DEV_SERVER_URL=http://localhost:8080 node scripts/dev.mjs",
"dev:prod": "yarn node scripts/dev.mjs",
"build": "zx scripts/build-layers.mjs",
"build": "NODE_ENV=production zx scripts/build-layers.mjs",
"generate-assets": "zx scripts/generate-assets.mjs",
"package": "electron-forge package",
"make": "electron-forge make",
"test": "DEBUG=pw:browser playwright test"
"test": "DEBUG=pw:browser yarn -T run playwright test -c ./playwright.config.ts"
},
"config": {
"forge": "./forge.config.js"
@@ -24,11 +24,12 @@
"main": "./dist/main.js",
"devDependencies": {
"@affine-test/kit": "workspace:*",
"@affine/env": "workspace:*",
"@affine/native": "workspace:*",
"@blocksuite/blocks": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/editor": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/lit": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/store": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/blocks": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/editor": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/lit": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/store": "0.0.0-20230711103520-ce18dd84-nightly",
"@electron-forge/cli": "^6.2.1",
"@electron-forge/core": "^6.2.1",
"@electron-forge/core-utils": "^6.2.1",
@@ -46,10 +47,9 @@
"electron-log": "^5.0.0-beta.24",
"electron-squirrel-startup": "1.0.0",
"electron-window-state": "^5.0.3",
"esbuild": "^0.18.10",
"esbuild": "^0.18.11",
"fs-extra": "^11.1.1",
"jotai": "^2.2.1",
"playwright": "=1.33.0",
"jotai": "^2.2.2",
"ts-node": "^10.9.1",
"undici": "^5.22.1",
"uuid": "^9.0.0",
@@ -59,7 +59,7 @@
"dependencies": {
"@toeverything/plugin-infra": "workspace:*",
"async-call-rpc": "^6.3.1",
"electron-updater": "^5.3.0",
"electron-updater": "^6.0.0",
"link-preview-js": "^3.0.4",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
@@ -81,7 +81,6 @@
"hoistingLimits": "workspaces"
},
"peerDependencies": {
"playwright": "*",
"ts-node": "*"
}
}

View File

@@ -11,7 +11,7 @@ import type { PlaywrightTestConfig } from '@playwright/test';
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
testDir: './e2e',
testIgnore: '**/lib/**',
fullyParallel: true,
timeout: process.env.CI ? 50_000 : 30_000,

View File

@@ -0,0 +1,24 @@
{
"name": "@affine/electron",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/electron",
"sourceRoot": "apps/electron/src",
"targets": {
"build": {
"executor": "nx:run-script",
"dependsOn": [
{
"projects": ["@affine/bookmark-block"],
"target": "build",
"params": "ignore"
},
"^build"
],
"options": {
"script": "build"
},
"outputs": ["{projectRoot}/dist"]
}
}
}

View File

@@ -38,7 +38,12 @@ export const config = () => {
bundle: true,
target: `node${NODE_MAJOR_VERSION}`,
platform: 'node',
external: ['electron', 'electron-updater', '@toeverything/plugin-infra'],
external: [
'electron',
'electron-updater',
'@toeverything/plugin-infra',
'yjs',
],
define: define,
format: 'cjs',
loader: {

View File

@@ -43,7 +43,6 @@ cd(repoRootDir);
if (!process.env.SKIP_WEB_BUILD) {
process.env.ENABLE_LEGACY_PROVIDER = 'false';
await $`yarn nx build @affine/web`;
await $`yarn nx export @affine/web`;
// step 1.5: amend sourceMappingURL to allow debugging in devtools
await glob('**/*.{js,css}', { cwd: affineWebOutDir }).then(files => {

View File

@@ -0,0 +1,42 @@
import { fileURLToPath } from 'node:url';
import { readdir } from 'node:fs/promises';
const outputRoot = fileURLToPath(
new URL(
'../zip-out/AFFiNE-canary.app/Contents/Resources/app',
import.meta.url
)
);
const outputList = [
[
'dist',
[
'main.js',
'helper.js',
'preload.js',
'affine.darwin-arm64.node',
'plugins',
'workers',
],
],
['dist/plugins', ['bookmark-block']],
['dist/plugins/bookmark-block', ['index.mjs']],
['dist/workers', ['plugin.worker.js']],
[
'node_modules/@toeverything/plugin-infra/dist',
['manager.js', 'manager.cjs'],
],
] as [entry: string, expected: string[]][];
await Promise.all(
outputList.map(async ([entry, output]) => {
const files = await readdir(`${outputRoot}/${entry}`);
output.forEach(file => {
if (!files.includes(file)) {
throw new Error(`File ${entry}/${file} not found`);
}
});
})
);
console.log('Output check passed');

View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Set the directory
dir="./out/canary/make/zip/darwin/arm64"
# Get the first file
file=$(ls -1 $dir | head -n 1)
# Check if file exists and is a zip file
if [ -f "$dir/$file" ] && [ ${file: -4} == ".zip" ]
then
# Unzip the file
unzip "$dir/$file" -d "zip-out"
else
echo "No zip file found"
fi

View File

@@ -1,10 +1,11 @@
import path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import fs from 'fs-extra';
import { v4 } from 'uuid';
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import { removeWithRetry } from '../../../../tests/utils';
const tmpDir = path.join(__dirname, 'tmp');
const appDataPath = path.join(tmpDir, 'app-data');
@@ -44,11 +45,7 @@ beforeEach(() => {
afterEach(async () => {
existProcess();
// wait for the db to be closed on Windows
if (process.platform === 'win32') {
await setTimeout(200);
}
await fs.remove(tmpDir);
await removeWithRetry(tmpDir);
vi.useRealTimers();
});

View File

@@ -0,0 +1,69 @@
import path from 'node:path';
import { SqliteConnection } from '@affine/native';
import { afterEach, describe, expect, it, vi } from 'vitest';
import * as Y from 'yjs';
import { removeWithRetry } from '../../../../tests/utils';
import { copyToTemp, migrateToSubdocAndReplaceDatabase } from '../migration';
const tmpDir = path.join(__dirname, 'tmp');
const testDBFilePath = path.resolve(__dirname, 'old-db.affine');
const appDataPath = path.join(tmpDir, 'app-data');
vi.mock('../../main-rpc', () => ({
mainRPC: {
getPath: async () => appDataPath,
},
}));
afterEach(async () => {
await removeWithRetry(tmpDir);
});
describe('migrateToSubdocAndReplaceDatabase', () => {
it('should migrate and replace the database', async () => {
const copiedDbFilePath = await copyToTemp(testDBFilePath);
await migrateToSubdocAndReplaceDatabase(copiedDbFilePath);
const db = new SqliteConnection(copiedDbFilePath);
await db.connect();
// check if db has two rows, one for root doc and one for subdoc
const rows = await db.getAllUpdates();
expect(rows.length).toBe(2);
const rootUpdate = rows.find(row => row.docId === undefined)!.data;
const subdocUpdate = rows.find(row => row.docId !== undefined)!.data;
expect(rootUpdate).toBeDefined();
expect(subdocUpdate).toBeDefined();
// apply updates
const rootDoc = new Y.Doc();
Y.applyUpdate(rootDoc, rootUpdate);
// check if root doc has one subdoc
expect(rootDoc.subdocs.size).toBe(1);
// populates subdoc
Y.applyUpdate(rootDoc.subdocs.values().next().value, subdocUpdate);
// check if root doc's meta is correct
const meta = rootDoc.getMap('meta').toJSON();
expect(meta.workspaceVersion).toBe(1);
expect(meta.name).toBe('hiw');
expect(meta.pages.length).toBe(1);
const pageMeta = meta.pages[0];
expect(pageMeta.title).toBe('Welcome to AFFiNEd');
// get the subdoc through id
const subDoc = rootDoc
.getMap('spaces')
.get(`space:${pageMeta.id}`) as Y.Doc;
expect(subDoc).toEqual(rootDoc.subdocs.values().next().value);
await db.close();
});
});

Binary file not shown.

View File

@@ -5,6 +5,7 @@ import { v4 } from 'uuid';
import { afterEach, expect, test, vi } from 'vitest';
import * as Y from 'yjs';
import { removeWithRetry } from '../../../../tests/utils';
import { dbSubjects } from '../subjects';
const tmpDir = path.join(__dirname, 'tmp');
@@ -17,7 +18,7 @@ vi.doMock('../../main-rpc', () => ({
}));
afterEach(async () => {
await fs.remove(tmpDir);
await removeWithRetry(tmpDir);
});
let testYDoc: Y.Doc;

View File

@@ -119,6 +119,8 @@ export abstract class BaseSQLiteAdapter {
`[SQLiteAdapter][${this.role}] addUpdateToSQLite`,
'length:',
updates.length,
'docids',
updates.map(u => u.docId),
performance.now() - start,
'ms'
);

View File

@@ -0,0 +1,55 @@
import { resolve } from 'node:path';
import { migrateToSubdoc } from '@affine/env/blocksuite';
import { SqliteConnection } from '@affine/native';
import fs from 'fs-extra';
import { nanoid } from 'nanoid';
import * as Y from 'yjs';
import { mainRPC } from '../main-rpc';
export const migrateToSubdocAndReplaceDatabase = async (path: string) => {
const db = new SqliteConnection(path);
await db.connect();
const rows = await db.getAllUpdates();
const originalDoc = new Y.Doc();
// 1. apply all updates to the root doc
rows.forEach(row => {
Y.applyUpdate(originalDoc, row.data);
});
// 2. migrate using migrateToSubdoc
const migratedDoc = migrateToSubdoc(originalDoc);
// 3. replace db rows with the migrated doc
await replaceRows(db, migratedDoc, true);
// 4. close db
await db.close();
};
export const copyToTemp = async (path: string) => {
const tmpDirPath = resolve(await mainRPC.getPath('sessionData'), 'tmp');
const tmpFilePath = resolve(tmpDirPath, nanoid());
await fs.ensureDir(tmpDirPath);
await fs.copyFile(path, tmpFilePath);
return tmpFilePath;
};
async function replaceRows(
db: SqliteConnection,
doc: Y.Doc,
isRoot: boolean
): Promise<void> {
const migratedUpdates = Y.encodeStateAsUpdate(doc);
const docId = isRoot ? undefined : doc.guid;
const rows = [{ data: migratedUpdates, docId: docId }];
await db.replaceUpdates(docId, rows);
await Promise.all(
[...doc.subdocs].map(async subdoc => {
await replaceRows(db, subdoc, false);
})
);
}

View File

@@ -115,19 +115,43 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
}
setupListener(docId?: string) {
logger.debug(
'SecondaryWorkspaceSQLiteDB:setupListener',
this.workspaceId,
docId
);
const doc = this.getDoc(docId);
if (!doc) {
const upstreamDoc = this.upstream.getDoc(docId);
if (!doc || !upstreamDoc) {
logger.warn(
'[SecondaryWorkspaceSQLiteDB] setupListener: doc not found',
docId
);
return;
}
const onUpstreamUpdate = (update: Uint8Array, origin: YOrigin) => {
if (origin === 'renderer') {
logger.debug(
'SecondaryWorkspaceSQLiteDB:onUpstreamUpdate',
origin,
this.workspaceId,
docId,
update.length
);
if (origin === 'renderer' || origin === 'self') {
// update to upstream yDoc should be replicated to self yDoc
this.applyUpdate(update, 'upstream', docId);
}
};
const onSelfUpdate = async (update: Uint8Array, origin: YOrigin) => {
logger.debug(
'SecondaryWorkspaceSQLiteDB:onSelfUpdate',
origin,
this.workspaceId,
docId,
update.length
);
// for self update from upstream, we need to push it to external DB
if (origin === 'upstream') {
await this.addUpdateToUpdateQueue({
@@ -147,15 +171,19 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
});
};
doc.subdocs.forEach(subdoc => {
this.setupListener(subdoc.guid);
});
// listen to upstream update
this.upstream.yDoc.on('update', onUpstreamUpdate);
this.yDoc.on('update', onSelfUpdate);
this.yDoc.on('subdocs', onSubdocs);
doc.on('update', onSelfUpdate);
doc.on('subdocs', onSubdocs);
this.unsubscribers.add(() => {
this.upstream.yDoc.off('update', onUpstreamUpdate);
this.yDoc.off('update', onSelfUpdate);
this.yDoc.off('subdocs', onSubdocs);
doc.off('update', onSelfUpdate);
doc.off('subdocs', onSubdocs);
});
}
@@ -188,7 +216,10 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
if (doc) {
Y.applyUpdate(this.yDoc, data, origin);
} else {
logger.warn('applyUpdate: doc not found', docId);
logger.warn(
'[SecondaryWorkspaceSQLiteDB] applyUpdate: doc not found',
docId
);
}
};

View File

@@ -18,7 +18,10 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
update$ = new Subject<void>();
constructor(public override path: string, public workspaceId: string) {
constructor(
public override path: string,
public workspaceId: string
) {
super(path);
}
@@ -49,9 +52,21 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
};
setupListener(docId?: string) {
logger.debug(
'WorkspaceSQLiteDB:setupListener',
this.workspaceId,
docId,
this.getWorkspaceName()
);
const doc = this.getDoc(docId);
if (doc) {
const onUpdate = async (update: Uint8Array, origin: YOrigin) => {
logger.debug(
'WorkspaceSQLiteDB:onUpdate',
this.workspaceId,
docId,
update.length
);
const insertRows = [{ data: update, docId }];
if (origin === 'renderer') {
await this.addUpdateToSQLite(insertRows);
@@ -65,7 +80,11 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
logger.debug('external update', this.workspaceId);
}
};
doc.subdocs.forEach(subdoc => {
this.setupListener(subdoc.guid);
});
const onSubdocs = ({ added }: { added: Set<Y.Doc> }) => {
logger.info('onSubdocs', this.workspaceId, docId, added);
added.forEach(subdoc => {
this.setupListener(subdoc.guid);
});
@@ -132,7 +151,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
if (doc) {
Y.applyUpdate(doc, data, origin);
} else {
logger.warn('applyUpdate: doc not found', docId);
logger.warn('[WorkspaceSQLiteDB] applyUpdate: doc not found', docId);
}
};

View File

@@ -1,9 +1,11 @@
import path from 'node:path';
import { ValidationResult } from '@affine/native';
import fs from 'fs-extra';
import { nanoid } from 'nanoid';
import { ensureSQLiteDB } from '../db/ensure-db';
import { copyToTemp, migrateToSubdocAndReplaceDatabase } from '../db/migration';
import type { WorkspaceSQLiteDB } from '../db/workspace-db-adapter';
import { logger } from '../logger';
import { mainRPC } from '../main-rpc';
@@ -55,6 +57,7 @@ const ErrorMessages = [
'DB_FILE_ALREADY_LOADED',
'DB_FILE_PATH_INVALID',
'DB_FILE_INVALID',
'DB_FILE_MIGRATION_FAILED',
'FILE_ALREADY_EXISTS',
'UNKNOWN_ERROR',
] as const;
@@ -191,27 +194,42 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
],
message: 'Load Workspace from a AFFiNE file',
}));
const filePath = ret.filePaths?.[0];
if (ret.canceled || !filePath) {
let originalPath = ret.filePaths?.[0];
if (ret.canceled || !originalPath) {
logger.info('loadDBFile canceled');
return { canceled: true };
}
// the imported file should not be in app data dir
if (filePath.startsWith(await getWorkspacesBasePath())) {
if (originalPath.startsWith(await getWorkspacesBasePath())) {
logger.warn('loadDBFile: db file in app data dir');
return { error: 'DB_FILE_PATH_INVALID' };
}
if (await dbFileAlreadyLoaded(filePath)) {
if (await dbFileAlreadyLoaded(originalPath)) {
logger.warn('loadDBFile: db file already loaded');
return { error: 'DB_FILE_ALREADY_LOADED' };
}
const { SqliteConnection } = await import('@affine/native');
if (!(await SqliteConnection.validate(filePath))) {
// TODO: report invalid db file error?
const validationResult = await SqliteConnection.validate(originalPath);
if (validationResult === ValidationResult.MissingDocIdColumn) {
try {
const tmpDBPath = await copyToTemp(originalPath);
await migrateToSubdocAndReplaceDatabase(tmpDBPath);
originalPath = tmpDBPath;
} catch (error) {
logger.warn(`loadDBFile, migration failed: ${originalPath}`, error);
return { error: 'DB_FILE_MIGRATION_FAILED' };
}
}
if (
validationResult !== ValidationResult.MissingDocIdColumn &&
validationResult !== ValidationResult.Valid
) {
return { error: 'DB_FILE_INVALID' }; // invalid db file
}
@@ -220,14 +238,12 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
const internalFilePath = await getWorkspaceDBPath(workspaceId);
await fs.ensureDir(await getWorkspacesBasePath());
await fs.copy(filePath, internalFilePath);
logger.info(`loadDBFile, copy: ${filePath} -> ${internalFilePath}`);
await fs.copy(originalPath, internalFilePath);
logger.info(`loadDBFile, copy: ${originalPath} -> ${internalFilePath}`);
await storeWorkspaceMeta(workspaceId, {
id: workspaceId,
mainDBPath: internalFilePath,
secondaryDBPath: filePath,
});
return { workspaceId };

View File

@@ -1,3 +1,4 @@
import type { RendererToHelper } from '@toeverything/infra/preload/electron';
import { AsyncCall } from 'async-call-rpc';
import { events, handlers } from './exposed';
@@ -30,7 +31,7 @@ function setupRendererConnection(rendererPort: Electron.MessagePortMain) {
});
}
);
const rpc = AsyncCall<PeersAPIs.RendererToHelper>(
const rpc = AsyncCall<RendererToHelper>(
Object.fromEntries(flattenedHandlers),
{
channel: {

View File

@@ -1,12 +1,16 @@
import type {
HelperToMain,
MainToHelper,
} from '@toeverything/infra/preload/electron';
import { AsyncCall } from 'async-call-rpc';
import { getExposedMeta } from './exposed';
const helperToMainServer: PeersAPIs.HelperToMain = {
const helperToMainServer: HelperToMain = {
getMeta: () => getExposedMeta(),
};
export const mainRPC = AsyncCall<PeersAPIs.MainToHelper>(helperToMainServer, {
export const mainRPC = AsyncCall<MainToHelper>(helperToMainServer, {
strict: {
unknownMessage: false,
},

View File

@@ -4,6 +4,8 @@ import fs from 'fs-extra';
import { v4 } from 'uuid';
import { afterEach, describe, expect, test, vi } from 'vitest';
import { removeWithRetry } from '../../../../tests/utils';
const tmpDir = path.join(__dirname, 'tmp');
const appDataPath = path.join(tmpDir, 'app-data');
@@ -20,7 +22,7 @@ vi.doMock('../../main-rpc', () => ({
}));
afterEach(async () => {
await fs.remove(tmpDir);
await removeWithRetry(tmpDir);
});
describe('list workspaces', () => {

View File

@@ -1,10 +1,10 @@
import assert from 'node:assert';
import path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import fs from 'fs-extra';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { removeWithRetry } from '../../../tests/utils';
import type { MainIPCHandlerMap } from '../exposed';
const registeredHandlers = new Map<
@@ -21,7 +21,7 @@ type WithoutFirstParameter<T> = T extends (_: any, ...args: infer P) => infer R
// however this is too hard to be typed correctly
async function dispatch<
T extends keyof MainIPCHandlerMap,
F extends keyof MainIPCHandlerMap[T]
F extends keyof MainIPCHandlerMap[T],
>(
namespace: T,
functionName: F,
@@ -121,11 +121,7 @@ beforeEach(async () => {
afterEach(async () => {
// reset registered handlers
registeredHandlers.get('before-quit')?.forEach(fn => fn());
// wait for the db to be closed on Windows
if (process.platform === 'win32') {
await setTimeout(200);
}
await fs.remove(SESSION_DATA_PATH);
await removeWithRetry(SESSION_DATA_PATH);
});
describe('UI handlers', () => {

View File

@@ -1,7 +1,7 @@
import { app, Menu } from 'electron';
import { revealLogFile } from '../logger';
import { checkForUpdatesAndNotify } from '../updater';
import { checkForUpdates } from '../updater';
import { isMacOS } from '../utils';
import { applicationMenuSubjects } from './subject';
@@ -125,7 +125,7 @@ export function createApplicationMenu() {
{
label: 'Check for Updates',
click: async () => {
await checkForUpdatesAndNotify(true);
await checkForUpdates(true);
},
},
],

View File

@@ -1,9 +1,9 @@
import { clipboard, nativeImage } from 'electron';
import { clipboard, type IpcMainInvokeEvent, nativeImage } from 'electron';
import type { NamespaceHandlers } from '../type';
export const clipboardHandlers = {
copyAsPng: async (_, dataURL: string) => {
copyAsImageFromString: async (_: IpcMainInvokeEvent, dataURL: string) => {
clipboard.writeImage(nativeImage.createFromDataURL(dataURL));
},
} satisfies NamespaceHandlers;

View File

@@ -39,6 +39,13 @@ export async function savePDFFileAs(
await BrowserWindow.getFocusedWindow()
?.webContents.printToPDF({
margins: {
marginType: 'custom',
top: 0,
bottom: 0,
left: 0,
right: 0,
},
pageSize: 'A4',
printBackground: true,
landscape: false,

View File

@@ -1,5 +1,9 @@
import path from 'node:path';
import type {
HelperToMain,
MainToHelper,
} from '@toeverything/infra/preload/electron';
import { type _AsyncVersionOf, AsyncCall } from 'async-call-rpc';
import {
app,
@@ -36,7 +40,7 @@ class HelperProcessManager {
#process: UtilityProcess;
// a rpc server for the main process -> helper process
rpc?: _AsyncVersionOf<PeersAPIs.HelperToMain>;
rpc?: _AsyncVersionOf<HelperToMain>;
static instance = new HelperProcessManager();
@@ -86,13 +90,13 @@ class HelperProcessManager {
]);
const appMethods = pickAndBind(app, ['getPath']);
const mainToHelperServer: PeersAPIs.MainToHelper = {
const mainToHelperServer: MainToHelper = {
...dialogMethods,
...shellMethods,
...appMethods,
};
this.rpc = AsyncCall<PeersAPIs.HelperToMain>(mainToHelperServer, {
this.rpc = AsyncCall<HelperToMain>(mainToHelperServer, {
strict: {
// the channel is shared for other purposes as well so that we do not want to
// restrict to only JSONRPC messages

View File

@@ -1,5 +1,5 @@
import { app } from 'electron';
import type { AppUpdater } from 'electron-updater';
import { autoUpdater } from 'electron-updater';
import { z } from 'zod';
import { logger } from '../logger';
@@ -20,56 +20,55 @@ export const buildType = ReleaseTypeSchema.parse(envBuildType);
const mode = process.env.NODE_ENV;
const isDev = mode === 'development';
let _autoUpdater: AppUpdater | null = null;
export const quitAndInstall = async () => {
_autoUpdater?.quitAndInstall();
autoUpdater.quitAndInstall();
};
let lastCheckTime = 0;
export const checkForUpdatesAndNotify = async (force = true) => {
if (!_autoUpdater) {
return void 0;
}
export const checkForUpdates = async (force = true) => {
// check every 30 minutes (1800 seconds) at most
if (force || lastCheckTime + 1000 * 1800 < Date.now()) {
lastCheckTime = Date.now();
return await _autoUpdater.checkForUpdatesAndNotify();
return await autoUpdater.checkForUpdates();
}
return void 0;
};
export const registerUpdater = async () => {
// so we wrap it in a function
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { autoUpdater } = require('electron-updater');
_autoUpdater = autoUpdater;
// skip auto update in dev mode
if (!_autoUpdater || isDev) {
if (isDev) {
return;
}
// TODO: support auto update on windows and linux
const allowAutoUpdate = isMacOS();
_autoUpdater.autoDownload = false;
_autoUpdater.allowPrerelease = buildType !== 'stable';
_autoUpdater.autoInstallOnAppQuit = false;
_autoUpdater.autoRunAppAfterInstall = true;
_autoUpdater.setFeedURL({
autoUpdater.logger = logger;
autoUpdater.autoDownload = false;
autoUpdater.allowPrerelease = buildType !== 'stable';
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.autoRunAppAfterInstall = true;
const feedUrl: Parameters<typeof autoUpdater.setFeedURL>[0] = {
channel: buildType,
provider: 'github',
repo: buildType !== 'internal' ? 'AFFiNE' : 'AFFiNE-Releases',
owner: 'toeverything',
releaseType: buildType === 'stable' ? 'release' : 'prerelease',
});
};
logger.debug('auto-updater feed config', feedUrl);
autoUpdater.setFeedURL(feedUrl);
// register events for checkForUpdatesAndNotify
_autoUpdater.on('update-available', info => {
autoUpdater.on('checking-for-update', () => {
logger.info('Checking for update');
});
autoUpdater.on('update-available', info => {
logger.info('Update available', info);
if (allowAutoUpdate) {
_autoUpdater?.downloadUpdate().catch(e => {
autoUpdater?.downloadUpdate().catch(e => {
logger.error('Failed to download update', e);
});
logger.info('Update available, downloading...', info);
@@ -79,11 +78,14 @@ export const registerUpdater = async () => {
allowAutoUpdate,
});
});
_autoUpdater.on('download-progress', e => {
autoUpdater.on('update-not-available', info => {
logger.info('Update not available', info);
});
autoUpdater.on('download-progress', e => {
logger.info(`Download progress: ${e.percent}`);
updaterSubjects.downloadProgress.next(e.percent);
});
_autoUpdater.on('update-downloaded', e => {
autoUpdater.on('update-downloaded', e => {
updaterSubjects.updateReady.next({
version: e.version,
allowAutoUpdate,
@@ -92,12 +94,12 @@ export const registerUpdater = async () => {
// updaterSubjects.clientDownloadProgress.next(100);
logger.info('Update downloaded, ready to install');
});
_autoUpdater.on('error', e => {
autoUpdater.on('error', e => {
logger.error('Error while updating client', e);
});
_autoUpdater.forceDevUpdateConfig = isDev;
autoUpdater.forceDevUpdateConfig = isDev;
app.on('activate', async () => {
await checkForUpdatesAndNotify(false);
await checkForUpdates(false);
});
};

View File

@@ -1,7 +1,7 @@
import { app } from 'electron';
import type { NamespaceHandlers } from '../type';
import { checkForUpdatesAndNotify, quitAndInstall } from './electron-updater';
import { checkForUpdates, quitAndInstall } from './electron-updater';
export const updaterHandlers = {
currentVersion: async () => {
@@ -11,7 +11,14 @@ export const updaterHandlers = {
return quitAndInstall();
},
checkForUpdatesAndNotify: async () => {
return checkForUpdatesAndNotify(true);
const res = await checkForUpdates(true);
if (res) {
const { updateInfo } = res;
return {
updateInfo,
};
}
return null;
},
} satisfies NamespaceHandlers;

View File

@@ -1,8 +1,10 @@
import { contextBridge, ipcRenderer } from 'electron';
(async () => {
const { appInfo, getAffineAPIs } = await import('./affine-apis');
const { apis, events } = getAffineAPIs();
const { appInfo, getElectronAPIs } = await import(
'@toeverything/infra/preload/electron'
);
const { apis, events } = getElectronAPIs();
contextBridge.exposeInMainWorld('appInfo', appInfo);
contextBridge.exposeInMainWorld('apis', apis);

View File

@@ -1,35 +0,0 @@
declare namespace PeersAPIs {
import type { app, dialog, shell } from 'electron';
interface ExposedMeta {
handlers: [string, string[]][];
events: [string, string[]][];
}
// render <-> helper
interface RendererToHelper {
postEvent: (channel: string, ...args: any[]) => void;
}
interface HelperToRenderer {
[key: string]: (...args: any[]) => Promise<any>;
}
// helper <-> main
interface HelperToMain {
getMeta: () => ExposedMeta;
}
type MainToHelper = Pick<
typeof dialog & typeof shell & typeof app,
| 'showOpenDialog'
| 'showSaveDialog'
| 'openExternal'
| 'showItemInFolder'
| 'getPath'
>;
// render <-> main
// these are handled via IPC
// TODO: fix type
}

View File

@@ -0,0 +1,26 @@
import { setTimeout } from 'node:timers/promises';
import fs from 'fs-extra';
export async function removeWithRetry(
filePath: string,
maxRetries = 5,
delay = 500
) {
for (let i = 0; i < maxRetries; i++) {
try {
await fs.remove(filePath);
console.log(`File ${filePath} successfully deleted.`);
return true;
} catch (err: any) {
if (err.code === 'EBUSY' || err.code === 'EPERM') {
console.log(`File ${filePath} is busy or locked, retrying...`);
await setTimeout(delay);
} else {
console.error(`Failed to delete file ${filePath}:`, err);
}
}
}
// Add a return statement here to ensure that a value is always returned
return false;
}

View File

@@ -14,7 +14,7 @@
"noImplicitOverride": true
},
"include": ["./src"],
"exclude": ["node_modules", "out", "dist"],
"exclude": ["node_modules", "out", "dist", "**/__tests__/**/*"],
"references": [
{
"path": "../../packages/plugin-infra"
@@ -25,13 +25,16 @@
{
"path": "../../packages/infra"
},
{
"path": "../../packages/env"
},
// Tests
{
"path": "./tsconfig.node.json"
},
{
"path": "./tests/tsconfig.json"
"path": "./e2e/tsconfig.json"
},
{ "path": "../../tests/kit" }
],

View File

@@ -7,7 +7,8 @@
"resolveJsonModule": true,
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"noEmit": false
"noEmit": false,
"outDir": "./lib/scripts"
},
"include": ["./scripts", "esbuild.main.config.ts", "esbuild.plugin.config.ts"]
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true
},
"include": ["**/__tests__/**/*", "./tests"],
"references": [
{
"path": "./tsconfig.json"
}
]
}

View File

@@ -1,2 +1,2 @@
SECRET_KEY="secret"
DATABASE_URL="postgresql://affine@localhost:5432/affine"
NEXTAUTH_URL="http://localhost:8080"

View File

@@ -1,7 +1,7 @@
{
"name": "@affine/server",
"private": true,
"version": "0.7.0-canary.26",
"version": "0.7.0-canary.42",
"description": "Affine Node.js server",
"type": "module",
"bin": {
@@ -15,18 +15,19 @@
"postinstall": "prisma generate"
},
"dependencies": {
"@apollo/server": "^4.7.4",
"@apollo/server": "^4.7.5",
"@auth/prisma-adapter": "^1.0.0",
"@aws-sdk/client-s3": "^3.359.0",
"@nestjs/apollo": "^12.0.3",
"@nestjs/common": "^10.0.3",
"@nestjs/core": "^10.0.3",
"@nestjs/graphql": "^12.0.3",
"@nestjs/platform-express": "^10.0.3",
"@aws-sdk/client-s3": "^3.363.0",
"@nestjs/apollo": "^12.0.7",
"@nestjs/common": "^10.0.4",
"@nestjs/core": "^10.0.4",
"@nestjs/graphql": "^12.0.7",
"@nestjs/platform-express": "^10.0.4",
"@node-rs/argon2": "^1.5.0",
"@node-rs/crc32": "^1.7.0",
"@node-rs/jsonwebtoken": "^0.2.0",
"@prisma/client": "^4.16.1",
"@prisma/client": "^4.16.2",
"cookie-parser": "^1.4.6",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"graphql": "^16.7.1",
@@ -34,24 +35,28 @@
"graphql-upload": "^16.0.2",
"lodash-es": "^4.17.21",
"next-auth": "^4.22.1",
"nodemailer": "^6.9.3",
"parse-duration": "^1.1.0",
"prisma": "^4.16.1",
"prisma": "^4.16.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"semver": "^7.5.4"
},
"devDependencies": {
"@affine/storage": "workspace:*",
"@napi-rs/image": "^1.6.1",
"@nestjs/testing": "^10.0.3",
"@nestjs/testing": "^10.0.4",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.17",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.16.18",
"@types/node": "^18.16.19",
"@types/nodemailer": "^6.4.8",
"@types/supertest": "^2.0.12",
"c8": "^8.0.0",
"nodemon": "^2.0.22",
"supertest": "^6.3.3",
"ts-node": "^10.9.1",
"typescript": "^5.1.5"
"typescript": "^5.1.6"
},
"nodemonConfig": {
"exec": "node",

View File

@@ -233,5 +233,11 @@ export interface AFFiNEConfig {
}
>
>;
email: {
server: string;
port: number;
sender: string;
password: string;
};
};
}

View File

@@ -65,6 +65,10 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
OAUTH_GOOGLE_CLIENT_SECRET: 'auth.oauthProviders.google.clientSecret',
OAUTH_GITHUB_CLIENT_ID: 'auth.oauthProviders.github.clientId',
OAUTH_GITHUB_CLIENT_SECRET: 'auth.oauthProviders.github.clientSecret',
OAUTH_EMAIL_SENDER: 'auth.email.sender',
OAUTH_EMAIL_SERVER: 'auth.email.server',
OAUTH_EMAIL_PORT: 'auth.email.port',
OAUTH_EMAIL_PASSWORD: 'auth.email.password',
} satisfies AFFiNEConfig['ENV_MAP'],
env: process.env.NODE_ENV ?? 'development',
get prod() {
@@ -102,7 +106,6 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
},
introspection: true,
playground: true,
debug: true,
},
auth: {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -114,8 +117,16 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
publicKey: jwtKeyPair.publicKey,
enableSignup: true,
enableOauth: false,
nextAuthSecret: '',
get nextAuthSecret() {
return this.privateKey;
},
oauthProviders: {},
email: {
server: 'smtp.gmail.com',
port: 465,
sender: '',
password: '',
},
},
objectStorage: {
r2: {
@@ -129,7 +140,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
path: join(homedir(), '.affine-storage'),
},
},
} as const;
} satisfies AFFiNEConfig;
applyEnvToConfig(defaultConfig);

View File

@@ -1,6 +1,7 @@
/// <reference types="./global.d.ts" />
import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';
import cookieParser from 'cookie-parser';
import { static as staticMiddleware } from 'express';
// @ts-expect-error graphql-upload is not typed
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
@@ -27,6 +28,8 @@ app.use(
})
);
app.use(cookieParser());
const config = app.get(Config);
const host = config.host ?? 'localhost';

View File

@@ -41,7 +41,10 @@ export const CurrentUser = createParamDecorator(
@Injectable()
class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private prisma: PrismaService) {}
constructor(
private auth: AuthService,
private prisma: PrismaService
) {}
async canActivate(context: ExecutionContext) {
const { req } = getRequestResponseFromContext(context);

View File

@@ -2,11 +2,10 @@ import { randomUUID } from 'node:crypto';
import { PrismaAdapter } from '@auth/prisma-adapter';
import {
All,
BadRequestException,
Controller,
Get,
Next,
Post,
Query,
Req,
Res,
@@ -15,6 +14,7 @@ import { Algorithm, sign, verify as jwtVerify } from '@node-rs/jsonwebtoken';
import type { NextFunction, Request, Response } from 'express';
import type { AuthAction, AuthOptions } from 'next-auth';
import { AuthHandler } from 'next-auth/core';
import Email from 'next-auth/providers/email';
import Github from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
@@ -28,16 +28,44 @@ const BASE_URL = '/api/auth/';
export class NextAuthController {
private readonly nextAuthOptions: AuthOptions;
constructor(readonly config: Config, readonly prisma: PrismaService) {
constructor(
readonly config: Config,
readonly prisma: PrismaService
) {
const prismaAdapter = PrismaAdapter(prisma);
// createUser exists in the adapter
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const createUser = prismaAdapter.createUser!.bind(prismaAdapter);
prismaAdapter.createUser = async data => {
if (data.email && !data.name) {
data.name = data.email.split('@')[0];
}
return createUser(data);
};
this.nextAuthOptions = {
providers: [],
providers: [
// @ts-expect-error esm interop issue
Email.default({
server: {
host: config.auth.email.server,
port: config.auth.email.port,
auth: {
user: config.auth.email.sender,
pass: config.auth.email.password,
},
},
from: `AFFiNE <no-reply@toeverything.info>`,
}),
],
// @ts-expect-error Third part library type mismatch
adapter: PrismaAdapter(prisma),
adapter: prismaAdapter,
debug: !config.prod,
};
if (config.auth.oauthProviders.github) {
this.nextAuthOptions.providers.push(
Github({
// @ts-expect-error esm interop issue
Github.default({
clientId: config.auth.oauthProviders.github.clientId,
clientSecret: config.auth.oauthProviders.github.clientSecret,
})
@@ -46,12 +74,14 @@ export class NextAuthController {
if (config.auth.oauthProviders.google) {
this.nextAuthOptions.providers.push(
Google({
// @ts-expect-error esm interop issue
Google.default({
clientId: config.auth.oauthProviders.google.clientId,
clientSecret: config.auth.oauthProviders.google.clientSecret,
})
);
}
this.nextAuthOptions.jwt = {
encode: async ({ token, maxAge }) => {
if (!token?.email) {
@@ -108,8 +138,7 @@ export class NextAuthController {
this.nextAuthOptions.secret ??= config.auth.nextAuthSecret;
}
@Get()
@Post()
@All('*')
async auth(
@Req() req: Request,
@Res() res: Response,

View File

@@ -19,7 +19,10 @@ export const getUtcTimestamp = () => Math.floor(new Date().getTime() / 1000);
@Injectable()
export class AuthService {
constructor(private config: Config, private prisma: PrismaService) {}
constructor(
private config: Config,
private prisma: PrismaService
) {}
sign(user: UserClaim) {
const now = getUtcTimestamp();

View File

@@ -101,7 +101,6 @@ type Query {
type Mutation {
register(name: String!, email: String!, password: String!): UserType!
signIn(email: String!, password: String!): UserType!
signUp(email: String!, password: String!, name: String!): UserType!
"""
Create a new workspace

View File

@@ -28,7 +28,7 @@ export type LeafPaths<
T,
Path extends string = '',
MaxDepth extends string = '...',
Depth extends string = ''
Depth extends string = '',
> = Depth extends MaxDepth
? never
: T extends Record<string | number, any>

View File

@@ -9,36 +9,36 @@
"dependencies": {
"@affine/component": "workspace:*",
"@affine/i18n": "workspace:*",
"@storybook/addon-actions": "^7.0.23",
"@storybook/addon-essentials": "^7.0.23",
"@storybook/addon-interactions": "^7.0.23",
"@storybook/addon-links": "^7.0.23",
"@storybook/addon-storysource": "^7.0.23",
"@storybook/blocks": "^7.0.23",
"@storybook/builder-vite": "^7.0.23",
"@storybook/addon-actions": "^7.0.24",
"@storybook/addon-essentials": "^7.0.24",
"@storybook/addon-interactions": "^7.0.24",
"@storybook/addon-links": "^7.0.24",
"@storybook/addon-storysource": "^7.0.24",
"@storybook/blocks": "^7.0.24",
"@storybook/builder-vite": "^7.0.24",
"@storybook/jest": "^0.1.0",
"@storybook/react": "^7.0.23",
"@storybook/react-vite": "^7.0.23",
"@storybook/react": "^7.0.24",
"@storybook/react-vite": "^7.0.24",
"@storybook/test-runner": "^0.11.0",
"@storybook/testing-library": "^0.2.0",
"@vitejs/plugin-react": "^4.0.1",
"concurrently": "^8.2.0",
"jest-mock": "^29.5.0",
"serve": "^14.2.0",
"storybook": "^7.0.21",
"storybook": "^7.0.24",
"storybook-dark-mode": "^3.0.0",
"wait-on": "^7.0.1"
},
"devDependencies": {
"@blocksuite/block-std": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/blocks": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/editor": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/global": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/icons": "^2.1.23",
"@blocksuite/lit": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/store": "0.0.0-20230630081054-55a25248-nightly",
"react": "18.3.0-canary-8ec962d82-20230623",
"react-dom": "18.3.0-canary-8ec962d82-20230623"
"@blocksuite/block-std": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/blocks": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/editor": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/global": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/icons": "^2.1.24",
"@blocksuite/lit": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/store": "0.0.0-20230711103520-ce18dd84-nightly",
"react": "18.3.0-canary-1fdacbefd-20230630",
"react-dom": "18.3.0-canary-1fdacbefd-20230630"
},
"peerDependencies": {
"@blocksuite/blocks": "*",
@@ -48,5 +48,5 @@
"@blocksuite/lit": "*",
"@blocksuite/store": "*"
},
"version": "0.7.0-canary.26"
"version": "0.7.0-canary.42"
}

View File

@@ -1,24 +0,0 @@
import { AffineLoading } from '@affine/component/affine-loading';
import type { StoryFn } from '@storybook/react';
export default {
title: 'AFFiNE/Loading',
component: AffineLoading,
};
export const Default: StoryFn = ({ width, loop, autoplay, autoReverse }) => (
<div
style={{
width: width,
height: width,
}}
>
<AffineLoading loop={loop} autoplay={autoplay} autoReverse={autoReverse} />
</div>
);
Default.args = {
width: 100,
loop: true,
autoplay: true,
autoReverse: true,
};

View File

@@ -1,6 +1,8 @@
/* deepscan-disable USELESS_ARROW_FUNC_BIND */
import { BlockHubWrapper } from '@affine/component/block-hub';
import type { EditorProps } from '@affine/component/block-suite-editor';
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
import { rootBlockHubAtom } from '@affine/workspace/atom';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
@@ -54,13 +56,13 @@ const Template: StoryFn<EditorProps> = (props: Partial<EditorProps>) => {
}}
>
<BlockSuiteEditor onInit={initPage} page={page} mode="page" {...props} />
<div
<BlockHubWrapper
style={{
position: 'absolute',
right: 12,
bottom: 12,
}}
id="toolWrapper"
blockHubAtom={rootBlockHubAtom}
/>
</div>
);

View File

@@ -2,27 +2,27 @@ import { toast } from '@affine/component';
import { BlockCard } from '@affine/component/card/block-card';
import { WorkspaceCard } from '@affine/component/card/workspace-card';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import { Workspace } from '@blocksuite/store';
export default {
title: 'AFFiNE/Card',
component: WorkspaceCard,
};
const blockSuiteWorkspace = new Workspace({
id: 'blocksuite-local',
});
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
'blocksuite-local',
WorkspaceFlavour.LOCAL
);
blockSuiteWorkspace.meta.setName('Hello World');
export const AffineWorkspaceCard = () => {
return (
<WorkspaceCard
workspace={{
meta={{
id: 'blocksuite-local',
flavour: WorkspaceFlavour.LOCAL,
id: 'local',
blockSuiteWorkspace,
}}
onClick={() => {}}
onSettingClick={() => {}}

View File

@@ -1,35 +0,0 @@
import { Button } from '@affine/component';
import type { ContactModalProps } from '@affine/component/contact-modal';
import { ContactModal } from '@affine/component/contact-modal';
import type { StoryFn } from '@storybook/react';
import { useState } from 'react';
export default {
title: 'AFFiNE/ContactModal',
component: ContactModal,
};
export const Basic: StoryFn<ContactModalProps> = args => {
const [open, setOpen] = useState(false);
return (
<>
<Button
onClick={() => {
setOpen(true);
}}
>
Open
</Button>
<ContactModal
{...args}
open={open}
onClose={() => {
setOpen(false);
}}
/>
</>
);
};
Basic.args = {
logoSrc: '/imgs/affine-text-logo.png',
};

View File

@@ -1,7 +1,9 @@
import { BlockHubWrapper } from '@affine/component/block-hub';
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
import { ImagePreviewModal } from '@affine/component/image-preview-modal';
import { initEmptyPage } from '@affine/env/blocksuite';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { rootBlockHubAtom } from '@affine/workspace/atom';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import type { Meta } from '@storybook/react';
@@ -54,13 +56,13 @@ export const Default = () => {
>
<BlockSuiteEditor mode="page" page={page} onInit={initEmptyPage} />
</div>
<div
<BlockHubWrapper
style={{
position: 'absolute',
right: 12,
bottom: 12,
}}
id="toolWrapper"
blockHubAtom={rootBlockHubAtom}
/>
</>
);

View File

@@ -1,6 +1,5 @@
import { Empty } from '@affine/component';
import { toast } from '@affine/component';
import { AffineLoading } from '@affine/component/affine-loading';
import type { OperationCellProps } from '@affine/component/page-list';
import { PageListTrashView } from '@affine/component/page-list';
import { PageList } from '@affine/component/page-list';
@@ -13,7 +12,7 @@ import { userEvent } from '@storybook/testing-library';
export default {
title: 'AFFiNE/PageList',
component: AffineLoading,
component: PageList,
};
export const AffineOperationCell: StoryFn<OperationCellProps> = ({
@@ -71,6 +70,7 @@ AffineAllPageList.args = {
icon: <PageIcon />,
isPublicPage: true,
title: 'Today Page',
tags: [],
preview: 'this is page preview',
createDate: new Date(),
updatedDate: new Date(),
@@ -87,6 +87,7 @@ AffineAllPageList.args = {
isPublicPage: true,
title:
'1 Example Public Page with long title that will be truncated because it is too too long',
tags: [],
preview:
'this is page preview and it is very long and will be truncated because it is too long and it is very long and will be truncated because it is too long',
createDate: new Date('2021-01-01'),
@@ -103,6 +104,7 @@ AffineAllPageList.args = {
isPublicPage: false,
icon: <PageIcon />,
title: '2 Favorited Page 2021',
tags: [],
createDate: new Date('2021-01-02'),
updatedDate: new Date('2021-01-01'),
bookmarkPage: () => toast('Bookmark page'),
@@ -117,6 +119,7 @@ AffineAllPageList.args = {
isPublicPage: false,
icon: <PageIcon />,
title: 'page created in 2023-04-01',
tags: [],
createDate: new Date('2023-04-01'),
updatedDate: new Date('2023-04-01'),
bookmarkPage: () => toast('Bookmark page'),

View File

@@ -5,14 +5,10 @@ import {
} from '@affine/component/share-menu';
import { ShareMenu } from '@affine/component/share-menu';
import type {
AffineLegacyCloudWorkspace,
AffineCloudWorkspace,
LocalWorkspace,
} from '@affine/env/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace';
import {
PermissionType,
WorkspaceType,
} from '@affine/env/workspace/legacy-cloud';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import type { Page } from '@blocksuite/store';
import { expect } from '@storybook/jest';
@@ -59,13 +55,10 @@ const localWorkspace: LocalWorkspace = {
blockSuiteWorkspace,
};
const affineWorkspace: AffineLegacyCloudWorkspace = {
const affineWorkspace: AffineCloudWorkspace = {
id: 'test-workspace',
flavour: WorkspaceFlavour.AFFINE,
flavour: WorkspaceFlavour.AFFINE_CLOUD,
blockSuiteWorkspace,
public: false,
type: WorkspaceType.Normal,
permission: PermissionType.Owner,
};
async function unimplemented() {

View File

@@ -1,6 +1,5 @@
import type { WorkspaceAvatarProps } from '@affine/component/workspace-avatar';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Workspace } from '@blocksuite/store';
import type { Meta, StoryFn } from '@storybook/react';
@@ -25,16 +24,7 @@ const basicBlockSuiteWorkspace = new Workspace({
basicBlockSuiteWorkspace.meta.setName('Hello World');
export const Basic: StoryFn<WorkspaceAvatarProps> = props => {
return (
<WorkspaceAvatar
{...props}
workspace={{
flavour: WorkspaceFlavour.LOCAL,
id: 'local',
blockSuiteWorkspace: basicBlockSuiteWorkspace,
}}
/>
);
return <WorkspaceAvatar {...props} workspace={basicBlockSuiteWorkspace} />;
};
Basic.args = {
@@ -60,16 +50,7 @@ fetch(new URL('@affine-test/fixtures/smile.png', import.meta.url))
});
export const BlobExample: StoryFn<WorkspaceAvatarProps> = props => {
return (
<WorkspaceAvatar
{...props}
workspace={{
flavour: WorkspaceFlavour.LOCAL,
id: 'local',
blockSuiteWorkspace: avatarBlockSuiteWorkspace,
}}
/>
);
return <WorkspaceAvatar {...props} workspace={avatarBlockSuiteWorkspace} />;
};
BlobExample.args = {

View File

@@ -13,6 +13,9 @@ import { blockSuiteFeatureFlags, buildFlags } from './preset.config.mjs';
import { getCommitHash, getGitVersion } from './scripts/git-info.mjs';
const require = createRequire(import.meta.url);
const packageJson = require('./package.json');
const appVersion = packageJson.version;
const editorVersion = packageJson.dependencies['@blocksuite/editor'];
const { createVanillaExtractPlugin } = require('@vanilla-extract/next-plugin');
const withVanillaExtract = createVanillaExtractPlugin();
@@ -45,32 +48,16 @@ if (process.env.COVERAGE === 'true') {
}
const profileTarget = {
ac: '100.85.73.88:12001',
dev: '100.84.105.99:11001',
test: '100.84.105.99:11001',
stage: '',
prod: 'https://app.affine.pro',
local: '127.0.0.1:3000',
};
const getRedirectConfig = profile => {
const target = profileTarget[profile || 'dev'] || profileTarget['dev'];
return [
[
{ source: '/api/:path*', destination: `http://${target}/api/:path*` },
{
source: '/collaboration/:path*',
destination: `http://${target}/collaboration/:path*`,
},
],
target,
profile || 'dev',
];
};
/** @type {import('next').NextConfig} */
const nextConfig = {
output: process.env.NODE_ENV === 'development' ? 'standalone' : 'export',
typescript: {
// We use `yarn typecheck` on top level to check types
ignoreBuildErrors: true,
},
sentry: {
hideSourceMaps: true,
},
@@ -118,12 +105,11 @@ const nextConfig = {
publicRuntimeConfig: {
PROJECT_NAME: process.env.npm_package_name ?? 'AFFiNE',
BUILD_DATE: new Date().toISOString(),
appVersion,
editorVersion,
gitVersion: getGitVersion(),
hash: getCommitHash(),
serverAPI:
profileTarget[process.env.API_SERVER_PROFILE || 'dev'] ??
profileTarget.dev,
editorVersion: require('./package.json').dependencies['@blocksuite/editor'],
serverAPI: profileTarget.local,
editorFlags: blockSuiteFeatureFlags,
...buildFlags,
},
@@ -162,13 +148,6 @@ const nextConfig = {
return config;
},
rewrites: async () => {
const [profile, target, desc] = getRedirectConfig(
process.env.API_SERVER_PROFILE
);
console.info(`API request proxy to [${desc} Server]: ` + target);
return profile;
},
basePath: process.env.NEXT_BASE_PATH,
assetPrefix: process.env.NEXT_ASSET_PREFIX,
pageExtensions: [
@@ -176,6 +155,15 @@ const nextConfig = {
],
};
if (process.env.NODE_ENV === 'development') {
nextConfig.rewrites = async () => [
{
source: '/api/auth/:path*/:path2*',
destination: 'http://localhost:3010/api/auth/:path*/:path2*',
},
];
}
const baseDir = process.env.LOCAL_BLOCK_SUITE ?? '/';
const withDebugLocal = debugLocal(
{

View File

@@ -1,12 +1,12 @@
{
"name": "@affine/web",
"private": true,
"version": "0.7.0-canary.26",
"version": "0.7.0-canary.42",
"scripts": {
"dev": "next dev",
"build": "next build",
"export": "next export",
"start": "next start"
"build": "next build && next export",
"start": "NODE_ENV=development next start",
"static-server": "ts-node-esm server.mts"
},
"dependencies": {
"@affine-test/fixtures": "workspace:*",
@@ -19,13 +19,13 @@
"@affine/jotai": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/block-std": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/blocks": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/editor": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/global": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/icons": "^2.1.23",
"@blocksuite/lit": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/store": "0.0.0-20230630081054-55a25248-nightly",
"@blocksuite/block-std": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/blocks": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/editor": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/global": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/icons": "^2.1.24",
"@blocksuite/lit": "0.0.0-20230711103520-ce18dd84-nightly",
"@blocksuite/store": "0.0.0-20230711103520-ce18dd84-nightly",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/cache": "^11.11.0",
@@ -33,8 +33,8 @@
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.13.6",
"@react-hookz/web": "^23.0.1",
"@sentry/nextjs": "^7.55.2",
"@react-hookz/web": "^23.1.0",
"@sentry/nextjs": "^7.57.0",
"@toeverything/hooks": "workspace:*",
"@toeverything/infra": "workspace:*",
"@toeverything/plugin-infra": "workspace:*",
@@ -42,15 +42,15 @@
"cmdk": "^0.2.0",
"css-spring": "^4.1.0",
"graphql": "^16.7.1",
"jotai": "^2.2.1",
"jotai": "^2.2.2",
"jotai-devtools": "^0.6.0",
"lit": "^2.7.5",
"lottie-web": "^5.12.2",
"next-themes": "^0.2.1",
"react": "18.3.0-canary-8ec962d82-20230623",
"react-dom": "18.3.0-canary-8ec962d82-20230623",
"react": "18.3.0-canary-1fdacbefd-20230630",
"react-dom": "18.3.0-canary-1fdacbefd-20230630",
"react-is": "^18.2.0",
"react-resizable-panels": "^0.0.51",
"react-resizable-panels": "^0.0.53",
"rxjs": "^7.8.1",
"swr": "^2.1.5",
"y-protocols": "^1.0.5",
@@ -67,20 +67,22 @@
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@types/webpack-env": "^1.18.1",
"@vanilla-extract/css": "^1.11.1",
"@vanilla-extract/css": "^1.12.0",
"@vanilla-extract/next-plugin": "=2.1.2",
"dotenv": "^16.3.1",
"eslint": "^8.43.0",
"eslint": "^8.44.0",
"eslint-config-next": "^13.4.7",
"eslint-plugin-unicorn": "^47.0.0",
"express": "^4.18.2",
"next": "=13.4.2",
"next-debug-local": "^0.1.5",
"next-router-mock": "^0.9.7",
"raw-loader": "^4.0.2",
"redux": "^4.2.1",
"swc-plugin-coverage-instrument": "^0.0.18",
"typescript": "^5.1.5",
"webpack": "^5.88.0"
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"webpack": "^5.88.1"
},
"stableVersion": "0.0.0"
}

View File

@@ -19,35 +19,37 @@ export const blockSuiteFeatureFlags = {
*/
const buildPreset = {
stable: {
enableAllPageSaving: false,
enablePlugin: false,
enableTestProperties: false,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
enableLegacyCloud: false,
changelogUrl: 'https://affine.pro/blog/whats-new-affine-0630',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: false,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: false,
enableCloud: false,
},
beta: {},
internal: {},
// canary will be aggressive and enable all features
canary: {
enableAllPageSaving: true,
enablePlugin: true,
enableTestProperties: true,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
enableLegacyCloud: false,
changelogUrl: 'https://affine.pro/blog/whats-new-affine-0630',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: true,
enableCloud: false,
},
};
@@ -67,15 +69,9 @@ const environmentPreset = {
enablePlugin: process.env.ENABLE_PLUGIN
? process.env.ENABLE_PLUGIN === 'true'
: currentBuildPreset.enablePlugin,
enableAllPageSaving: process.env.ENABLE_ALL_PAGE_SAVING
? process.env.ENABLE_ALL_PAGE_FILTER === 'true'
: currentBuildPreset.enableAllPageSaving,
enableTestProperties: process.env.ENABLE_TEST_PROPERTIES
? process.env.ENABLE_TEST_PROPERTIES === 'true'
: currentBuildPreset.enableTestProperties,
enableLegacyCloud: process.env.ENABLE_LEGACY_PROVIDER
? process.env.ENABLE_LEGACY_PROVIDER === 'true'
: currentBuildPreset.enableLegacyCloud,
enableBroadcastChannelProvider: process.env.ENABLE_BC_PROVIDER
? process.env.ENABLE_BC_PROVIDER !== 'false'
: currentBuildPreset.enableBroadcastChannelProvider,
@@ -95,6 +91,12 @@ const environmentPreset = {
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,
};
/**

34
apps/web/project.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "@affine/web",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/web",
"sourceRoot": "apps/web/src",
"targets": {
"build": {
"executor": "nx:run-script",
"dependsOn": ["^build"],
"inputs": [
"{projectRoot}/**/*",
"{workspaceRoot}/packages/component/src/**/*",
"{workspaceRoot}/packages/debug/src/**/*",
"{workspaceRoot}/packages/debug/graphql/**/*",
"{workspaceRoot}/packages/hooks/src/**/*",
"{workspaceRoot}/packages/jotai/src/**/*",
"{workspaceRoot}/packages/templates/src/**/*",
"{workspaceRoot}/packages/workspace/src/**/*"
],
"options": {
"script": "build"
},
"outputs": ["{projectRoot}/out"]
},
"dev": {
"executor": "nx:run-script",
"options": {
"script": "dev"
},
"outputs": ["{projectRoot}/.next"]
}
}
}

View File

@@ -0,0 +1,93 @@
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,93 @@
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,91 @@
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,93 @@
Copyright 2016 Google Inc. All Rights Reserved.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More