mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-10 19:38:39 +00:00
Compare commits
139 Commits
v0.7.0-can
...
v0.7.0-can
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4109490789 | ||
|
|
e813436af7 | ||
|
|
5b87d90ffe | ||
|
|
ccbae6f496 | ||
|
|
1ac1c33bb1 | ||
|
|
bd42380f8a | ||
|
|
30dee18835 | ||
|
|
b509302711 | ||
|
|
e51c98c1dd | ||
|
|
bbb1387469 | ||
|
|
4f88774999 | ||
|
|
3968deb6d4 | ||
|
|
37c8465af8 | ||
|
|
d88a21d24a | ||
|
|
6ad2d106bc | ||
|
|
8c1fcee135 | ||
|
|
0514da9759 | ||
|
|
b2fed03f30 | ||
|
|
f5e45573af | ||
|
|
ddb2931f38 | ||
|
|
acf17ebace | ||
|
|
7af3c05b8b | ||
|
|
01de2ae714 | ||
|
|
cfa18d1bc3 | ||
|
|
127c63601e | ||
|
|
f079b0b49a | ||
|
|
6caf934d47 | ||
|
|
2f910fbad0 | ||
|
|
dac4e390aa | ||
|
|
812e0e9c9a | ||
|
|
05291a8a36 | ||
|
|
8bcc4d6a57 | ||
|
|
e06d5e1c8d | ||
|
|
1c8895f23f | ||
|
|
8b5d997322 | ||
|
|
33644a68b2 | ||
|
|
bc85ad5b65 | ||
|
|
fe895905bd | ||
|
|
3c5ccd7231 | ||
|
|
da140b0b85 | ||
|
|
c4d53d59b5 | ||
|
|
a48726d088 | ||
|
|
b49306607b | ||
|
|
3d15c60cb1 | ||
|
|
283f0cd263 | ||
|
|
66152401be | ||
|
|
b12412a3c1 | ||
|
|
ce1e8d868c | ||
|
|
3294043180 | ||
|
|
152fbaabda | ||
|
|
5756bdf8d7 | ||
|
|
80ee33fd3e | ||
|
|
955d80e2c1 | ||
|
|
67fe7f04da | ||
|
|
6395521f09 | ||
|
|
822078e640 | ||
|
|
fafd93f7dc | ||
|
|
00ce086e79 | ||
|
|
28653d6892 | ||
|
|
e30c67482f | ||
|
|
bda28e0404 | ||
|
|
ce63364299 | ||
|
|
f468dff6aa | ||
|
|
fab03006e8 | ||
|
|
8a565b8633 | ||
|
|
e79a6a5d47 | ||
|
|
95c2e20cb5 | ||
|
|
2e0f410978 | ||
|
|
fa1cd87348 | ||
|
|
e95d28e136 | ||
|
|
87ba71e77e | ||
|
|
dec0c0d3d1 | ||
|
|
776172bc88 | ||
|
|
d582548ed8 | ||
|
|
70ac31b907 | ||
|
|
cff9fd1ead | ||
|
|
319febb00d | ||
|
|
72fa2da2d3 | ||
|
|
3084c427f1 | ||
|
|
9cd1f013f8 | ||
|
|
a3f58d4302 | ||
|
|
d4cb89eafc | ||
|
|
33ba034336 | ||
|
|
e158c09160 | ||
|
|
c6ccd6d5de | ||
|
|
ec87864c34 | ||
|
|
a06ba403d0 | ||
|
|
dfbec46ded | ||
|
|
24be73ef63 | ||
|
|
3976c37d41 | ||
|
|
2bc15665b9 | ||
|
|
e4539dfeb1 | ||
|
|
1070e17310 | ||
|
|
b4f7eb36ef | ||
|
|
000f802baa | ||
|
|
e871ffcba0 | ||
|
|
8d2ffe3936 | ||
|
|
9e253420d2 | ||
|
|
edb7847e95 | ||
|
|
3d70148e0f | ||
|
|
7f89b197da | ||
|
|
32692bd54a | ||
|
|
7b2acec7c3 | ||
|
|
f1adf23631 | ||
|
|
a5d2fafad6 | ||
|
|
3d0a907b49 | ||
|
|
bacd00655d | ||
|
|
08e003b0f6 | ||
|
|
0f1c5163a1 | ||
|
|
18874d0d1e | ||
|
|
7f0a74c694 | ||
|
|
901fc87716 | ||
|
|
ee2ab4086f | ||
|
|
af94674c18 | ||
|
|
262289a398 | ||
|
|
467eab4ddf | ||
|
|
63517e4912 | ||
|
|
6f9487deb7 | ||
|
|
8d0edd5255 | ||
|
|
bdea153c82 | ||
|
|
d447883b7d | ||
|
|
03ec51a96c | ||
|
|
0adf18f5e6 | ||
|
|
5e7dc9ff21 | ||
|
|
33097382c6 | ||
|
|
b9df2cdabb | ||
|
|
158338508a | ||
|
|
640967d9ae | ||
|
|
ec973395da | ||
|
|
b35d99d935 | ||
|
|
c0f6e751d2 | ||
|
|
6af454ceed | ||
|
|
ed829dd43b | ||
|
|
a9adb4dda2 | ||
|
|
54a7eeda37 | ||
|
|
711e683c6f | ||
|
|
81c5e6d3d2 | ||
|
|
7a5a5d503a | ||
|
|
b597dbd80f |
@@ -8,3 +8,4 @@ _next
|
||||
lib
|
||||
.eslintrc.js
|
||||
packages/i18n/src/i18n-generated.ts
|
||||
e2e-dist-*
|
||||
|
||||
13
.eslintrc.js
13
.eslintrc.js
@@ -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,
|
||||
|
||||
72
.github/actions/setup-node/action.yml
vendored
72
.github/actions/setup-node/action.yml
vendored
@@ -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
16
.github/labeler.yml
vendored
@@ -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/**/*'
|
||||
|
||||
113
.github/workflows/build.yml
vendored
113
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
3
.github/workflows/cancel.yml
vendored
3
.github/workflows/cancel.yml
vendored
@@ -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 }}
|
||||
|
||||
5
.github/workflows/nightly-build.yml
vendored
5
.github/workflows/nightly-build.yml
vendored
@@ -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
54
.github/workflows/nx.yml
vendored
Normal 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
|
||||
5
.github/workflows/release-desktop-app.yml
vendored
5
.github/workflows/release-desktop-app.yml
vendored
@@ -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
22
.github/workflows/workers.yml
vendored
Normal 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'
|
||||
@@ -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
1
.npmrc
@@ -1,2 +1,3 @@
|
||||
shell-emulator=true
|
||||
electron_mirror="https://cdn.npmmirror.com/binaries/electron/"
|
||||
engine-strict=true
|
||||
|
||||
@@ -10,3 +10,5 @@ dist
|
||||
.yarn
|
||||
tests/affine-legacy/0.7.0-canary.18/static
|
||||
.github/helm
|
||||
_next
|
||||
storybook-static
|
||||
|
||||
17
README.md
17
README.md
@@ -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 (əˈfʌɪn | a-fine).</em>
|
||||
<em>Docs, canvas and tables are hyper-merged with AFFiNE - just like the word affine (əˈfʌɪ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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/electron/.gitignore
vendored
2
apps/electron/.gitignore
vendored
@@ -13,3 +13,5 @@ resources/web-static
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
dev.json
|
||||
|
||||
zip-out
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
});
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
24
apps/electron/project.json
Normal file
24
apps/electron/project.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
42
apps/electron/scripts/macos-arm64-output-check.mts
Normal file
42
apps/electron/scripts/macos-arm64-output-check.mts
Normal 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');
|
||||
16
apps/electron/scripts/unzip-macos-arm64.sh
Executable file
16
apps/electron/scripts/unzip-macos-arm64.sh
Executable 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
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
69
apps/electron/src/helper/db/__tests__/migration.spec.ts
Normal file
69
apps/electron/src/helper/db/__tests__/migration.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
BIN
apps/electron/src/helper/db/__tests__/old-db.affine
Normal file
BIN
apps/electron/src/helper/db/__tests__/old-db.affine
Normal file
Binary file not shown.
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
55
apps/electron/src/helper/db/migration.ts
Normal file
55
apps/electron/src/helper/db/migration.ts
Normal 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);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
35
apps/electron/src/types.d.ts
vendored
35
apps/electron/src/types.d.ts
vendored
@@ -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
|
||||
}
|
||||
26
apps/electron/tests/utils.ts
Normal file
26
apps/electron/tests/utils.ts
Normal 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;
|
||||
}
|
||||
@@ -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" }
|
||||
],
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
12
apps/electron/tsconfig.tests.json
Normal file
12
apps/electron/tsconfig.tests.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"include": ["**/__tests__/**/*", "./tests"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
SECRET_KEY="secret"
|
||||
DATABASE_URL="postgresql://affine@localhost:5432/affine"
|
||||
NEXTAUTH_URL="http://localhost:8080"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -233,5 +233,11 @@ export interface AFFiNEConfig {
|
||||
}
|
||||
>
|
||||
>;
|
||||
email: {
|
||||
server: string;
|
||||
port: number;
|
||||
sender: string;
|
||||
password: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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={() => {}}
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
34
apps/web/project.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
apps/web/public/fonts/inter/Inter-VariableFont_slnt,wght.ttf
Normal file
BIN
apps/web/public/fonts/inter/Inter-VariableFont_slnt,wght.ttf
Normal file
Binary file not shown.
93
apps/web/public/fonts/inter/OFL.txt
Normal file
93
apps/web/public/fonts/inter/OFL.txt
Normal 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.
|
||||
93
apps/web/public/fonts/source-code-pro/OFL.txt
Normal file
93
apps/web/public/fonts/source-code-pro/OFL.txt
Normal 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.
|
||||
Binary file not shown.
Binary file not shown.
91
apps/web/public/fonts/source-serif-4/OFL.txt
Normal file
91
apps/web/public/fonts/source-serif-4/OFL.txt
Normal 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.
|
||||
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Bold.ttf
Normal file
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-BoldItalic.ttf
Normal file
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-BoldItalic.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Italic.ttf
Normal file
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Italic.ttf
Normal file
Binary file not shown.
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Light.ttf
Normal file
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Light.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Medium.ttf
Normal file
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Medium.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Regular.ttf
Normal file
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-Regular.ttf
Normal file
Binary file not shown.
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-SemiBold.ttf
Normal file
BIN
apps/web/public/fonts/source-serif-4/SourceSerif4-SemiBold.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
93
apps/web/public/fonts/space-mono/OFL.txt
Normal file
93
apps/web/public/fonts/space-mono/OFL.txt
Normal 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.
|
||||
BIN
apps/web/public/fonts/space-mono/SpaceMono-Bold.ttf
Normal file
BIN
apps/web/public/fonts/space-mono/SpaceMono-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/web/public/fonts/space-mono/SpaceMono-BoldItalic.ttf
Normal file
BIN
apps/web/public/fonts/space-mono/SpaceMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/web/public/fonts/space-mono/SpaceMono-Italic.ttf
Normal file
BIN
apps/web/public/fonts/space-mono/SpaceMono-Italic.ttf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user