mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 16:44:56 +00:00
Compare commits
91 Commits
hwang/inte
...
v0.8.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ddc76a703 | ||
|
|
9d6d5594b5 | ||
|
|
96f3bbd484 | ||
|
|
561970f7ff | ||
|
|
357b403073 | ||
|
|
77d1dd674b | ||
|
|
7035584203 | ||
|
|
c55df09db0 | ||
|
|
ddbc37dd45 | ||
|
|
e59ec2de62 | ||
|
|
73aea1e2d1 | ||
|
|
38a50b4fe8 | ||
|
|
c2d901c245 | ||
|
|
9eee00ddf3 | ||
|
|
96dcd84ee1 | ||
|
|
4d047db7ec | ||
|
|
aa254fc8fd | ||
|
|
8a69efe899 | ||
|
|
ae6171709a | ||
|
|
b147fc54cc | ||
|
|
531abb149a | ||
|
|
a603105175 | ||
|
|
e3bf83e107 | ||
|
|
a22f0c3380 | ||
|
|
4e492cd515 | ||
|
|
03ba5b3fbb | ||
|
|
2302797be4 | ||
|
|
82e40325b7 | ||
|
|
20a1aa697f | ||
|
|
b6c46e82d2 | ||
|
|
88648a018c | ||
|
|
b944d80fa8 | ||
|
|
1f563c7cca | ||
|
|
78caa7cebc | ||
|
|
6a59320db3 | ||
|
|
04b174f7b7 | ||
|
|
ff843b450a | ||
|
|
41cdb411a0 | ||
|
|
36e59d84fa | ||
|
|
1337943917 | ||
|
|
b118335a27 | ||
|
|
76c3d4d814 | ||
|
|
791c70d66f | ||
|
|
5a2d9426ed | ||
|
|
0fb5c9c7b9 | ||
|
|
c664a0f071 | ||
|
|
dc6c3809e7 | ||
|
|
cf2cca86a3 | ||
|
|
b4ccd808fd | ||
|
|
08e7d75ddd | ||
|
|
bbddd2ef70 | ||
|
|
ca6c0519d0 | ||
|
|
d3baf5a401 | ||
|
|
a83e16fdca | ||
|
|
82f94b0294 | ||
|
|
c23d0dd917 | ||
|
|
85f670e02e | ||
|
|
51ced217db | ||
|
|
efcd106ea1 | ||
|
|
2dbee6b3eb | ||
|
|
abf743ccd1 | ||
|
|
bda913e334 | ||
|
|
d3240a2787 | ||
|
|
dda087de08 | ||
|
|
555feb59d1 | ||
|
|
ab70ab2126 | ||
|
|
7f7bf6fef9 | ||
|
|
01de16a3ae | ||
|
|
28779c73c2 | ||
|
|
46b5d2bf1a | ||
|
|
5f8084137d | ||
|
|
936f588db4 | ||
|
|
3ec108b60c | ||
|
|
dbbd83dd1e | ||
|
|
7e83593d5e | ||
|
|
d4fa24a4b0 | ||
|
|
05b28e386f | ||
|
|
2694891574 | ||
|
|
044ea4ae64 | ||
|
|
6ded3664ea | ||
|
|
c64cce61f9 | ||
|
|
207343c923 | ||
|
|
296092323a | ||
|
|
314f126e4f | ||
|
|
04172c5b04 | ||
|
|
90f3fe0e29 | ||
|
|
d9cce14b1f | ||
|
|
4e6af63751 | ||
|
|
dd7e701276 | ||
|
|
d158c5a0dc | ||
|
|
8dd491784d |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
@@ -21,6 +21,7 @@
|
||||
"native",
|
||||
"templates",
|
||||
"y-indexeddb",
|
||||
"y-provider",
|
||||
"debug",
|
||||
"storage",
|
||||
"infra",
|
||||
|
||||
4
.github/actions/setup-node/action.yml
vendored
4
.github/actions/setup-node/action.yml
vendored
@@ -121,3 +121,7 @@ runs:
|
||||
- name: Build Infra
|
||||
shell: bash
|
||||
run: yarn run build:infra
|
||||
|
||||
- name: Build Plugins
|
||||
shell: bash
|
||||
run: yarn run build:plugins
|
||||
|
||||
177
.github/workflows/build-desktop.yml
vendored
Normal file
177
.github/workflows/build-desktop.yml
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
name: Build(Desktop) & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- .github/**
|
||||
- '!.github/workflows/build-desktop.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/build-desktop.yml'
|
||||
- '!.github/actions/build-rust/action.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
BUILD_TYPE: canary
|
||||
APP_NAME: affine
|
||||
COVERAGE: true
|
||||
DISTRIBUTION: desktop
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build-core:
|
||||
name: Build @affine/core
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
- name: Build Core
|
||||
run: yarn nx build @affine/core
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
desktop-test:
|
||||
name: Desktop Test
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
environment: development
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
|
||||
matrix:
|
||||
spec:
|
||||
- {
|
||||
os: macos-latest,
|
||||
platform: macos,
|
||||
arch: x64,
|
||||
target: x86_64-apple-darwin,
|
||||
test: true,
|
||||
}
|
||||
- {
|
||||
os: macos-latest,
|
||||
platform: macos,
|
||||
arch: arm64,
|
||||
target: aarch64-apple-darwin,
|
||||
test: false,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
platform: linux,
|
||||
arch: x64,
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
test: true,
|
||||
}
|
||||
- {
|
||||
os: windows-latest,
|
||||
platform: windows,
|
||||
arch: x64,
|
||||
target: x86_64-pc-windows-msvc,
|
||||
test: true,
|
||||
}
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
playwright-install: true
|
||||
hard-link-nm: false
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
- name: Run unit tests
|
||||
if: ${{ matrix.spec.test }}
|
||||
shell: bash
|
||||
run: yarn vitest
|
||||
working-directory: ./apps/electron
|
||||
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: Upload desktop dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: dist-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: ./apps/electron/dist
|
||||
|
||||
- name: Run desktop tests
|
||||
if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
|
||||
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 workspace @affine/electron test
|
||||
env:
|
||||
COVERAGE: true
|
||||
|
||||
- name: Make bundle
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
env:
|
||||
SKIP_BUNDLE: true
|
||||
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: |
|
||||
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
|
||||
|
||||
- name: Upload e2e test coverage results
|
||||
if: ${{ matrix.spec.test }}
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./.coverage/lcov.info
|
||||
flags: e2etest-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
260
.github/workflows/build.yml
vendored
260
.github/workflows/build.yml
vendored
@@ -30,6 +30,7 @@ env:
|
||||
BUILD_TYPE: canary
|
||||
APP_NAME: affine
|
||||
COVERAGE: true
|
||||
DISTRIBUTION: browser
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
|
||||
@@ -133,26 +134,6 @@ jobs:
|
||||
path: ./apps/storybook/storybook-static
|
||||
if-no-files-found: error
|
||||
|
||||
build-core:
|
||||
name: Build @affine/core
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
- name: Build Core
|
||||
run: yarn nx build @affine/core
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-storage:
|
||||
name: Build Storage
|
||||
runs-on: ubuntu-latest
|
||||
@@ -235,33 +216,10 @@ jobs:
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
storybook-test:
|
||||
name: Storybook Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: [build-storybook]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download storybook artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storybook
|
||||
path: ./apps/storybook/storybook-static
|
||||
- name: Run storybook tests
|
||||
working-directory: ./apps/storybook
|
||||
run: |
|
||||
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
|
||||
|
||||
e2e-plugin-test:
|
||||
name: E2E Plugin Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
@@ -269,11 +227,6 @@ jobs:
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: tests/affine-plugin
|
||||
@@ -350,8 +303,6 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
environment: development
|
||||
needs: build-core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
@@ -359,11 +310,6 @@ jobs:
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
@@ -395,7 +341,6 @@ jobs:
|
||||
name: E2E Migration Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
strategy:
|
||||
matrix:
|
||||
spec:
|
||||
@@ -409,12 +354,6 @@ jobs:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
|
||||
- name: Unzip
|
||||
run: yarn unzip
|
||||
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
|
||||
@@ -431,127 +370,6 @@ jobs:
|
||||
path: ./tests/affine-legacy/${{ matrix.spec.package }}/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
desktop-test:
|
||||
name: Desktop Test
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
environment: development
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
|
||||
matrix:
|
||||
spec:
|
||||
- {
|
||||
os: macos-latest,
|
||||
platform: macos,
|
||||
arch: x64,
|
||||
target: x86_64-apple-darwin,
|
||||
test: true,
|
||||
}
|
||||
- {
|
||||
os: macos-latest,
|
||||
platform: macos,
|
||||
arch: arm64,
|
||||
target: aarch64-apple-darwin,
|
||||
test: false,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
platform: linux,
|
||||
arch: x64,
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
test: true,
|
||||
}
|
||||
- {
|
||||
os: windows-latest,
|
||||
platform: windows,
|
||||
arch: x64,
|
||||
target: x86_64-pc-windows-msvc,
|
||||
test: true,
|
||||
}
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
playwright-install: true
|
||||
hard-link-nm: false
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
- name: Run unit tests
|
||||
if: ${{ matrix.spec.test }}
|
||||
shell: bash
|
||||
run: yarn vitest
|
||||
working-directory: ./apps/electron
|
||||
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: Upload desktop dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: dist-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: ./apps/electron/dist
|
||||
|
||||
- name: Run desktop tests
|
||||
if: ${{ matrix.spec.test && matrix.spec.os == 'ubuntu-latest' }}
|
||||
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 workspace @affine/electron test
|
||||
env:
|
||||
COVERAGE: true
|
||||
|
||||
- name: Make bundle
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
env:
|
||||
SKIP_BUNDLE: true
|
||||
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: |
|
||||
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
|
||||
|
||||
- name: Upload e2e test coverage results
|
||||
if: ${{ matrix.spec.test }}
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./.coverage/lcov.info
|
||||
flags: e2etest-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
unit-test:
|
||||
name: Unit Test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -574,79 +392,3 @@ jobs:
|
||||
flags: unittest
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
build-docker:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
name: Build Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-server
|
||||
- build-core
|
||||
- build-storage
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
- name: Download server dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
- name: Download storage.node
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
- name: Setup Git short hash
|
||||
run: |
|
||||
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
logout: false
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Build front Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
pull: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: true
|
||||
file: .github/deployment/front/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine-front:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:latest
|
||||
|
||||
# setup node without cache configuration
|
||||
# Prisma cache is not compatible with docker build cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
scope: '@toeverything'
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn workspaces focus @affine/server --production
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: yarn workspace @affine/server prisma generate
|
||||
|
||||
- name: Build graphql Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
pull: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: true
|
||||
file: .github/deployment/node/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine-graphql:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:latest
|
||||
|
||||
2
.github/workflows/cancel.yml
vendored
2
.github/workflows/cancel.yml
vendored
@@ -14,5 +14,5 @@ jobs:
|
||||
- uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
|
||||
workflow_id: 44038251, 61883931, 65188160
|
||||
workflow_id: 44038251, 61883931, 65188160, 66789140
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
38
.github/workflows/publish-storybook.yml
vendored
Normal file
38
.github/workflows/publish-storybook.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Publish Storybook
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- .github/**
|
||||
- '!.github/workflows/publish-storybook.yml'
|
||||
|
||||
jobs:
|
||||
publish-storybook:
|
||||
name: Publish Storybook
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
# This is required to fetch all commits for chromatic
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
- name: Publish to Chromatic
|
||||
uses: chromaui/action@v1
|
||||
with:
|
||||
workingDir: apps/storybook
|
||||
buildScriptName: build
|
||||
onlyChanged: true
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
211
.github/workflows/release-desktop-app.yml
vendored
211
.github/workflows/release-desktop-app.yml
vendored
@@ -77,34 +77,23 @@ jobs:
|
||||
make-distribution:
|
||||
environment: production
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
# For windows, we need a separate approach
|
||||
matrix:
|
||||
spec:
|
||||
- {
|
||||
os: macos-latest,
|
||||
platform: darwin,
|
||||
arch: x64,
|
||||
target: x86_64-apple-darwin,
|
||||
}
|
||||
- {
|
||||
os: macos-latest,
|
||||
platform: darwin,
|
||||
arch: arm64,
|
||||
target: aarch64-apple-darwin,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
platform: linux,
|
||||
arch: x64,
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
}
|
||||
- {
|
||||
os: windows-latest,
|
||||
platform: win32,
|
||||
arch: x64,
|
||||
target: x86_64-pc-windows-msvc,
|
||||
}
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
- runner: macos-latest
|
||||
platform: darwin
|
||||
arch: x64
|
||||
target: x86_64-apple-darwin
|
||||
- runner: macos-latest
|
||||
platform: darwin
|
||||
arch: arm64
|
||||
target: aarch64-apple-darwin
|
||||
- runner: ubuntu-latest
|
||||
platform: linux
|
||||
arch: x64
|
||||
target: x86_64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
needs: before-make
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
@@ -151,15 +140,6 @@ jobs:
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
mv apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
- name: Save artifacts (windows)
|
||||
if: ${{ matrix.spec.platform == 'win32' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.nupkg ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.nupkg
|
||||
|
||||
- name: Save artifacts (linux)
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
run: |
|
||||
@@ -173,8 +153,166 @@ jobs:
|
||||
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
|
||||
path: builds
|
||||
|
||||
package-distribution-windows:
|
||||
environment: production
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
# For windows, we need a separate approach
|
||||
matrix:
|
||||
spec:
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
needs: before-make
|
||||
outputs:
|
||||
FILES_TO_BE_SIGNED: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
|
||||
env:
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
|
||||
- name: package
|
||||
run: yarn workspace @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\apps\electron\out\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path apps/electron/out/* -DestinationPath archive.zip
|
||||
|
||||
- name: Save packaged artifacts for signing
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: |
|
||||
archive.zip
|
||||
!**/*.map
|
||||
|
||||
sign-packaged-artifacts-windows:
|
||||
needs: package-distribution-windows
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED }}
|
||||
artifact-name: packaged-win32-x64
|
||||
|
||||
make-windows-installer:
|
||||
environment: production
|
||||
needs: sign-packaged-artifacts-windows
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
# For windows, we need a separate approach
|
||||
matrix:
|
||||
spec:
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
outputs:
|
||||
FILES_TO_BE_SIGNED: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Download and overwrite packaged artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: signed-packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: .
|
||||
- name: unzip file
|
||||
run: Expand-Archive -Path signed.zip -DestinationPath apps/electron/out
|
||||
|
||||
- name: Make squirrel.windows installer
|
||||
run: yarn workspace @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path apps/electron/out/${{ env.BUILD_TYPE }}/make/* -DestinationPath archive.zip
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path apps/electron/out/${{ env.BUILD_TYPE }}/make -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\apps\electron\out\${{ env.BUILD_TYPE }}\make\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Save installer for signing
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: archive.zip
|
||||
|
||||
sign-installer-artifacts-windows:
|
||||
needs: make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.make-windows-installer.outputs.FILES_TO_BE_SIGNED }}
|
||||
artifact-name: installer-win32-x64
|
||||
|
||||
finalize-installer-windows:
|
||||
environment: production
|
||||
needs: sign-installer-artifacts-windows
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
# For windows, we need a separate approach
|
||||
matrix:
|
||||
spec:
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
steps:
|
||||
- name: Download and overwrite installer artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: signed-installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: .
|
||||
- name: unzip file
|
||||
run: Expand-Archive -Path signed.zip -DestinationPath apps/electron/out/${{ env.BUILD_TYPE }}/make
|
||||
|
||||
- name: Save artifacts
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
|
||||
path: builds
|
||||
|
||||
release:
|
||||
needs: [before-make, make-distribution]
|
||||
needs: [before-make, make-distribution, finalize-installer-windows]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -222,7 +360,6 @@ jobs:
|
||||
./*.zip
|
||||
./*.dmg
|
||||
./*.exe
|
||||
./*.nupkg
|
||||
./RELEASES
|
||||
./*.AppImage
|
||||
./*.apk
|
||||
|
||||
146
.github/workflows/release.yml
vendored
146
.github/workflows/release.yml
vendored
@@ -5,6 +5,13 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
BUILD_TYPE: stable
|
||||
APP_NAME: affine
|
||||
COVERAGE: false
|
||||
DISTRIBUTION: browser
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Try publishing npm@latest release
|
||||
@@ -17,3 +24,142 @@ jobs:
|
||||
run: ./scripts/publish.sh
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
build-core:
|
||||
name: Build @affine/core
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
- name: Build Core
|
||||
run: yarn nx build @affine/core
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-server:
|
||||
name: Build Server
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Server
|
||||
run: yarn nx build @affine/server
|
||||
- name: Upload server dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-storage:
|
||||
name: Build Storage
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: '-C debuginfo=1'
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
- name: Build Storage
|
||||
run: yarn build:storage
|
||||
- name: Upload storage.node
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
if-no-files-found: error
|
||||
|
||||
build-docker:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
name: Build Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-server
|
||||
- build-core
|
||||
- build-storage
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
- name: Download server dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
- name: Download storage.node
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
- name: Setup Git short hash
|
||||
run: |
|
||||
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
logout: false
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Build front Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
pull: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: true
|
||||
file: .github/deployment/front/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine-front:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:latest
|
||||
|
||||
# setup node without cache configuration
|
||||
# Prisma cache is not compatible with docker build cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
scope: '@toeverything'
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn workspaces focus @affine/server --production
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: yarn workspace @affine/server prisma generate
|
||||
|
||||
- name: Build graphql Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
pull: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: true
|
||||
file: .github/deployment/node/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine-graphql:${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:latest
|
||||
|
||||
42
.github/workflows/windows-signer.yml
vendored
Normal file
42
.github/workflows/windows-signer.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Windows Signer
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
artifact-name:
|
||||
required: true
|
||||
type: string
|
||||
files:
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
sign:
|
||||
runs-on: [self-hosted, win-signer]
|
||||
env:
|
||||
ARCHIVE_DIR: ${{ github.run_id }}-${{ github.run_attempt }}-${{ inputs.artifact-name }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ inputs.artifact-name }}
|
||||
path: ${{ env.ARCHIVE_DIR }}
|
||||
- name: unzip file
|
||||
shell: cmd
|
||||
# 7za is pre-installed on the signer machine
|
||||
run: |
|
||||
cd ${{ env.ARCHIVE_DIR }}
|
||||
md out
|
||||
7za x archive.zip -y -oout
|
||||
- name: sign
|
||||
shell: cmd
|
||||
run: |
|
||||
cd ${{ env.ARCHIVE_DIR }}/out
|
||||
signtool sign /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a ${{ inputs.files }}
|
||||
- name: zip file
|
||||
shell: cmd
|
||||
run: |
|
||||
cd ${{ env.ARCHIVE_DIR }}
|
||||
7za a signed.zip .\out\*
|
||||
- name: upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: signed-${{ inputs.artifact-name }}
|
||||
path: ${{ env.ARCHIVE_DIR }}/signed.zip
|
||||
14
README.md
14
README.md
@@ -127,7 +127,7 @@ If you have questions, you are welcome to contact us. One of the best places to
|
||||
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing |
|
||||
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image |
|
||||
|
||||
## Thanks
|
||||
## Upstreams
|
||||
|
||||
We would also like to give thanks to open-source projects that make AFFiNE possible:
|
||||
|
||||
@@ -178,11 +178,19 @@ See [BUILDING.md] for instructions on how to build AFFiNE from source code.
|
||||
We welcome contributions from everyone.
|
||||
See [docs/contributing/tutorial.md](./docs/contributing/tutorial.md) for details.
|
||||
|
||||
## Thanks
|
||||
|
||||
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
|
||||
|
||||
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE] for details.
|
||||
|
||||
[all-contributors-badge]: https://img.shields.io/github/all-contributors/toeverything/AFFiNE/master?color=orange
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
|
||||
|
||||
[all-contributors-badge]: https://img.shields.io/github/contributors/toeverything/AFFiNE
|
||||
[license]: ./LICENSE
|
||||
[building.md]: ./docs/BUILDING.md
|
||||
[update page]: https://affine.pro/blog?tag=Release%20Note
|
||||
@@ -196,5 +204,3 @@ See [LICENSE] for details.
|
||||
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
|
||||
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fcore%2Fpackage.json&label=blocksuite
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftoeverything%2FAFFiNE?ref=badge_large)
|
||||
|
||||
@@ -6,14 +6,21 @@ const require = createRequire(import.meta.url);
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
const editorFlags: BlockSuiteFeatureFlags = {
|
||||
enable_database: true,
|
||||
enable_slash_menu: true,
|
||||
enable_edgeless_toolbar: true,
|
||||
enable_block_hub: true,
|
||||
enable_drag_handle: true,
|
||||
enable_block_hub: true,
|
||||
enable_surface: true,
|
||||
enable_edgeless_toolbar: true,
|
||||
enable_slash_menu: true,
|
||||
enable_database: true,
|
||||
enable_database_filter: false,
|
||||
enable_data_view: false,
|
||||
enable_page_tags: false,
|
||||
enable_toggle_block: false,
|
||||
enable_linked_page: true,
|
||||
enable_bookmark_operation: false,
|
||||
enable_note_index: false,
|
||||
|
||||
enable_attachment_block: true,
|
||||
};
|
||||
|
||||
export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
@@ -23,7 +30,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableTestProperties: false,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0728',
|
||||
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0818',
|
||||
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.21",
|
||||
"version": "0.8.0-beta.3",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
"static-server": "ts-node-esm ./server.mts"
|
||||
"static-server": "yarn -T run dev-core --static"
|
||||
},
|
||||
"exports": {
|
||||
"./app": "./src/app.tsx",
|
||||
"./router": "./src/router.ts",
|
||||
"./bootstrap/setup": "./src/bootstrap/setup.ts",
|
||||
"./bootstrap/register-plugins": "./src/bootstrap/register-plugins.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
@@ -18,33 +24,32 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.4",
|
||||
"@mui/material": "^5.14.5",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"@toeverything/components": "^0.0.10",
|
||||
"@types/lodash.throttle": "^4.1.7",
|
||||
"@toeverything/components": "^0.0.12",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"graphql": "^16.7.1",
|
||||
"graphql": "^16.8.0",
|
||||
"intl-segmenter-polyfill-rs": "^0.1.5",
|
||||
"jotai": "^2.3.1",
|
||||
"jotai-devtools": "^0.6.1",
|
||||
"lit": "^2.8.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lottie-web": "^5.12.2",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"next-themes": "^0.2.1",
|
||||
@@ -52,21 +57,22 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-is": "18.2.0",
|
||||
"react-resizable-panels": "^0.0.54",
|
||||
"react-resizable-panels": "^0.0.55",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"ses": "^0.18.7",
|
||||
"swr": "2.2.1",
|
||||
"y-protocols": "^1.0.5",
|
||||
"yjs": "^13.6.7",
|
||||
"zod": "^3.21.4"
|
||||
"zod": "^3.22.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@perfsee/webpack": "^1.8.4",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
|
||||
"@sentry/webpack-plugin": "^2.6.2",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
"@swc/core": "^1.3.76",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.3.77",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/webpack-env": "^1.18.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
@@ -78,7 +84,6 @@
|
||||
"swc-loader": "^0.2.3",
|
||||
"swc-plugin-coverage-instrument": "^0.0.20",
|
||||
"thread-loader": "^4.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// static server for web app
|
||||
import express from 'express';
|
||||
const app = express();
|
||||
|
||||
const PORT = process.env.PORT || 8080;
|
||||
|
||||
app.use('/', express.static('dist'));
|
||||
|
||||
app.get('/*', (req, res) => {
|
||||
if (req.url.startsWith('/plugins')) {
|
||||
res.sendFile(req.url, { root: 'dist' });
|
||||
}
|
||||
res.sendFile('index.html', { root: 'dist' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
@@ -1,17 +1,23 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import {
|
||||
getCurrentStore,
|
||||
loadedPluginNameAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { use } from 'foxact/use';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Provider } from 'jotai/react';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { _pluginNestedImportsMap } from '../bootstrap/plugins/setup';
|
||||
import { pluginRegisterPromise } from '../bootstrap/register-plugins';
|
||||
import { createSetup } from '../bootstrap/plugins/setup';
|
||||
import { bootstrapPluginSystem } from '../bootstrap/register-plugins';
|
||||
|
||||
async function main() {
|
||||
const { setup } = await import('../bootstrap/setup');
|
||||
await setup();
|
||||
const rootStore = getCurrentStore();
|
||||
await setup(rootStore);
|
||||
const { _pluginNestedImportsMap } = createSetup(rootStore);
|
||||
const pluginRegisterPromise = bootstrapPluginSystem(rootStore);
|
||||
const root = document.getElementById('app');
|
||||
assertExists(root);
|
||||
|
||||
|
||||
@@ -20,8 +20,11 @@ import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { setPageModeAtom } from '../../atoms';
|
||||
import {
|
||||
BlockSuitePageList,
|
||||
NewWorkspaceSettingDetail,
|
||||
@@ -43,7 +46,12 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
);
|
||||
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||
if (runtimeConfig.enablePreloading) {
|
||||
buildShowcaseWorkspace(blockSuiteWorkspace).catch(err => {
|
||||
buildShowcaseWorkspace(blockSuiteWorkspace, {
|
||||
store: getCurrentStore(),
|
||||
atoms: {
|
||||
pageMode: setPageModeAtom,
|
||||
},
|
||||
}).catch(err => {
|
||||
logger.error('init page with preloading failed', err);
|
||||
});
|
||||
} else {
|
||||
@@ -84,7 +92,7 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
<>
|
||||
<PageDetailEditor
|
||||
pageId={currentPageId}
|
||||
onInit={initEmptyPage}
|
||||
onInit={useCallback(async page => initEmptyPage(page), [])}
|
||||
onLoad={onLoadEditor}
|
||||
workspace={workspace}
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import '@toeverything/components/style.css';
|
||||
import { AffineContext } from '@affine/component/context';
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { use } from 'foxact/use';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, memo, Suspense } from 'react';
|
||||
@@ -47,7 +48,7 @@ export const App = memo(function App() {
|
||||
use(languageLoadingPromise);
|
||||
return (
|
||||
<CacheProvider value={cache}>
|
||||
<AffineContext>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<DebugProvider>
|
||||
<RouterProvider
|
||||
fallbackElement={<WorkspaceFallback key="RouterFallback" />}
|
||||
|
||||
@@ -18,27 +18,37 @@ import {
|
||||
contentLayoutAtom,
|
||||
currentPageAtom,
|
||||
currentWorkspaceAtom,
|
||||
rootStore,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { atom } from 'jotai';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { createStore } from 'jotai/vanilla';
|
||||
import { createElement, type PropsWithChildren } from 'react';
|
||||
|
||||
import { createFetch } from './endowments/fercher';
|
||||
import { createTimers } from './endowments/timer';
|
||||
import { setupImportsMap } from './setup-imports-map';
|
||||
|
||||
const dynamicImportKey = '$h_import';
|
||||
// DO NOT REMOVE INVISIBLE CHARACTERS
|
||||
const dynamicImportKey = '$h_import';
|
||||
|
||||
const permissionLogger = new DebugLogger('plugins:permission');
|
||||
const importLogger = new DebugLogger('plugins:import');
|
||||
const entryLogger = new DebugLogger('plugins:entry');
|
||||
|
||||
const pushLayoutAtom = atom<
|
||||
null,
|
||||
// fixme: check plugin name here
|
||||
[pluginName: string, create: (root: HTMLElement) => () => void],
|
||||
[
|
||||
pluginName: string,
|
||||
create: (root: HTMLElement) => () => void,
|
||||
options:
|
||||
| {
|
||||
maxWidth: (number | undefined)[];
|
||||
}
|
||||
| undefined,
|
||||
],
|
||||
void
|
||||
>(null, (_, set, pluginName, callback) => {
|
||||
>(null, (_, set, pluginName, callback, options) => {
|
||||
set(pluginWindowAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback,
|
||||
@@ -50,20 +60,20 @@ const pushLayoutAtom = atom<
|
||||
first: 'editor',
|
||||
second: pluginName,
|
||||
splitPercentage: 70,
|
||||
maxWidth: options?.maxWidth,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...layout,
|
||||
direction: 'horizontal',
|
||||
first: 'editor',
|
||||
splitPercentage: 70,
|
||||
second: {
|
||||
direction: 'horizontal',
|
||||
// fixme: incorrect type here
|
||||
first: layout.second,
|
||||
second: pluginName,
|
||||
splitPercentage: 70,
|
||||
first: pluginName,
|
||||
second: layout.second,
|
||||
splitPercentage: 50,
|
||||
},
|
||||
} as ExpectedLayout;
|
||||
} satisfies ExpectedLayout;
|
||||
}
|
||||
});
|
||||
addCleanup(pluginName, () => {
|
||||
@@ -77,471 +87,494 @@ const deleteLayoutAtom = atom<null, [string], void>(null, (_, set, id) => {
|
||||
delete newItems[id];
|
||||
return newItems;
|
||||
});
|
||||
const removeLayout = (layout: LayoutNode): LayoutNode => {
|
||||
if (layout === 'editor') {
|
||||
return 'editor';
|
||||
const removeLayout = (layout: LayoutNode): LayoutNode | string => {
|
||||
if (typeof layout === 'string') {
|
||||
return layout;
|
||||
}
|
||||
if (layout.first === id) {
|
||||
return layout.second;
|
||||
} else if (layout.second === id) {
|
||||
return layout.first;
|
||||
} else {
|
||||
if (typeof layout === 'string') {
|
||||
return layout as ExpectedLayout;
|
||||
}
|
||||
if (layout.first === id) {
|
||||
return layout.second;
|
||||
} else if (layout.second === id) {
|
||||
return layout.first;
|
||||
} else {
|
||||
return removeLayout(layout.second);
|
||||
}
|
||||
return {
|
||||
...layout,
|
||||
second: removeLayout(layout.second),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
set(contentLayoutAtom, layout => {
|
||||
if (layout === 'editor') {
|
||||
return 'editor';
|
||||
} else {
|
||||
if (typeof layout === 'string') {
|
||||
return layout as ExpectedLayout;
|
||||
}
|
||||
if (layout.first === id) {
|
||||
return layout.second as ExpectedLayout;
|
||||
} else if (layout.second === id) {
|
||||
return layout.first as ExpectedLayout;
|
||||
} else {
|
||||
return removeLayout(layout.second) as ExpectedLayout;
|
||||
}
|
||||
return removeLayout(layout) as ExpectedLayout;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// module -> importName -> updater[]
|
||||
export const _rootImportsMap = new Map<string, Map<string, any>>();
|
||||
const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
|
||||
react: import('react'),
|
||||
'react/jsx-runtime': import('react/jsx-runtime'),
|
||||
'react-dom': import('react-dom'),
|
||||
'react-dom/client': import('react-dom/client'),
|
||||
jotai: import('jotai'),
|
||||
'jotai/utils': import('jotai/utils'),
|
||||
swr: import('swr'),
|
||||
'@affine/component': import('@affine/component'),
|
||||
'@blocksuite/icons': import('@blocksuite/icons'),
|
||||
'@affine/sdk/entry': {
|
||||
rootStore: rootStore,
|
||||
currentWorkspaceAtom: currentWorkspaceAtom,
|
||||
currentPageAtom: currentPageAtom,
|
||||
pushLayoutAtom: pushLayoutAtom,
|
||||
deleteLayoutAtom: deleteLayoutAtom,
|
||||
},
|
||||
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
|
||||
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
|
||||
'@toeverything/components/button': import('@toeverything/components/button'),
|
||||
});
|
||||
|
||||
// pluginName -> module -> importName -> updater[]
|
||||
export const _pluginNestedImportsMap = new Map<
|
||||
string,
|
||||
Map<string, Map<string, any>>
|
||||
const setupWeakMap = new WeakMap<
|
||||
ReturnType<typeof createStore>,
|
||||
ReturnType<typeof createSetupImpl>
|
||||
>();
|
||||
|
||||
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
|
||||
export const createImports = (pluginName: string) => {
|
||||
if (pluginImportsFunctionMap.has(pluginName)) {
|
||||
export function createSetup(rootStore: ReturnType<typeof createStore>) {
|
||||
if (setupWeakMap.has(rootStore)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return pluginImportsFunctionMap.get(pluginName)!;
|
||||
return setupWeakMap.get(rootStore)!;
|
||||
}
|
||||
const imports = (
|
||||
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
|
||||
const setup = createSetupImpl(rootStore);
|
||||
setupWeakMap.set(rootStore, setup);
|
||||
return setup;
|
||||
}
|
||||
|
||||
function createSetupImpl(rootStore: ReturnType<typeof createStore>) {
|
||||
// clean up plugin windows when switching to other pages
|
||||
rootStore.sub(currentPageAtom, () => {
|
||||
rootStore.set(contentLayoutAtom, 'editor');
|
||||
});
|
||||
|
||||
// module -> importName -> updater[]
|
||||
const _rootImportsMap = new Map<string, Map<string, any>>();
|
||||
const rootImportsMapSetupPromise = setupImportsMap(_rootImportsMap, {
|
||||
react: import('react'),
|
||||
'react/jsx-runtime': import('react/jsx-runtime'),
|
||||
'react-dom': import('react-dom'),
|
||||
'react-dom/client': import('react-dom/client'),
|
||||
jotai: import('jotai'),
|
||||
'jotai/utils': import('jotai/utils'),
|
||||
swr: import('swr'),
|
||||
'@affine/component': import('@affine/component'),
|
||||
'@blocksuite/icons': import('@blocksuite/icons'),
|
||||
'@blocksuite/blocks': import('@blocksuite/blocks'),
|
||||
'@affine/sdk/entry': {
|
||||
rootStore,
|
||||
currentWorkspaceAtom: currentWorkspaceAtom,
|
||||
currentPageAtom: currentPageAtom,
|
||||
pushLayoutAtom: pushLayoutAtom,
|
||||
deleteLayoutAtom: deleteLayoutAtom,
|
||||
},
|
||||
'@blocksuite/global/utils': import('@blocksuite/global/utils'),
|
||||
'@toeverything/infra/atom': import('@toeverything/infra/atom'),
|
||||
'@toeverything/components/button': import(
|
||||
'@toeverything/components/button'
|
||||
),
|
||||
});
|
||||
|
||||
// pluginName -> module -> importName -> updater[]
|
||||
const _pluginNestedImportsMap = new Map<
|
||||
string,
|
||||
Map<string, Map<string, any>>
|
||||
>();
|
||||
|
||||
const pluginImportsFunctionMap = new Map<string, (imports: any) => void>();
|
||||
const createImports = (pluginName: string) => {
|
||||
if (pluginImportsFunctionMap.has(pluginName)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return pluginImportsFunctionMap.get(pluginName)!;
|
||||
}
|
||||
const imports = (
|
||||
newUpdaters: [string, [string, ((val: any) => void)[]][]][]
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
importLogger.debug('currentImportMap', pluginName, currentImportMap);
|
||||
|
||||
for (const [module, moduleUpdaters] of newUpdaters) {
|
||||
importLogger.debug('imports module', module, moduleUpdaters);
|
||||
let moduleImports = _rootImportsMap.get(module);
|
||||
if (!moduleImports) {
|
||||
moduleImports = currentImportMap.get(module);
|
||||
}
|
||||
if (moduleImports) {
|
||||
for (const [importName, importUpdaters] of moduleUpdaters) {
|
||||
const updateImport = (value: any) => {
|
||||
for (const importUpdater of importUpdaters) {
|
||||
importUpdater(value);
|
||||
}
|
||||
};
|
||||
if (moduleImports.has(importName)) {
|
||||
const val = moduleImports.get(importName);
|
||||
updateImport(val);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
'cannot find module in plugin import map',
|
||||
module,
|
||||
currentImportMap,
|
||||
_pluginNestedImportsMap
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
pluginImportsFunctionMap.set(pluginName, imports);
|
||||
return imports;
|
||||
};
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const pluginFetch = createFetch({});
|
||||
const timer = createTimers(abortController.signal);
|
||||
|
||||
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
|
||||
Object: globalThis.Object,
|
||||
fetch: pluginFetch,
|
||||
ReadableStream: globalThis.ReadableStream,
|
||||
Symbol: globalThis.Symbol,
|
||||
Error: globalThis.Error,
|
||||
TypeError: globalThis.TypeError,
|
||||
RangeError: globalThis.RangeError,
|
||||
console: globalThis.console,
|
||||
crypto: globalThis.crypto,
|
||||
});
|
||||
|
||||
const dynamicImportMap = new Map<
|
||||
string,
|
||||
(moduleName: string) => Promise<any>
|
||||
>();
|
||||
|
||||
const createOrGetDynamicImport = (baseUrl: string, pluginName: string) => {
|
||||
if (dynamicImportMap.has(pluginName)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return dynamicImportMap.get(pluginName)!;
|
||||
}
|
||||
const dynamicImport = async (moduleName: string): Promise<any> => {
|
||||
const codeUrl = `${baseUrl}/${moduleName}`;
|
||||
const analysisUrl = `${baseUrl}/${moduleName}.json`;
|
||||
const response = await fetch(codeUrl);
|
||||
const analysisResponse = await fetch(analysisUrl);
|
||||
const analysis = await analysisResponse.json();
|
||||
const exports = analysis.exports as string[];
|
||||
const code = await response.text();
|
||||
const moduleCompartment = new Compartment(
|
||||
createOrGetGlobalThis(
|
||||
pluginName,
|
||||
// use singleton here to avoid infinite loop
|
||||
createOrGetDynamicImport(pluginName, baseUrl)
|
||||
)
|
||||
);
|
||||
const entryPoint = moduleCompartment.evaluate(code, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
const moduleExports = {} as Record<string, any>;
|
||||
const setVarProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, p: string): any {
|
||||
return (newValue: any) => {
|
||||
moduleExports[p] = newValue;
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
entryPoint({
|
||||
imports: createImports(pluginName),
|
||||
liveVar: setVarProxy,
|
||||
onceVar: setVarProxy,
|
||||
});
|
||||
importLogger.debug('import', moduleName, exports, moduleExports);
|
||||
return moduleExports;
|
||||
};
|
||||
dynamicImportMap.set(pluginName, dynamicImport);
|
||||
return dynamicImport;
|
||||
};
|
||||
|
||||
const globalThisMap = new Map<string, any>();
|
||||
|
||||
const createOrGetGlobalThis = (
|
||||
pluginName: string,
|
||||
dynamicImport: (moduleName: string) => Promise<any>
|
||||
) => {
|
||||
if (globalThisMap.has(pluginName)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return globalThisMap.get(pluginName)!;
|
||||
}
|
||||
const pluginGlobalThis = Object.assign(
|
||||
Object.create(null),
|
||||
sharedGlobalThis,
|
||||
{
|
||||
// fixme: vite build output bundle will have this, we should remove it
|
||||
process: Object.freeze({
|
||||
env: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
},
|
||||
}),
|
||||
// dynamic import function
|
||||
[dynamicImportKey]: dynamicImport,
|
||||
// UNSAFE: React will read `window` and `document`
|
||||
window: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
permissionLogger.debug(`${pluginName} is accessing window`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(window, key);
|
||||
if (typeof result === 'function') {
|
||||
if (result === ShadowRoot) {
|
||||
return result;
|
||||
}
|
||||
return function (...args: any[]) {
|
||||
permissionLogger.debug(
|
||||
`${pluginName} is calling window`,
|
||||
key,
|
||||
args
|
||||
);
|
||||
return result.apply(window, args);
|
||||
};
|
||||
}
|
||||
permissionLogger.debug('window', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
document: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
permissionLogger.debug(
|
||||
`${pluginName} is accessing document`,
|
||||
key
|
||||
);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(document, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
permissionLogger.debug(
|
||||
`${pluginName} is calling window`,
|
||||
key,
|
||||
args
|
||||
);
|
||||
return result.apply(document, args);
|
||||
};
|
||||
}
|
||||
permissionLogger.debug('document', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
navigator: {
|
||||
userAgent: navigator.userAgent,
|
||||
},
|
||||
|
||||
MouseEvent: globalThis.MouseEvent,
|
||||
KeyboardEvent: globalThis.KeyboardEvent,
|
||||
CustomEvent: globalThis.CustomEvent,
|
||||
|
||||
// copilot uses these
|
||||
Date: globalThis.Date,
|
||||
Math: globalThis.Math,
|
||||
URL: globalThis.URL,
|
||||
URLSearchParams: globalThis.URLSearchParams,
|
||||
Headers: globalThis.Headers,
|
||||
TextEncoder: globalThis.TextEncoder,
|
||||
TextDecoder: globalThis.TextDecoder,
|
||||
Request: globalThis.Request,
|
||||
|
||||
// image-preview uses these
|
||||
Blob: globalThis.Blob,
|
||||
ClipboardItem: globalThis.ClipboardItem,
|
||||
|
||||
// vue uses these
|
||||
Element: globalThis.Element,
|
||||
SVGElement: globalThis.SVGElement,
|
||||
|
||||
// fixme: use our own db api
|
||||
indexedDB: globalThis.indexedDB,
|
||||
IDBRequest: globalThis.IDBRequest,
|
||||
IDBDatabase: globalThis.IDBDatabase,
|
||||
IDBCursorWithValue: globalThis.IDBCursorWithValue,
|
||||
IDBFactory: globalThis.IDBFactory,
|
||||
IDBKeyRange: globalThis.IDBKeyRange,
|
||||
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
|
||||
IDBTransaction: globalThis.IDBTransaction,
|
||||
IDBObjectStore: globalThis.IDBObjectStore,
|
||||
IDBIndex: globalThis.IDBIndex,
|
||||
IDBCursor: globalThis.IDBCursor,
|
||||
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
|
||||
}
|
||||
);
|
||||
pluginGlobalThis.global = pluginGlobalThis;
|
||||
globalThisMap.set(pluginName, pluginGlobalThis);
|
||||
return pluginGlobalThis;
|
||||
};
|
||||
|
||||
const setupPluginCode = async (
|
||||
baseUrl: string,
|
||||
pluginName: string,
|
||||
filename: string
|
||||
) => {
|
||||
await rootImportsMapSetupPromise;
|
||||
if (!_pluginNestedImportsMap.has(pluginName)) {
|
||||
_pluginNestedImportsMap.set(pluginName, new Map());
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
importLogger.debug('currentImportMap', pluginName, currentImportMap);
|
||||
const isMissingPackage = (name: string) =>
|
||||
_rootImportsMap.has(name) && !currentImportMap.has(name);
|
||||
|
||||
for (const [module, moduleUpdaters] of newUpdaters) {
|
||||
importLogger.debug('imports module', module, moduleUpdaters);
|
||||
let moduleImports = _rootImportsMap.get(module);
|
||||
if (!moduleImports) {
|
||||
moduleImports = currentImportMap.get(module);
|
||||
}
|
||||
if (moduleImports) {
|
||||
for (const [importName, importUpdaters] of moduleUpdaters) {
|
||||
const updateImport = (value: any) => {
|
||||
for (const importUpdater of importUpdaters) {
|
||||
importUpdater(value);
|
||||
}
|
||||
};
|
||||
if (moduleImports.has(importName)) {
|
||||
const val = moduleImports.get(importName);
|
||||
updateImport(val);
|
||||
}
|
||||
const bundleAnalysis = await fetch(`${baseUrl}/${filename}.json`).then(
|
||||
res => res.json()
|
||||
);
|
||||
const moduleExports = bundleAnalysis.exports as Record<string, [string]>;
|
||||
const moduleImports = bundleAnalysis.imports as string[];
|
||||
const moduleReexports = bundleAnalysis.reexports as Record<
|
||||
string,
|
||||
[localName: string, exportedName: string][]
|
||||
>;
|
||||
await Promise.all(
|
||||
moduleImports.map(name => {
|
||||
if (isMissingPackage(name)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
importLogger.debug('missing package', name);
|
||||
return setupPluginCode(baseUrl, pluginName, name);
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
'cannot find module in plugin import map',
|
||||
module,
|
||||
currentImportMap,
|
||||
_pluginNestedImportsMap
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
pluginImportsFunctionMap.set(pluginName, imports);
|
||||
return imports;
|
||||
};
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const pluginFetch = createFetch({});
|
||||
const timer = createTimers(abortController.signal);
|
||||
|
||||
const sharedGlobalThis = Object.assign(Object.create(null), timer, {
|
||||
Object: globalThis.Object,
|
||||
fetch: pluginFetch,
|
||||
ReadableStream: globalThis.ReadableStream,
|
||||
Symbol: globalThis.Symbol,
|
||||
Error: globalThis.Error,
|
||||
TypeError: globalThis.TypeError,
|
||||
RangeError: globalThis.RangeError,
|
||||
console: globalThis.console,
|
||||
crypto: globalThis.crypto,
|
||||
});
|
||||
|
||||
const dynamicImportMap = new Map<
|
||||
string,
|
||||
(moduleName: string) => Promise<any>
|
||||
>();
|
||||
|
||||
export const createOrGetDynamicImport = (
|
||||
baseUrl: string,
|
||||
pluginName: string
|
||||
) => {
|
||||
if (dynamicImportMap.has(pluginName)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return dynamicImportMap.get(pluginName)!;
|
||||
}
|
||||
const dynamicImport = async (moduleName: string): Promise<any> => {
|
||||
const codeUrl = `${baseUrl}/${moduleName}`;
|
||||
const analysisUrl = `${baseUrl}/${moduleName}.json`;
|
||||
const response = await fetch(codeUrl);
|
||||
const analysisResponse = await fetch(analysisUrl);
|
||||
const analysis = await analysisResponse.json();
|
||||
const exports = analysis.exports as string[];
|
||||
const code = await response.text();
|
||||
})
|
||||
);
|
||||
const code = await fetch(
|
||||
`${baseUrl}/${filename.replace(/^\.\//, '')}`
|
||||
).then(res => res.text());
|
||||
importLogger.debug('evaluating', filename);
|
||||
const moduleCompartment = new Compartment(
|
||||
createOrGetGlobalThis(
|
||||
pluginName,
|
||||
// use singleton here to avoid infinite loop
|
||||
createOrGetDynamicImport(pluginName, baseUrl)
|
||||
createOrGetDynamicImport(baseUrl, pluginName)
|
||||
)
|
||||
);
|
||||
const entryPoint = moduleCompartment.evaluate(code, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
const moduleExports = {} as Record<string, any>;
|
||||
const moduleExportsMap = new Map<string, any>();
|
||||
const setVarProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, p: string): any {
|
||||
return (newValue: any) => {
|
||||
moduleExports[p] = newValue;
|
||||
moduleExportsMap.set(p, newValue);
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
currentImportMap.set(filename, moduleExportsMap);
|
||||
entryPoint({
|
||||
imports: createImports(pluginName),
|
||||
liveVar: setVarProxy,
|
||||
onceVar: setVarProxy,
|
||||
});
|
||||
importLogger.debug('import', moduleName, exports, moduleExports);
|
||||
return moduleExports;
|
||||
|
||||
for (const [newExport, [originalExport]] of Object.entries(moduleExports)) {
|
||||
if (newExport === originalExport) continue;
|
||||
const value = moduleExportsMap.get(originalExport);
|
||||
moduleExportsMap.set(newExport, value);
|
||||
moduleExportsMap.delete(originalExport);
|
||||
}
|
||||
|
||||
for (const [name, reexports] of Object.entries(moduleReexports)) {
|
||||
const targetExports = currentImportMap.get(filename);
|
||||
const moduleExports = currentImportMap.get(name);
|
||||
assertExists(targetExports);
|
||||
assertExists(moduleExports);
|
||||
for (const [exportedName, localName] of reexports) {
|
||||
const exportedValue: any = moduleExports.get(exportedName);
|
||||
assertExists(exportedValue);
|
||||
targetExports.set(localName, exportedValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
dynamicImportMap.set(pluginName, dynamicImport);
|
||||
return dynamicImport;
|
||||
};
|
||||
|
||||
const globalThisMap = new Map<string, any>();
|
||||
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
createElement(
|
||||
Provider,
|
||||
{
|
||||
store: rootStore,
|
||||
},
|
||||
children
|
||||
);
|
||||
|
||||
export const createOrGetGlobalThis = (
|
||||
pluginName: string,
|
||||
dynamicImport: (moduleName: string) => Promise<any>
|
||||
) => {
|
||||
if (globalThisMap.has(pluginName)) {
|
||||
const evaluatePluginEntry = (pluginName: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return globalThisMap.get(pluginName)!;
|
||||
}
|
||||
const pluginGlobalThis = Object.assign(
|
||||
Object.create(null),
|
||||
sharedGlobalThis,
|
||||
{
|
||||
// fixme: vite build output bundle will have this, we should remove it
|
||||
process: Object.freeze({
|
||||
env: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
},
|
||||
}),
|
||||
// dynamic import function
|
||||
[dynamicImportKey]: dynamicImport,
|
||||
// UNSAFE: React will read `window` and `document`
|
||||
window: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
permissionLogger.debug(`${pluginName} is accessing window`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(window, key);
|
||||
if (typeof result === 'function') {
|
||||
if (result === ShadowRoot) {
|
||||
return result;
|
||||
}
|
||||
return function (...args: any[]) {
|
||||
permissionLogger.debug(
|
||||
`${pluginName} is calling window`,
|
||||
key,
|
||||
args
|
||||
);
|
||||
return result.apply(window, args);
|
||||
};
|
||||
}
|
||||
permissionLogger.debug('window', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
document: new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, key) {
|
||||
permissionLogger.debug(`${pluginName} is accessing document`, key);
|
||||
if (sharedGlobalThis[key]) return sharedGlobalThis[key];
|
||||
const result = Reflect.get(document, key);
|
||||
if (typeof result === 'function') {
|
||||
return function (...args: any[]) {
|
||||
permissionLogger.debug(
|
||||
`${pluginName} is calling window`,
|
||||
key,
|
||||
args
|
||||
);
|
||||
return result.apply(document, args);
|
||||
};
|
||||
}
|
||||
permissionLogger.debug('document', key, result);
|
||||
return result;
|
||||
},
|
||||
}
|
||||
),
|
||||
navigator: {
|
||||
userAgent: navigator.userAgent,
|
||||
},
|
||||
|
||||
MouseEvent: globalThis.MouseEvent,
|
||||
KeyboardEvent: globalThis.KeyboardEvent,
|
||||
CustomEvent: globalThis.CustomEvent,
|
||||
|
||||
// copilot uses these
|
||||
Date: globalThis.Date,
|
||||
Math: globalThis.Math,
|
||||
URL: globalThis.URL,
|
||||
URLSearchParams: globalThis.URLSearchParams,
|
||||
Headers: globalThis.Headers,
|
||||
TextEncoder: globalThis.TextEncoder,
|
||||
TextDecoder: globalThis.TextDecoder,
|
||||
Request: globalThis.Request,
|
||||
|
||||
// image-preview uses these
|
||||
Blob: globalThis.Blob,
|
||||
ClipboardItem: globalThis.ClipboardItem,
|
||||
|
||||
// vue uses these
|
||||
Element: globalThis.Element,
|
||||
SVGElement: globalThis.SVGElement,
|
||||
|
||||
// fixme: use our own db api
|
||||
indexedDB: globalThis.indexedDB,
|
||||
IDBRequest: globalThis.IDBRequest,
|
||||
IDBDatabase: globalThis.IDBDatabase,
|
||||
IDBCursorWithValue: globalThis.IDBCursorWithValue,
|
||||
IDBFactory: globalThis.IDBFactory,
|
||||
IDBKeyRange: globalThis.IDBKeyRange,
|
||||
IDBOpenDBRequest: globalThis.IDBOpenDBRequest,
|
||||
IDBTransaction: globalThis.IDBTransaction,
|
||||
IDBObjectStore: globalThis.IDBObjectStore,
|
||||
IDBIndex: globalThis.IDBIndex,
|
||||
IDBCursor: globalThis.IDBCursor,
|
||||
IDBVersionChangeEvent: globalThis.IDBVersionChangeEvent,
|
||||
}
|
||||
);
|
||||
pluginGlobalThis.global = pluginGlobalThis;
|
||||
globalThisMap.set(pluginName, pluginGlobalThis);
|
||||
return pluginGlobalThis;
|
||||
};
|
||||
|
||||
export const setupPluginCode = async (
|
||||
baseUrl: string,
|
||||
pluginName: string,
|
||||
filename: string
|
||||
) => {
|
||||
await rootImportsMapSetupPromise;
|
||||
if (!_pluginNestedImportsMap.has(pluginName)) {
|
||||
_pluginNestedImportsMap.set(pluginName, new Map());
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
const isMissingPackage = (name: string) =>
|
||||
_rootImportsMap.has(name) && !currentImportMap.has(name);
|
||||
|
||||
const bundleAnalysis = await fetch(`${baseUrl}/${filename}.json`).then(res =>
|
||||
res.json()
|
||||
);
|
||||
const moduleExports = bundleAnalysis.exports as Record<string, [string]>;
|
||||
const moduleImports = bundleAnalysis.imports as string[];
|
||||
const moduleReexports = bundleAnalysis.reexports as Record<
|
||||
string,
|
||||
[localName: string, exportedName: string][]
|
||||
>;
|
||||
await Promise.all(
|
||||
moduleImports.map(name => {
|
||||
if (isMissingPackage(name)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
importLogger.debug('missing package', name);
|
||||
return setupPluginCode(baseUrl, pluginName, name);
|
||||
}
|
||||
})
|
||||
);
|
||||
const code = await fetch(`${baseUrl}/${filename.replace(/^\.\//, '')}`).then(
|
||||
res => res.text()
|
||||
);
|
||||
importLogger.debug('evaluating', filename);
|
||||
const moduleCompartment = new Compartment(
|
||||
createOrGetGlobalThis(
|
||||
pluginName,
|
||||
// use singleton here to avoid infinite loop
|
||||
createOrGetDynamicImport(baseUrl, pluginName)
|
||||
)
|
||||
);
|
||||
const entryPoint = moduleCompartment.evaluate(code, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
const moduleExportsMap = new Map<string, any>();
|
||||
const setVarProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, p: string): any {
|
||||
return (newValue: any) => {
|
||||
moduleExportsMap.set(p, newValue);
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
currentImportMap.set(filename, moduleExportsMap);
|
||||
entryPoint({
|
||||
imports: createImports(pluginName),
|
||||
liveVar: setVarProxy,
|
||||
onceVar: setVarProxy,
|
||||
});
|
||||
|
||||
for (const [newExport, [originalExport]] of Object.entries(moduleExports)) {
|
||||
if (newExport === originalExport) continue;
|
||||
const value = moduleExportsMap.get(originalExport);
|
||||
moduleExportsMap.set(newExport, value);
|
||||
moduleExportsMap.delete(originalExport);
|
||||
}
|
||||
|
||||
for (const [name, reexports] of Object.entries(moduleReexports)) {
|
||||
const targetExports = currentImportMap.get(filename);
|
||||
const moduleExports = currentImportMap.get(name);
|
||||
assertExists(targetExports);
|
||||
assertExists(moduleExports);
|
||||
for (const [exportedName, localName] of reexports) {
|
||||
const exportedValue: any = moduleExports.get(exportedName);
|
||||
assertExists(exportedValue);
|
||||
targetExports.set(localName, exportedValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
createElement(
|
||||
Provider,
|
||||
{
|
||||
store: rootStore,
|
||||
},
|
||||
children
|
||||
);
|
||||
|
||||
const entryLogger = new DebugLogger('plugin:entry');
|
||||
|
||||
export const evaluatePluginEntry = (pluginName: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
const pluginExports = currentImportMap.get('index.js');
|
||||
assertExists(pluginExports);
|
||||
const entryFunction = pluginExports.get('entry');
|
||||
const cleanup = entryFunction(<PluginContext>{
|
||||
register: (part, callback) => {
|
||||
entryLogger.info(`Registering ${pluginName} to ${part}`);
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(pluginHeaderItemAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
addCleanup(pluginName, () => {
|
||||
rootStore.set(pluginHeaderItemAtom, items => {
|
||||
const newItems = { ...items };
|
||||
delete newItems[pluginName];
|
||||
return newItems;
|
||||
});
|
||||
});
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(pluginEditorAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
addCleanup(pluginName, () => {
|
||||
rootStore.set(pluginEditorAtom, items => {
|
||||
const newItems = { ...items };
|
||||
delete newItems[pluginName];
|
||||
return newItems;
|
||||
});
|
||||
});
|
||||
} else if (part === 'setting') {
|
||||
rootStore.set(pluginSettingAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['setting'],
|
||||
}));
|
||||
addCleanup(pluginName, () => {
|
||||
rootStore.set(pluginSettingAtom, items => {
|
||||
const newItems = { ...items };
|
||||
delete newItems[pluginName];
|
||||
return newItems;
|
||||
});
|
||||
});
|
||||
} else if (part === 'formatBar') {
|
||||
const register = (widget: AffineFormatBarWidget) => {
|
||||
const div = document.createElement('div');
|
||||
const root = widget.root;
|
||||
const cleanup = (callback as CallbackMap['formatBar'])(
|
||||
div,
|
||||
widget.page,
|
||||
() => {
|
||||
return root.selectionManager.value;
|
||||
}
|
||||
);
|
||||
const currentImportMap = _pluginNestedImportsMap.get(pluginName)!;
|
||||
const pluginExports = currentImportMap.get('index.js');
|
||||
assertExists(pluginExports);
|
||||
const entryFunction = pluginExports.get('entry');
|
||||
const cleanup = entryFunction(<PluginContext>{
|
||||
register: (part, callback) => {
|
||||
entryLogger.info(`Registering ${pluginName} to ${part}`);
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(pluginHeaderItemAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
addCleanup(pluginName, () => {
|
||||
AffineFormatBarWidget.customElements.delete(register);
|
||||
cleanup();
|
||||
rootStore.set(pluginHeaderItemAtom, items => {
|
||||
const newItems = { ...items };
|
||||
delete newItems[pluginName];
|
||||
return newItems;
|
||||
});
|
||||
});
|
||||
return div;
|
||||
};
|
||||
AffineFormatBarWidget.customElements.add(register);
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
});
|
||||
if (typeof cleanup !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
addCleanup(pluginName, cleanup);
|
||||
};
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(pluginEditorAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
addCleanup(pluginName, () => {
|
||||
rootStore.set(pluginEditorAtom, items => {
|
||||
const newItems = { ...items };
|
||||
delete newItems[pluginName];
|
||||
return newItems;
|
||||
});
|
||||
});
|
||||
} else if (part === 'setting') {
|
||||
rootStore.set(pluginSettingAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['setting'],
|
||||
}));
|
||||
addCleanup(pluginName, () => {
|
||||
rootStore.set(pluginSettingAtom, items => {
|
||||
const newItems = { ...items };
|
||||
delete newItems[pluginName];
|
||||
return newItems;
|
||||
});
|
||||
});
|
||||
} else if (part === 'formatBar') {
|
||||
const register = (widget: AffineFormatBarWidget) => {
|
||||
const div = document.createElement('div');
|
||||
const root = widget.root;
|
||||
const cleanup = (callback as CallbackMap['formatBar'])(
|
||||
div,
|
||||
widget.page,
|
||||
() => {
|
||||
return root.selectionManager.value;
|
||||
}
|
||||
);
|
||||
addCleanup(pluginName, () => {
|
||||
AffineFormatBarWidget.customElements.delete(register);
|
||||
cleanup();
|
||||
});
|
||||
return div;
|
||||
};
|
||||
AffineFormatBarWidget.customElements.add(register);
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
});
|
||||
if (typeof cleanup !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
addCleanup(pluginName, cleanup);
|
||||
};
|
||||
return {
|
||||
_rootImportsMap,
|
||||
_pluginNestedImportsMap,
|
||||
createImports,
|
||||
createOrGetDynamicImport,
|
||||
setupPluginCode,
|
||||
evaluatePluginEntry,
|
||||
createOrGetGlobalThis,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,11 +5,14 @@ import {
|
||||
invokeCleanup,
|
||||
pluginPackageJson,
|
||||
} from '@toeverything/infra/__internal__/plugin';
|
||||
import { loadedPluginNameAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import {
|
||||
getCurrentStore,
|
||||
loadedPluginNameAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { packageJsonOutputSchema } from '@toeverything/infra/type';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { evaluatePluginEntry, setupPluginCode } from './plugins/setup';
|
||||
import { createSetup } from './plugins/setup';
|
||||
|
||||
const logger = new DebugLogger('register-plugins');
|
||||
|
||||
@@ -20,106 +23,112 @@ declare global {
|
||||
|
||||
Object.defineProperty(globalThis, '__pluginPackageJson__', {
|
||||
get() {
|
||||
return rootStore.get(pluginPackageJson);
|
||||
return getCurrentStore().get(pluginPackageJson);
|
||||
},
|
||||
});
|
||||
|
||||
rootStore.sub(enabledPluginAtom, () => {
|
||||
const added = new Set<string>();
|
||||
const removed = new Set<string>();
|
||||
const enabledPlugin = new Set(rootStore.get(enabledPluginAtom));
|
||||
enabledPlugin.forEach(pluginName => {
|
||||
if (!enabledPluginSet.has(pluginName)) {
|
||||
added.add(pluginName);
|
||||
}
|
||||
export async function bootstrapPluginSystem(
|
||||
rootStore: ReturnType<typeof getCurrentStore>
|
||||
) {
|
||||
const { evaluatePluginEntry, setupPluginCode } = createSetup(rootStore);
|
||||
rootStore.sub(enabledPluginAtom, () => {
|
||||
const added = new Set<string>();
|
||||
const removed = new Set<string>();
|
||||
const enabledPlugin = new Set(rootStore.get(enabledPluginAtom));
|
||||
enabledPlugin.forEach(pluginName => {
|
||||
if (!enabledPluginSet.has(pluginName)) {
|
||||
added.add(pluginName);
|
||||
}
|
||||
});
|
||||
enabledPluginSet.forEach(pluginName => {
|
||||
if (!enabledPlugin.has(pluginName)) {
|
||||
removed.add(pluginName);
|
||||
}
|
||||
});
|
||||
// update plugins
|
||||
enabledPluginSet.clear();
|
||||
enabledPlugin.forEach(pluginName => {
|
||||
enabledPluginSet.add(pluginName);
|
||||
});
|
||||
added.forEach(pluginName => {
|
||||
evaluatePluginEntry(pluginName);
|
||||
});
|
||||
removed.forEach(pluginName => {
|
||||
invokeCleanup(pluginName);
|
||||
});
|
||||
});
|
||||
enabledPluginSet.forEach(pluginName => {
|
||||
if (!enabledPlugin.has(pluginName)) {
|
||||
removed.add(pluginName);
|
||||
}
|
||||
});
|
||||
// update plugins
|
||||
enabledPluginSet.clear();
|
||||
enabledPlugin.forEach(pluginName => {
|
||||
enabledPluginSet.add(pluginName);
|
||||
});
|
||||
added.forEach(pluginName => {
|
||||
evaluatePluginEntry(pluginName);
|
||||
});
|
||||
removed.forEach(pluginName => {
|
||||
invokeCleanup(pluginName);
|
||||
});
|
||||
});
|
||||
const enabledPluginSet = new Set(rootStore.get(enabledPluginAtom));
|
||||
const loadedAssets = new Set<string>();
|
||||
const enabledPluginSet = new Set(rootStore.get(enabledPluginAtom));
|
||||
const loadedAssets = new Set<string>();
|
||||
|
||||
// we will load all plugins in parallel from builtinPlugins
|
||||
export const pluginRegisterPromise = Promise.all(
|
||||
[...builtinPluginPaths].map(url => {
|
||||
return fetch(`${url}/package.json`)
|
||||
.then(async res => {
|
||||
const packageJson = (await res.json()) as z.infer<
|
||||
typeof packageJsonOutputSchema
|
||||
>;
|
||||
packageJsonOutputSchema.parse(packageJson);
|
||||
const {
|
||||
name: pluginName,
|
||||
affinePlugin: {
|
||||
release,
|
||||
entry: { core },
|
||||
assets,
|
||||
},
|
||||
} = packageJson;
|
||||
rootStore.set(pluginPackageJson, json => [...json, packageJson]);
|
||||
logger.debug(`registering plugin ${pluginName}`);
|
||||
logger.debug(`package.json: ${packageJson}`);
|
||||
if (!release && !runtimeConfig.enablePlugin) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const baseURL = url;
|
||||
const entryURL = `${baseURL}/${core}`;
|
||||
rootStore.set(loadedPluginNameAtom, prev => [...prev, pluginName]);
|
||||
await setupPluginCode(baseURL, pluginName, core);
|
||||
console.log(`prepareImports for ${pluginName} done`);
|
||||
await fetch(entryURL).then(async () => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async (asset: string) => {
|
||||
// todo(himself65): add assets into shadow dom
|
||||
if (loadedAssets.has(asset)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (asset.endsWith('.css')) {
|
||||
loadedAssets.add(asset);
|
||||
const res = await fetch(`${baseURL}/${asset}`);
|
||||
if (res.ok) {
|
||||
// todo: how to put css file into sandbox?
|
||||
return res.text().then(text => {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('plugin-id', pluginName);
|
||||
style.textContent = text;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
// we will load all plugins in parallel from builtinPlugins
|
||||
return Promise.all(
|
||||
[...builtinPluginPaths].map(url => {
|
||||
return fetch(`${url}/package.json`)
|
||||
.then(async res => {
|
||||
const packageJson = (await res.json()) as z.infer<
|
||||
typeof packageJsonOutputSchema
|
||||
>;
|
||||
packageJsonOutputSchema.parse(packageJson);
|
||||
const {
|
||||
name: pluginName,
|
||||
affinePlugin: {
|
||||
release,
|
||||
entry: { core },
|
||||
assets,
|
||||
},
|
||||
} = packageJson;
|
||||
rootStore.set(pluginPackageJson, json => [...json, packageJson]);
|
||||
logger.debug(`registering plugin ${pluginName}`);
|
||||
logger.debug(`package.json: ${packageJson}`);
|
||||
if (!release && !runtimeConfig.enablePlugin) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const baseURL = url;
|
||||
const entryURL = `${baseURL}/${core}`;
|
||||
rootStore.set(loadedPluginNameAtom, prev => [...prev, pluginName]);
|
||||
await setupPluginCode(baseURL, pluginName, core);
|
||||
console.log(`prepareImports for ${pluginName} done`);
|
||||
await fetch(entryURL).then(async () => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async (asset: string) => {
|
||||
const loadedAssetName = `${pluginName}_${asset}`;
|
||||
// todo(himself65): add assets into shadow dom
|
||||
if (loadedAssets.has(loadedAssetName)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!enabledPluginSet.has(pluginName)) {
|
||||
logger.debug(`plugin ${pluginName} is not enabled`);
|
||||
} else {
|
||||
logger.debug(`plugin ${pluginName} is enabled`);
|
||||
evaluatePluginEntry(pluginName);
|
||||
}
|
||||
if (asset.endsWith('.css')) {
|
||||
loadedAssets.add(loadedAssetName);
|
||||
const res = await fetch(`${baseURL}/${asset}`);
|
||||
if (res.ok) {
|
||||
// todo: how to put css file into sandbox?
|
||||
return res.text().then(text => {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('plugin-id', pluginName);
|
||||
style.textContent = text;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!enabledPluginSet.has(pluginName)) {
|
||||
logger.debug(`plugin ${pluginName} is not enabled`);
|
||||
} else {
|
||||
logger.debug(`plugin ${pluginName} is enabled`);
|
||||
evaluatePluginEntry(pluginName);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(`error when fetch plugin from ${url}`, e);
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(`error when fetch plugin from ${url}`, e);
|
||||
});
|
||||
})
|
||||
).then(() => {
|
||||
console.info('All plugins loaded');
|
||||
});
|
||||
})
|
||||
).then(() => {
|
||||
console.info('All plugins loaded');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
import type { createStore } from 'jotai/vanilla';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
@@ -143,7 +143,7 @@ async function tryMigration() {
|
||||
}
|
||||
}
|
||||
|
||||
function createFirstAppData() {
|
||||
function createFirstAppData(store: ReturnType<typeof createStore>) {
|
||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
@@ -166,11 +166,11 @@ function createFirstAppData() {
|
||||
const result = createFirst();
|
||||
console.info('create first workspace', result);
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
rootStore.set(rootWorkspacesMetadataAtom, result);
|
||||
store.set(rootWorkspacesMetadataAtom, result);
|
||||
}
|
||||
|
||||
export async function setup() {
|
||||
rootStore.set(
|
||||
export async function setup(store: ReturnType<typeof createStore>) {
|
||||
store.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
@@ -181,8 +181,8 @@ export async function setup() {
|
||||
console.log('setup global');
|
||||
setupGlobal();
|
||||
|
||||
createFirstAppData();
|
||||
createFirstAppData(store);
|
||||
await tryMigration();
|
||||
await rootStore.get(rootWorkspacesMetadataAtom);
|
||||
await store.get(rootWorkspacesMetadataAtom);
|
||||
console.log('setup done');
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
|
||||
|
||||
const blockSuiteWorkspace = getOrCreateWorkspace(
|
||||
'test',
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
|
||||
const page = blockSuiteWorkspace.createPage({ id: 'page0' });
|
||||
|
||||
const Editor = () => {
|
||||
const onLoad = useCallback((page: Page, editor: EditorContainer) => {
|
||||
// @ts-expect-error
|
||||
globalThis.page = page;
|
||||
// @ts-expect-error
|
||||
globalThis.editor = editor;
|
||||
return () => void 0;
|
||||
}, []);
|
||||
|
||||
if (!page) {
|
||||
return <>loading...</>;
|
||||
}
|
||||
return (
|
||||
<BlockSuiteEditor
|
||||
page={page}
|
||||
mode="page"
|
||||
onInit={initEmptyPage}
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
@@ -8,7 +8,7 @@ import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
getCurrentStore,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { Provider } from 'jotai/react';
|
||||
@@ -102,7 +102,7 @@ export class AffineErrorBoundary extends Component<
|
||||
return (
|
||||
<>
|
||||
{errorDetail}
|
||||
<Provider key="JotaiProvider" store={rootStore}>
|
||||
<Provider key="JotaiProvider" store={getCurrentStore()}>
|
||||
<DumpInfo />
|
||||
</Provider>
|
||||
</>
|
||||
|
||||
@@ -70,7 +70,7 @@ const NameWorkspaceContent = ({
|
||||
<Input
|
||||
ref={ref => {
|
||||
if (ref) {
|
||||
setTimeout(() => ref.focus(), 0);
|
||||
window.setTimeout(() => ref.focus(), 0);
|
||||
}
|
||||
}}
|
||||
data-testid="create-workspace-input"
|
||||
@@ -81,11 +81,7 @@ const NameWorkspaceContent = ({
|
||||
onChange={setWorkspaceName}
|
||||
/>
|
||||
<div className={style.buttonGroup}>
|
||||
<Button
|
||||
data-testid="create-workspace-close-button"
|
||||
type="primary"
|
||||
onClick={onClose}
|
||||
>
|
||||
<Button data-testid="create-workspace-close-button" onClick={onClose}>
|
||||
{t.Cancel()}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component';
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
type MenuProps,
|
||||
MenuTrigger,
|
||||
styled,
|
||||
} from '@affine/component';
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useI18N } from '@affine/i18n';
|
||||
import type { ButtonProps } from '@toeverything/components/button';
|
||||
@@ -6,7 +12,6 @@ import type { ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const StyledListItem = styled(MenuItem)(() => ({
|
||||
width: '132px',
|
||||
height: '38px',
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
@@ -46,11 +51,14 @@ const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface LanguageMenuProps {
|
||||
interface LanguageMenuProps extends Omit<MenuProps, 'children'> {
|
||||
triggerProps?: ButtonProps;
|
||||
}
|
||||
|
||||
export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
|
||||
export const LanguageMenu = ({
|
||||
triggerProps,
|
||||
...menuProps
|
||||
}: LanguageMenuProps) => {
|
||||
const i18n = useI18N();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
@@ -67,6 +75,7 @@ export const LanguageMenu = ({ triggerProps }: LanguageMenuProps) => {
|
||||
placement="bottom-end"
|
||||
trigger="click"
|
||||
disablePortal={true}
|
||||
{...menuProps}
|
||||
>
|
||||
<MenuTrigger
|
||||
data-testid="language-menu-button"
|
||||
|
||||
@@ -82,7 +82,7 @@ export const WorkspaceDeleteModal = ({
|
||||
<Input
|
||||
ref={ref => {
|
||||
if (ref) {
|
||||
setTimeout(() => ref.focus(), 0);
|
||||
window.setTimeout(() => ref.focus(), 0);
|
||||
}
|
||||
}}
|
||||
onChange={setDeleteStr}
|
||||
|
||||
@@ -30,11 +30,11 @@ export const DeleteLeaveWorkspace = ({
|
||||
name={
|
||||
<span style={{ color: 'var(--affine-error-color)' }}>
|
||||
{isOwner
|
||||
? t['com.affine.settings.workspace.remove']()
|
||||
? t['com.affine.settings.remove-workspace']()
|
||||
: t['Leave Workspace']()}
|
||||
</span>
|
||||
}
|
||||
desc={t['com.affine.settings.workspace.remove.message']()}
|
||||
desc={t['com.affine.settings.remove-workspace-description']()}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setShowDelete(true);
|
||||
|
||||
@@ -38,9 +38,8 @@ export const ExportPanel = ({ workspace }: ExportPanelProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const onExport = useCallback(async () => {
|
||||
await syncBlobsToSqliteDb(workspace);
|
||||
const result: SaveDBFileResult = await window.apis?.dialog.saveDBFileAs(
|
||||
workspaceId
|
||||
);
|
||||
const result: SaveDBFileResult =
|
||||
await window.apis?.dialog.saveDBFileAs(workspaceId);
|
||||
if (result?.error) {
|
||||
toast(t[result.error]());
|
||||
} else if (!result?.canceled) {
|
||||
|
||||
@@ -59,14 +59,12 @@ export const WorkspaceSettingDetail = ({
|
||||
<>
|
||||
<SettingHeader
|
||||
title={t[`Workspace Settings with name`]({ name })}
|
||||
subtitle={t['You can customize your workspace here.']()}
|
||||
subtitle={t['com.affine.settings.workspace.description']()}
|
||||
/>
|
||||
<SettingWrapper title={t['Info']()}>
|
||||
<SettingRow
|
||||
name={t['Workspace Profile']()}
|
||||
desc={t[
|
||||
'Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.'
|
||||
]()}
|
||||
desc={t['com.affine.settings.workspace.not-owner']()}
|
||||
spreadCol={false}
|
||||
>
|
||||
<ProfilePanel workspace={workspace} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Input, toast } from '@affine/component';
|
||||
import { FlexWrapper, Input, toast, Wrapper } from '@affine/component';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DoneIcon } from '@blocksuite/icons';
|
||||
import { CameraIcon, DoneIcon } from '@blocksuite/icons';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
@@ -11,25 +11,6 @@ import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import { Upload } from '../../pure/file-upload';
|
||||
import * as style from './style.css';
|
||||
|
||||
const CameraIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.6236 4.25001C10.635 4.25001 10.6467 4.25002 10.6584 4.25002H13.3416C13.3533 4.25002 13.365 4.25001 13.3764 4.25001C13.5609 4.24995 13.7105 4.2499 13.8543 4.26611C14.5981 4.34997 15.2693 4.75627 15.6826 5.38026C15.7624 5.50084 15.83 5.63398 15.9121 5.79586C15.9173 5.80613 15.9226 5.81652 15.9279 5.82703C15.9538 5.87792 15.9679 5.90562 15.9789 5.9261C15.9832 5.9341 15.9857 5.93861 15.9869 5.94065C16.0076 5.97069 16.0435 5.99406 16.0878 5.99905L16.0849 5.99877C16.0849 5.99877 16.0907 5.99918 16.1047 5.99947C16.1286 5.99998 16.1604 6.00002 16.2181 6.00002L17.185 6.00001C17.6577 6 18.0566 5.99999 18.3833 6.02627C18.7252 6.05377 19.0531 6.11364 19.3656 6.27035C19.8402 6.50842 20.2283 6.88944 20.4723 7.36077C20.6336 7.67233 20.6951 7.99944 20.7232 8.33858C20.75 8.66166 20.75 9.05554 20.75 9.51992V16.2301C20.75 16.6945 20.75 17.0884 20.7232 17.4114C20.6951 17.7506 20.6336 18.0777 20.4723 18.3893C20.2283 18.8606 19.8402 19.2416 19.3656 19.4797C19.0531 19.6364 18.7252 19.6963 18.3833 19.7238C18.0566 19.75 17.6578 19.75 17.185 19.75H6.81497C6.34225 19.75 5.9434 19.75 5.61668 19.7238C5.27477 19.6963 4.94688 19.6364 4.63444 19.4797C4.15978 19.2416 3.77167 18.8606 3.52771 18.3893C3.36644 18.0777 3.30494 17.7506 3.27679 17.4114C3.24998 17.0884 3.24999 16.6945 3.25 16.2302V9.51987C3.24999 9.05551 3.24998 8.66164 3.27679 8.33858C3.30494 7.99944 3.36644 7.67233 3.52771 7.36077C3.77167 6.88944 4.15978 6.50842 4.63444 6.27035C4.94688 6.11364 5.27477 6.05377 5.61668 6.02627C5.9434 5.99999 6.34225 6 6.81498 6.00001L7.78191 6.00002C7.83959 6.00002 7.87142 5.99998 7.8953 5.99947C7.90607 5.99924 7.91176 5.99897 7.91398 5.99884C7.95747 5.99343 7.99267 5.9703 8.01312 5.94066C8.01429 5.93863 8.01684 5.93412 8.02113 5.9261C8.0321 5.90561 8.04622 5.87791 8.07206 5.82703C8.07739 5.81653 8.08266 5.80615 8.08787 5.79588C8.17004 5.63397 8.23759 5.50086 8.31745 5.38026C8.73067 4.75627 9.40192 4.34997 10.1457 4.26611C10.2895 4.2499 10.4391 4.24995 10.6236 4.25001ZM10.6584 5.75002C10.422 5.75002 10.3627 5.75114 10.3138 5.75666C10.0055 5.79142 9.73316 5.95919 9.56809 6.20845C9.54218 6.24758 9.51544 6.29761 9.40943 6.50633C9.40611 6.51287 9.40274 6.5195 9.39934 6.52622C9.36115 6.60161 9.31758 6.68761 9.26505 6.76694C8.9964 7.17261 8.56105 7.4354 8.08026 7.48961C7.98625 7.50021 7.89021 7.50011 7.80434 7.50003C7.79678 7.50002 7.7893 7.50002 7.78191 7.50002H6.84445C6.33444 7.50002 5.99634 7.50058 5.73693 7.52144C5.48594 7.54163 5.37478 7.57713 5.30693 7.61115C5.11257 7.70864 4.95675 7.86306 4.85983 8.05029C4.82733 8.11308 4.79194 8.21816 4.77165 8.46266C4.7506 8.71626 4.75 9.0474 4.75 9.55001V16.2C4.75 16.7026 4.7506 17.0338 4.77165 17.2874C4.79194 17.5319 4.82733 17.6369 4.85983 17.6997C4.95675 17.887 5.11257 18.0414 5.30693 18.1389C5.37478 18.1729 5.48594 18.2084 5.73693 18.2286C5.99634 18.2494 6.33444 18.25 6.84445 18.25H17.1556C17.6656 18.25 18.0037 18.2494 18.2631 18.2286C18.5141 18.2084 18.6252 18.1729 18.6931 18.1389C18.8874 18.0414 19.0433 17.887 19.1402 17.6997C19.1727 17.6369 19.2081 17.5319 19.2283 17.2874C19.2494 17.0338 19.25 16.7026 19.25 16.2V9.55001C19.25 9.0474 19.2494 8.71626 19.2283 8.46266C19.2081 8.21816 19.1727 8.11308 19.1402 8.05029C19.0433 7.86306 18.8874 7.70864 18.6931 7.61115C18.6252 7.57713 18.5141 7.54163 18.2631 7.52144C18.0037 7.50058 17.6656 7.50002 17.1556 7.50002H16.2181C16.2107 7.50002 16.2032 7.50002 16.1957 7.50003C16.1098 7.50011 16.0138 7.50021 15.9197 7.48961C15.4389 7.4354 15.0036 7.17261 14.735 6.76694C14.6824 6.68761 14.6389 6.60163 14.6007 6.52622C14.5973 6.5195 14.5939 6.51287 14.5906 6.50633C14.4846 6.29763 14.4578 6.24758 14.4319 6.20846C14.2668 5.95919 13.9945 5.79142 13.6862 5.75666C13.6373 5.75114 13.578 5.75002 13.3416 5.75002H10.6584ZM12 11C10.9303 11 10.0833 11.8506 10.0833 12.875C10.0833 13.8995 10.9303 14.75 12 14.75C13.0697 14.75 13.9167 13.8995 13.9167 12.875C13.9167 11.8506 13.0697 11 12 11ZM8.58333 12.875C8.58333 11 10.1242 9.50002 12 9.50002C13.8758 9.50002 15.4167 11 15.4167 12.875C15.4167 14.7501 13.8758 16.25 12 16.25C10.1242 16.25 8.58333 14.7501 8.58333 12.875Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
interface ProfilePanelProps {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}
|
||||
@@ -74,32 +55,35 @@ export const ProfilePanel = ({ workspace }: ProfilePanelProps) => {
|
||||
</>
|
||||
</Upload>
|
||||
</div>
|
||||
<div className={style.profileHandlerWrapper}>
|
||||
<Input
|
||||
width={280}
|
||||
height={32}
|
||||
defaultValue={input}
|
||||
data-testid="workspace-name-input"
|
||||
placeholder={t['Workspace Name']()}
|
||||
maxLength={64}
|
||||
minLength={0}
|
||||
onChange={setInput}
|
||||
/>
|
||||
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
|
||||
<IconButton
|
||||
data-testid="save-workspace-name"
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName(input);
|
||||
}}
|
||||
active={true}
|
||||
style={{
|
||||
marginLeft: '12px',
|
||||
}}
|
||||
>
|
||||
<DoneIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
<Wrapper marginLeft={20}>
|
||||
<div className={style.label}>{t['Workspace Name']()}</div>
|
||||
<FlexWrapper alignItems="center" flexGrow="1">
|
||||
<Input
|
||||
width={280}
|
||||
height={32}
|
||||
defaultValue={input}
|
||||
data-testid="workspace-name-input"
|
||||
placeholder={t['Workspace Name']()}
|
||||
maxLength={64}
|
||||
minLength={0}
|
||||
onChange={setInput}
|
||||
/>
|
||||
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
|
||||
<IconButton
|
||||
data-testid="save-workspace-name"
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName(input);
|
||||
}}
|
||||
active={true}
|
||||
style={{
|
||||
marginLeft: '12px',
|
||||
}}
|
||||
>
|
||||
<DoneIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</FlexWrapper>
|
||||
</Wrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -94,7 +94,7 @@ const FakePublishPanelAffine = (_props: FakePublishPanelAffineProps) => {
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={t['com.affine.settings.workspace.publish.local-tooltip']()}
|
||||
content={t['com.affine.settings.workspace.publish-tooltip']()}
|
||||
placement="top"
|
||||
>
|
||||
<div className={style.fakeWrapper}>
|
||||
|
||||
@@ -5,12 +5,6 @@ export const profileWrapper = style({
|
||||
alignItems: 'flex-end',
|
||||
marginTop: '12px',
|
||||
});
|
||||
export const profileHandlerWrapper = style({
|
||||
flexGrow: '1',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '20px',
|
||||
});
|
||||
|
||||
export const avatarWrapper = style({
|
||||
width: '56px',
|
||||
@@ -39,6 +33,8 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(60, 61, 63, 0.5)',
|
||||
zIndex: '1',
|
||||
color: 'var(--affine-white)',
|
||||
fontSize: '24px',
|
||||
});
|
||||
|
||||
export const urlButton = style({
|
||||
@@ -71,3 +67,9 @@ export const fakeWrapper = style({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const label = style({
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
marginBottom: '5px',
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ export const AboutAffine = () => {
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t[`Discover what's new`]()}
|
||||
desc={t['View the AFFiNE Changelog.']()}
|
||||
desc={t['Changelog description']()}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||
|
||||
@@ -35,10 +35,10 @@ export const ThemeSettings = () => {
|
||||
<RadioButton value="system" data-testid="system-theme-trigger">
|
||||
{t['system']()}
|
||||
</RadioButton>
|
||||
<RadioButton bold={true} value="light" data-testid="light-theme-trigger">
|
||||
<RadioButton value="light" data-testid="light-theme-trigger">
|
||||
{t['light']()}
|
||||
</RadioButton>
|
||||
<RadioButton bold={true} value="dark" data-testid="dark-theme-trigger">
|
||||
<RadioButton value="dark" data-testid="dark-theme-trigger">
|
||||
{t['dark']()}
|
||||
</RadioButton>
|
||||
</RadioButtonGroup>
|
||||
@@ -63,7 +63,6 @@ const FontFamilySettings = () => {
|
||||
return (
|
||||
<RadioButton
|
||||
key={key}
|
||||
bold={true}
|
||||
value={key}
|
||||
data-testid="system-font-style-trigger"
|
||||
style={{
|
||||
@@ -110,16 +109,28 @@ export const AppearanceSettings = () => {
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['Display Language']()}
|
||||
desc={t['Select the language for the interface.']()}
|
||||
desc={t['com.affine.settings.appearance.language-description']()}
|
||||
>
|
||||
<div className={settingWrapper}>
|
||||
<LanguageMenu />
|
||||
<LanguageMenu
|
||||
triggerContainerStyle={{ width: '100%' }}
|
||||
triggerProps={{
|
||||
style: {
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
fontWeight: 600,
|
||||
padding: '0 10px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SettingRow>
|
||||
{environment.isDesktop ? (
|
||||
<SettingRow
|
||||
name={t['Client Border Style']()}
|
||||
desc={t['Customize the appearance of the client.']()}
|
||||
desc={t[
|
||||
'com.affine.settings.appearance.border-style-description'
|
||||
]()}
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.clientBorder}
|
||||
@@ -130,7 +141,7 @@ export const AppearanceSettings = () => {
|
||||
|
||||
<SettingRow
|
||||
name={t['Full width Layout']()}
|
||||
desc={t['Maximum display of content within a page.']()}
|
||||
desc={t['com.affine.settings.appearance.full-width-description']()}
|
||||
>
|
||||
<Switch
|
||||
data-testid="full-width-layout-trigger"
|
||||
@@ -141,7 +152,9 @@ export const AppearanceSettings = () => {
|
||||
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
|
||||
<SettingRow
|
||||
name={t['Window frame style']()}
|
||||
desc={t['Customize appearance of Windows Client.']()}
|
||||
desc={t[
|
||||
'com.affine.settings.appearance.window-frame-description'
|
||||
]()}
|
||||
>
|
||||
<RadioButtonGroup
|
||||
className={settingWrapper}
|
||||
@@ -166,7 +179,7 @@ export const AppearanceSettings = () => {
|
||||
<SettingWrapper title={t['Date']()}>
|
||||
<SettingRow
|
||||
name={t['Date Format']()}
|
||||
desc={t['Customize your date style.']()}
|
||||
desc={t['com.affine.settings.appearance.date-format-description']()}
|
||||
>
|
||||
<div className={settingWrapper}>
|
||||
<DateFormatSetting />
|
||||
@@ -174,7 +187,7 @@ export const AppearanceSettings = () => {
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['Start Week On Monday']()}
|
||||
desc={t['By default, the week starts on Sunday.']()}
|
||||
desc={t['com.affine.settings.appearance.start-week-description']()}
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.startWeekOnMonday}
|
||||
@@ -187,8 +200,8 @@ export const AppearanceSettings = () => {
|
||||
{environment.isDesktop ? (
|
||||
<SettingWrapper title={t['Sidebar']()}>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.appearance.sidebar.noise']()}
|
||||
desc={t['com.affine.settings.appearance.sidebar.noise.message']()}
|
||||
name={t['com.affine.settings.noise-style']()}
|
||||
desc={t['com.affine.settings.noise-style-description']()}
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.enableNoisyBackground}
|
||||
@@ -198,10 +211,8 @@ export const AppearanceSettings = () => {
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
name={t['com.affine.settings.appearance.sidebar.translucent']()}
|
||||
desc={t[
|
||||
'com.affine.settings.appearance.sidebar.translucent.message'
|
||||
]()}
|
||||
name={t['com.affine.settings.translucent-style']()}
|
||||
desc={t['com.affine.settings.translucent-style-description']()}
|
||||
>
|
||||
<Switch
|
||||
checked={appSettings.enableBlurBackground}
|
||||
|
||||
@@ -3,20 +3,48 @@ import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import {
|
||||
type ShortcutsInfo,
|
||||
useEdgelessShortcuts,
|
||||
useGeneralShortcuts,
|
||||
useMarkdownShortcuts,
|
||||
usePageShortcuts,
|
||||
} from '../../../../../hooks/affine/use-shortcuts';
|
||||
import { shortcutRow } from './style.css';
|
||||
import { shortcutKey, shortcutKeyContainer, shortcutRow } from './style.css';
|
||||
|
||||
const ShortcutsPanel = ({
|
||||
shortcutsInfo,
|
||||
}: {
|
||||
shortcutsInfo: ShortcutsInfo;
|
||||
}) => {
|
||||
return (
|
||||
<SettingWrapper title={shortcutsInfo.title}>
|
||||
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
<span>{title}</span>
|
||||
<div className={shortcutKeyContainer}>
|
||||
{shortcuts.map(key => {
|
||||
return (
|
||||
<span className={shortcutKey} key={key}>
|
||||
{key}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SettingWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const Shortcuts = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const markdownShortcuts = useMarkdownShortcuts();
|
||||
const pageShortcuts = usePageShortcuts();
|
||||
const edgelessShortcuts = useEdgelessShortcuts();
|
||||
const generalShortcuts = useGeneralShortcuts();
|
||||
const markdownShortcutsInfo = useMarkdownShortcuts();
|
||||
const pageShortcutsInfo = usePageShortcuts();
|
||||
const edgelessShortcutsInfo = useEdgelessShortcuts();
|
||||
const generalShortcutsInfo = useGeneralShortcuts();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -25,46 +53,10 @@ export const Shortcuts = () => {
|
||||
subtitle={t['Check Keyboard Shortcuts quickly']()}
|
||||
data-testid="keyboard-shortcuts-title"
|
||||
/>
|
||||
<SettingWrapper title={t['General']()}>
|
||||
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
<span>{title}</span>
|
||||
<span className="shortcut">{shortcuts}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Page']()}>
|
||||
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
<span>{title}</span>
|
||||
<span className="shortcut">{shortcuts}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Edgeless']()}>
|
||||
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
<span>{title}</span>
|
||||
<span className="shortcut">{shortcuts}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Markdown Syntax']()}>
|
||||
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
<span>{title}</span>
|
||||
<span className="shortcut">{shortcuts}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SettingWrapper>
|
||||
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const shortcutRow = style({
|
||||
height: '32px',
|
||||
@@ -14,8 +14,25 @@ export const shortcutRow = style({
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle(`${shortcutRow} .shortcut`, {
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
borderRadius: '8px',
|
||||
padding: '4px 18px',
|
||||
export const shortcutKeyContainer = style({
|
||||
display: 'flex',
|
||||
});
|
||||
export const shortcutKey = style({
|
||||
minWidth: '24px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '0 6px',
|
||||
border: '1px solid var(--affine-border-color)',
|
||||
borderRadius: '4px',
|
||||
background: 'var(--affine-background-tertiary-color)',
|
||||
boxShadow:
|
||||
'0px 6px 4px 0px rgba(255, 255, 255, 0.24) inset, 0px 0px 0px 0.5px rgba(0, 0, 0, 0.10) inset',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
selectors: {
|
||||
'&:not(:last-of-type)': {
|
||||
marginRight: '2px',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {
|
||||
SettingModal as SettingModalBase,
|
||||
type SettingModalProps as SettingModalBaseProps,
|
||||
WorkspaceDetailSkeleton,
|
||||
} from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ContactWithUsIcon } from '@blocksuite/icons';
|
||||
import { useCallback } from 'react';
|
||||
import { Suspense, useCallback } from 'react';
|
||||
|
||||
import { AccountSetting } from './account-setting';
|
||||
import {
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
useGeneralSettingList,
|
||||
} from './general-setting';
|
||||
import { SettingSidebar } from './setting-sidebar';
|
||||
import { settingContent } from './style.css';
|
||||
import { footerIconWrapper, settingContent } from './style.css';
|
||||
import { WorkspaceSetting } from './workspace-setting';
|
||||
|
||||
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
|
||||
@@ -77,7 +78,9 @@ export const SettingModal = ({
|
||||
<div className="wrapper">
|
||||
<div className="content">
|
||||
{activeTab === 'workspace' && workspaceId ? (
|
||||
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
|
||||
<Suspense fallback={<WorkspaceDetailSkeleton />}>
|
||||
<WorkspaceSetting key={workspaceId} workspaceId={workspaceId} />
|
||||
</Suspense>
|
||||
) : null}
|
||||
{generalSettingList.find(v => v.key === activeTab) ? (
|
||||
<GeneralSetting generalKey={activeTab as GeneralSettingKeys} />
|
||||
@@ -85,15 +88,15 @@ export const SettingModal = ({
|
||||
{activeTab === 'account' ? <AccountSetting /> : null}
|
||||
</div>
|
||||
<div className="footer">
|
||||
<ContactWithUsIcon />
|
||||
<div className={footerIconWrapper}>
|
||||
<ContactWithUsIcon />
|
||||
</div>
|
||||
<a
|
||||
href="https://community.affine.pro/home"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t[
|
||||
'Need more customization options? You can suggest them to us in the community.'
|
||||
]()}
|
||||
{t['com.affine.settings.suggestion']()}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,16 +28,18 @@ globalStyle(`${settingContent} .footer`, {
|
||||
marginTop: '-80px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
display: 'flex',
|
||||
minHeight: '100px',
|
||||
});
|
||||
|
||||
globalStyle(`${settingContent} .footer a`, {
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
lineHeight: 'normal',
|
||||
});
|
||||
|
||||
globalStyle(`${settingContent} .footer > svg`, {
|
||||
export const footerIconWrapper = style({
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
color: 'var(--affine-icon-color)',
|
||||
marginRight: '12px',
|
||||
marginTop: '1px',
|
||||
height: '19px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { WorkspaceDetailSkeleton } from '@affine/component/setting-components';
|
||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { Suspense, useCallback } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { getUIAdapter } from '../../../../adapters/workspace';
|
||||
import { openSettingModalAtom } from '../../../../atoms';
|
||||
@@ -33,12 +32,10 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
|
||||
const onTransformWorkspace = useOnTransformWorkspace();
|
||||
|
||||
return (
|
||||
<Suspense fallback={<WorkspaceDetailSkeleton />}>
|
||||
<NewSettingsDetail
|
||||
onTransformWorkspace={onTransformWorkspace}
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
currentWorkspaceId={workspaceId}
|
||||
/>
|
||||
</Suspense>
|
||||
<NewSettingsDetail
|
||||
onTransformWorkspace={onTransformWorkspace}
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
currentWorkspaceId={workspaceId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
>
|
||||
{t['com.affine.header.option.add-tag']()}
|
||||
</MenuItem> */}
|
||||
<Divider />
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
<MenuItem
|
||||
icon={<DuplicateIcon />}
|
||||
data-testid="editor-option-menu-duplicate"
|
||||
@@ -162,7 +162,7 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
|
||||
{t['Import']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
<Divider />
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
<MoveToTrash
|
||||
data-testid="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
import { type ComplexStyleRule, style } from '@vanilla-extract/css';
|
||||
|
||||
export const headerTitleContainer = style({
|
||||
display: 'flex',
|
||||
@@ -10,11 +10,11 @@ export const headerTitleContainer = style({
|
||||
});
|
||||
|
||||
export const titleEditButton = style({
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
WebkitAppRegion: 'no-drag',
|
||||
} as ComplexStyleRule);
|
||||
|
||||
export const titleInput = style({
|
||||
position: 'absolute',
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { HTMLAttributes } from 'react';
|
||||
import type React from 'react';
|
||||
import { cloneElement, useState } from 'react';
|
||||
|
||||
import edgelessHover from './animation-data/edgeless-hover.json';
|
||||
import pageHover from './animation-data/page-hover.json';
|
||||
import { StyledSwitchItem } from './style';
|
||||
|
||||
type HoverAnimateControllerProps = {
|
||||
@@ -52,7 +54,7 @@ export const PageSwitchItem = (
|
||||
options={{
|
||||
loop: false,
|
||||
autoplay: false,
|
||||
animationData: require('./animation-data/page-hover.json'),
|
||||
animationData: pageHover,
|
||||
rendererSettings: {
|
||||
preserveAspectRatio: 'xMidYMid slice',
|
||||
},
|
||||
@@ -71,7 +73,7 @@ export const EdgelessSwitchItem = (
|
||||
options={{
|
||||
loop: false,
|
||||
autoplay: false,
|
||||
animationData: require('./animation-data/edgeless-hover.json'),
|
||||
animationData: edgelessHover,
|
||||
rendererSettings: {
|
||||
preserveAspectRatio: 'xMidYMid slice',
|
||||
},
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
@@ -8,7 +10,7 @@ import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
|
||||
export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
|
||||
const { openPage } = useNavigateHelper();
|
||||
const { openPage, jumpToSubPath } = useNavigateHelper();
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const pageSettings = useAtomValue(pageSettingsAtom);
|
||||
const isPreferredEdgeless = useCallback(
|
||||
@@ -20,9 +22,7 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
|
||||
(id?: string, mode?: 'page' | 'edgeless') => {
|
||||
const page = createPage(id);
|
||||
initEmptyPage(page); // we don't need to wait it to be loaded right?
|
||||
if (mode) {
|
||||
setPageMode(page.id, mode);
|
||||
}
|
||||
setPageMode(page.id, mode || 'page');
|
||||
openPage(blockSuiteWorkspace.id, page.id);
|
||||
},
|
||||
[blockSuiteWorkspace.id, createPage, openPage, setPageMode]
|
||||
@@ -35,8 +35,25 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
|
||||
);
|
||||
const importFileAndOpen = useCallback(async () => {
|
||||
const { showImportModal } = await import('@blocksuite/blocks');
|
||||
showImportModal({ workspace: blockSuiteWorkspace });
|
||||
}, [blockSuiteWorkspace]);
|
||||
const onSuccess = (pageIds: string[], isWorkspaceFile: boolean) => {
|
||||
toast(
|
||||
`Successfully imported ${pageIds.length} Page${
|
||||
pageIds.length > 1 ? 's' : ''
|
||||
}.`
|
||||
);
|
||||
if (isWorkspaceFile) {
|
||||
jumpToSubPath(blockSuiteWorkspace.id, WorkspaceSubPath.ALL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
const pageId = pageIds[0];
|
||||
openPage(blockSuiteWorkspace.id, pageId);
|
||||
};
|
||||
showImportModal({ workspace: blockSuiteWorkspace, onSuccess });
|
||||
}, [blockSuiteWorkspace, openPage, jumpToSubPath]);
|
||||
return {
|
||||
createPage: createPageAndOpen,
|
||||
createEdgeless: createEdgelessAndOpen,
|
||||
|
||||
@@ -13,11 +13,19 @@ import {
|
||||
pluginEditorAtom,
|
||||
pluginWindowAtom,
|
||||
} from '@toeverything/infra/__internal__/plugin';
|
||||
import { contentLayoutAtom, rootStore } from '@toeverything/infra/atom';
|
||||
import { contentLayoutAtom, getCurrentStore } from '@toeverything/infra/atom';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { CSSProperties, ReactElement } from 'react';
|
||||
import { memo, Suspense, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
memo,
|
||||
startTransition,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import { pageSettingFamily } from '../atoms';
|
||||
@@ -95,9 +103,10 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
if (onLoad) {
|
||||
dispose = onLoad(page, editor);
|
||||
}
|
||||
const rootStore = getCurrentStore();
|
||||
const editorItems = rootStore.get(pluginEditorAtom);
|
||||
let disposes: (() => void)[] = [];
|
||||
const renderTimeout = setTimeout(() => {
|
||||
const renderTimeout = window.setTimeout(() => {
|
||||
disposes = Object.entries(editorItems).map(([id, editorItem]) => {
|
||||
const div = document.createElement('div');
|
||||
div.setAttribute('plugin-id', id);
|
||||
@@ -114,7 +123,7 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
return () => {
|
||||
dispose();
|
||||
clearTimeout(renderTimeout);
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
disposes.forEach(dispose => dispose());
|
||||
});
|
||||
};
|
||||
@@ -134,25 +143,42 @@ interface PluginContentAdapterProps {
|
||||
|
||||
const PluginContentAdapter = memo<PluginContentAdapterProps>(
|
||||
function PluginContentAdapter({ windowItem, pluginName }) {
|
||||
return (
|
||||
<div
|
||||
className={pluginContainer}
|
||||
ref={useCallback(
|
||||
(ref: HTMLDivElement | null) => {
|
||||
if (ref) {
|
||||
const div = document.createElement('div');
|
||||
const cleanup = windowItem(div);
|
||||
ref.appendChild(div);
|
||||
addCleanup(pluginName, () => {
|
||||
cleanup();
|
||||
ref.removeChild(div);
|
||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController();
|
||||
const root = rootRef.current;
|
||||
if (root) {
|
||||
startTransition(() => {
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
const div = document.createElement('div');
|
||||
const cleanup = windowItem(div);
|
||||
root.appendChild(div);
|
||||
if (abortController.signal.aborted) {
|
||||
cleanup();
|
||||
root.removeChild(div);
|
||||
} else {
|
||||
const cl = () => {
|
||||
cleanup();
|
||||
root.removeChild(div);
|
||||
};
|
||||
const dispose = addCleanup(pluginName, cl);
|
||||
abortController.signal.addEventListener('abort', () => {
|
||||
window.setTimeout(() => {
|
||||
dispose();
|
||||
cl();
|
||||
});
|
||||
}
|
||||
},
|
||||
[pluginName, windowItem]
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}
|
||||
return;
|
||||
}, [pluginName, windowItem]);
|
||||
return <div className={pluginContainer} ref={rootRef} />;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -175,13 +201,13 @@ const LayoutPanel = memo(function LayoutPanel(
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<PanelGroup
|
||||
style={{
|
||||
height: 'calc(100% - 52px)',
|
||||
}}
|
||||
direction={node.direction}
|
||||
>
|
||||
<Panel defaultSize={node.splitPercentage}>
|
||||
<PanelGroup direction={node.direction}>
|
||||
<Panel
|
||||
defaultSize={node.splitPercentage}
|
||||
style={{
|
||||
maxWidth: node.maxWidth?.[0],
|
||||
}}
|
||||
>
|
||||
<Suspense>
|
||||
<LayoutPanel node={node.first} editorProps={props.editorProps} />
|
||||
</Suspense>
|
||||
@@ -191,6 +217,7 @@ const LayoutPanel = memo(function LayoutPanel(
|
||||
defaultSize={100 - node.splitPercentage}
|
||||
style={{
|
||||
overflow: 'scroll',
|
||||
maxWidth: node.maxWidth?.[1],
|
||||
}}
|
||||
>
|
||||
<Suspense>
|
||||
@@ -211,6 +238,18 @@ export const PageDetailEditor = (props: PageDetailEditorProps) => {
|
||||
|
||||
const layout = useAtomValue(contentLayoutAtom);
|
||||
|
||||
if (layout === 'editor') {
|
||||
return (
|
||||
<Suspense>
|
||||
<PanelGroup direction="horizontal">
|
||||
<Panel>
|
||||
<EditorWrapper {...props} />
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Suspense>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { type CSSProperties, forwardRef } from 'react';
|
||||
|
||||
import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
||||
import { stringToColour } from '../../../utils';
|
||||
import { StyledFooter, StyledSignInButton } from './styles';
|
||||
import { StyledFooter } from './styles';
|
||||
|
||||
export const Footer = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -13,13 +14,13 @@ export const Footer = () => {
|
||||
|
||||
return (
|
||||
<StyledFooter data-testid="workspace-list-modal-footer">
|
||||
<StyledSignInButton
|
||||
<Button
|
||||
data-testid="sign-in-button"
|
||||
type="plain"
|
||||
icon={
|
||||
<div className="circle">
|
||||
<CloudWorkspaceIcon />
|
||||
</div>
|
||||
<CloudWorkspaceIcon
|
||||
style={{ color: 'var(--affine-primary-color)' }}
|
||||
/>
|
||||
}
|
||||
onClick={async () => {
|
||||
if (!runtimeConfig.enableCloud) {
|
||||
@@ -28,7 +29,7 @@ export const Footer = () => {
|
||||
}}
|
||||
>
|
||||
{t['Sign in']()}
|
||||
</StyledSignInButton>
|
||||
</Button>
|
||||
</StyledFooter>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
displayFlex,
|
||||
displayInlineFlex,
|
||||
styled,
|
||||
textEllipsis,
|
||||
} from '@affine/component';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
export const StyleWorkspaceInfo = styled('div')(() => {
|
||||
return {
|
||||
marginLeft: '15px',
|
||||
@@ -116,21 +110,3 @@ export const StyledModalHeader = styled('div')(() => {
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInButton = styled(Button)(() => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
paddingLeft: 0,
|
||||
'.circle': {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '20px',
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
color: 'var(--affine-primary-color)',
|
||||
fontSize: '24px',
|
||||
flexShrink: 0,
|
||||
marginRight: '16px',
|
||||
...displayInlineFlex('center', 'center'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import clsx from 'clsx';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import throttle from 'lodash.throttle';
|
||||
import debounce from 'lodash.debounce';
|
||||
import type { MutableRefObject, ReactNode } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
@@ -22,37 +22,52 @@ interface HeaderPros {
|
||||
|
||||
const useIsTinyScreen = ({
|
||||
mainContainer,
|
||||
leftDoms,
|
||||
leftStatic,
|
||||
leftSlot,
|
||||
centerDom,
|
||||
rightDoms,
|
||||
rightStatic,
|
||||
rightSlot,
|
||||
}: {
|
||||
mainContainer: HTMLElement;
|
||||
leftDoms: MutableRefObject<HTMLElement | null>[];
|
||||
leftStatic: MutableRefObject<HTMLElement | null>;
|
||||
leftSlot: MutableRefObject<HTMLElement | null>[];
|
||||
centerDom: MutableRefObject<HTMLElement | null>;
|
||||
rightDoms: MutableRefObject<HTMLElement | null>[];
|
||||
rightStatic: MutableRefObject<HTMLElement | null>;
|
||||
rightSlot: MutableRefObject<HTMLElement | null>[];
|
||||
}) => {
|
||||
const [isTinyScreen, setIsTinyScreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = throttle(() => {
|
||||
const handleResize = debounce(() => {
|
||||
if (!centerDom.current) {
|
||||
return;
|
||||
}
|
||||
const leftTotalWidth = leftDoms.reduce((accWidth, dom) => {
|
||||
const leftStaticWidth = leftStatic.current?.clientWidth || 0;
|
||||
const leftSlotWidth = leftSlot.reduce((accWidth, dom) => {
|
||||
return accWidth + (dom.current?.clientWidth || 0);
|
||||
}, 0);
|
||||
|
||||
const rightTotalWidth = rightDoms.reduce((accWidth, dom) => {
|
||||
const rightStaticWidth = rightStatic.current?.clientWidth || 0;
|
||||
|
||||
const rightSlotWidth = rightSlot.reduce((accWidth, dom) => {
|
||||
return accWidth + (dom.current?.clientWidth || 0);
|
||||
}, 0);
|
||||
|
||||
if (!leftSlotWidth && !rightSlotWidth) {
|
||||
if (isTinyScreen) {
|
||||
setIsTinyScreen(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const containerRect = mainContainer.getBoundingClientRect();
|
||||
const centerRect = centerDom.current.getBoundingClientRect();
|
||||
|
||||
const offset = isTinyScreen ? 50 : 0;
|
||||
if (
|
||||
leftTotalWidth + containerRect.left >= centerRect.left - offset ||
|
||||
containerRect.right - centerRect.right <= rightTotalWidth + offset
|
||||
leftStaticWidth + leftSlotWidth + containerRect.left >=
|
||||
centerRect.left ||
|
||||
containerRect.right - centerRect.right <=
|
||||
rightSlotWidth + rightStaticWidth
|
||||
) {
|
||||
setIsTinyScreen(true);
|
||||
} else {
|
||||
@@ -67,7 +82,19 @@ const useIsTinyScreen = ({
|
||||
});
|
||||
|
||||
resizeObserver.observe(mainContainer);
|
||||
}, [centerDom, isTinyScreen, leftDoms, mainContainer, rightDoms]);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [
|
||||
centerDom,
|
||||
isTinyScreen,
|
||||
leftSlot,
|
||||
leftStatic,
|
||||
mainContainer,
|
||||
rightSlot,
|
||||
rightStatic,
|
||||
]);
|
||||
|
||||
return isTinyScreen;
|
||||
};
|
||||
@@ -84,9 +111,11 @@ export const Header = ({ left, center, right }: HeaderPros) => {
|
||||
|
||||
const isTinyScreen = useIsTinyScreen({
|
||||
mainContainer: document.querySelector('.main-container') || document.body,
|
||||
leftDoms: [sidebarSwitchRef, leftSlotRef],
|
||||
leftStatic: sidebarSwitchRef,
|
||||
leftSlot: [leftSlotRef],
|
||||
centerDom: centerSlotRef,
|
||||
rightDoms: [rightSlotRef, windowControlsRef],
|
||||
rightSlot: [rightSlotRef],
|
||||
rightStatic: windowControlsRef,
|
||||
});
|
||||
|
||||
const isWindowsDesktop = globalThis.platform === 'win32' && isDesktop;
|
||||
@@ -124,7 +153,6 @@ export const Header = ({ left, center, right }: HeaderPros) => {
|
||||
className={clsx({
|
||||
[style.headerCenter]: center,
|
||||
'is-window': isWindowsDesktop,
|
||||
'has-min-width': !isTinyScreen,
|
||||
})}
|
||||
ref={centerSlotRef}
|
||||
>
|
||||
|
||||
@@ -8,6 +8,7 @@ export const header = style({
|
||||
padding: '0 16px',
|
||||
minHeight: '52px',
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
zIndex: 2,
|
||||
selectors: {
|
||||
'&[data-sidebar-floating="false"]': {
|
||||
WebkitAppRegion: 'drag',
|
||||
@@ -48,6 +49,7 @@ export const headerCenter = style({
|
||||
height: '52px',
|
||||
flexShrink: 0,
|
||||
maxWidth: '60%',
|
||||
minWidth: '300px',
|
||||
position: 'absolute',
|
||||
transform: 'translateX(-50%)',
|
||||
left: '50%',
|
||||
@@ -56,9 +58,6 @@ export const headerCenter = style({
|
||||
'&.is-window': {
|
||||
maxWidth: '50%',
|
||||
},
|
||||
'&.is-window.has-min-width': {
|
||||
minWidth: '400px',
|
||||
},
|
||||
'&.shadow': {
|
||||
position: 'static',
|
||||
visibility: 'hidden',
|
||||
@@ -76,6 +75,7 @@ export const headerSideContainer = style({
|
||||
},
|
||||
'&.block': {
|
||||
display: 'block',
|
||||
paddingBottom: '10px',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -83,6 +83,8 @@ export const headerSideContainer = style({
|
||||
export const windowAppControlsWrapper = style({
|
||||
display: 'flex',
|
||||
marginLeft: '20px',
|
||||
// header padding right
|
||||
transform: 'translateX(16px)',
|
||||
});
|
||||
|
||||
export const windowAppControl = style({
|
||||
@@ -98,7 +100,6 @@ export const windowAppControl = style({
|
||||
'&[data-type="close"]': {
|
||||
width: '56px',
|
||||
paddingRight: '5px',
|
||||
marginRight: '-12px',
|
||||
},
|
||||
'&[data-type="close"]:hover': {
|
||||
background: 'var(--affine-windows-close-button)',
|
||||
|
||||
@@ -44,7 +44,7 @@ const OSWarningMessage = () => {
|
||||
return (
|
||||
<span>
|
||||
<Trans i18nKey="recommendBrowser">
|
||||
We recommend the <strong>Chrome</strong> browser for optimal
|
||||
We recommend the <strong>Chrome</strong> browser for an optimal
|
||||
experience.
|
||||
</Trans>
|
||||
</span>
|
||||
|
||||
@@ -60,7 +60,11 @@ export const HelpIsland = ({
|
||||
style={{ height: spread ? `${showList.length * 40 + 4}px` : 0 }}
|
||||
>
|
||||
{showList.includes('whatNew') && (
|
||||
<Tooltip content={t["Discover what's new!"]()} placement="left-end">
|
||||
<Tooltip
|
||||
content={t["Discover what's new!"]()}
|
||||
placement="left-end"
|
||||
showArrow={true}
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="right-bottom-change-log-icon"
|
||||
onClick={() => {
|
||||
@@ -72,7 +76,11 @@ export const HelpIsland = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('contact') && (
|
||||
<Tooltip content={t['Contact Us']()} placement="left-end">
|
||||
<Tooltip
|
||||
content={t['Contact Us']()}
|
||||
placement="left-end"
|
||||
showArrow={true}
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="right-bottom-contact-us-icon"
|
||||
onClick={openAbout}
|
||||
@@ -82,7 +90,11 @@ export const HelpIsland = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('shortcuts') && (
|
||||
<Tooltip content={t['Keyboard Shortcuts']()} placement="left-end">
|
||||
<Tooltip
|
||||
content={t['Keyboard Shortcuts']()}
|
||||
placement="left-end"
|
||||
showArrow={true}
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="shortcuts-icon"
|
||||
onClick={() => {
|
||||
@@ -98,6 +110,7 @@ export const HelpIsland = ({
|
||||
<Tooltip
|
||||
content={t['com.affine.helpIsland.gettingStarted']()}
|
||||
placement="left-end"
|
||||
showArrow={true}
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="easy-guide"
|
||||
@@ -112,7 +125,11 @@ export const HelpIsland = ({
|
||||
)}
|
||||
</StyledAnimateWrapper>
|
||||
|
||||
<Tooltip content={t['Help and Feedback']()} placement="left-end">
|
||||
<Tooltip
|
||||
content={t['Help and Feedback']()}
|
||||
placement={'left-end'}
|
||||
showArrow={true}
|
||||
>
|
||||
<MuiFade in={!spread} data-testid="faq-icon">
|
||||
<StyledTriggerWrapper>
|
||||
<HelpIcon />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { PageBlockModel } from '@blocksuite/blocks';
|
||||
import { assertEquals } from '@blocksuite/global/utils';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
@@ -39,15 +38,7 @@ export const Footer = ({
|
||||
const id = nanoid();
|
||||
const page = createPage(id);
|
||||
assertEquals(page.id, id);
|
||||
await initEmptyPage(page);
|
||||
const block = page.getBlockByFlavour(
|
||||
'affine:page'
|
||||
)[0] as PageBlockModel;
|
||||
if (block) {
|
||||
block.title.insert(query, 0);
|
||||
} else {
|
||||
console.warn('No page block found');
|
||||
}
|
||||
await initEmptyPage(page, query);
|
||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
||||
title: query,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal, ModalWrapper } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Command } from 'cmdk';
|
||||
import { startTransition } from 'react';
|
||||
import { startTransition, Suspense } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
StyledModalDivider,
|
||||
StyledModalFooter,
|
||||
StyledModalHeader,
|
||||
StyledNotFound,
|
||||
StyledShortcut,
|
||||
} from './style';
|
||||
|
||||
@@ -41,7 +42,7 @@ export const QuickSearchModal = ({
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
// Add ‘⌘+K’ shortcut keys as switches
|
||||
// Add ‘⌘+K’ shortcut keys as switches
|
||||
useEffect(() => {
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
if ((e.key === 'k' && e.metaKey) || (e.key === 'k' && e.ctrlKey)) {
|
||||
@@ -131,12 +132,20 @@ export const QuickSearchModal = ({
|
||||
<StyledModalDivider />
|
||||
<Command.List>
|
||||
<StyledContent>
|
||||
<Results
|
||||
query={query}
|
||||
onClose={handleClose}
|
||||
workspace={workspace}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
<Suspense
|
||||
fallback={
|
||||
<StyledNotFound>
|
||||
<span>{t['com.affine.loading']()}</span>
|
||||
</StyledNotFound>
|
||||
}
|
||||
>
|
||||
<Results
|
||||
query={query}
|
||||
onClose={handleClose}
|
||||
workspace={workspace}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
</Suspense>
|
||||
</StyledContent>
|
||||
{showCreatePage ? (
|
||||
<>
|
||||
|
||||
@@ -2,11 +2,13 @@ import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { Command } from 'cmdk';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { type Atom, atom, useAtomValue } from 'jotai';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { startTransition, useEffect } from 'react';
|
||||
|
||||
import { recentPageSettingsAtom } from '../../../atoms';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
@@ -20,6 +22,29 @@ export interface ResultsProps {
|
||||
onClose: () => void;
|
||||
setShowCreatePage: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const loadAllPageWeakMap = new WeakMap<Workspace, Atom<Promise<void>>>();
|
||||
|
||||
function getLoadAllPage(workspace: Workspace) {
|
||||
if (loadAllPageWeakMap.has(workspace)) {
|
||||
return loadAllPageWeakMap.get(workspace) as Atom<Promise<void>>;
|
||||
} else {
|
||||
const aAtom = atom(async () => {
|
||||
// fixme: we have to load all pages here and re-index them
|
||||
// there might have performance issue
|
||||
await Promise.all(
|
||||
[...workspace.pages.values()].map(page =>
|
||||
page.waitForLoaded().then(() => {
|
||||
workspace.indexer.search.refreshPageIndex(page.id, page.spaceDoc);
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
loadAllPageWeakMap.set(workspace, aAtom);
|
||||
return aAtom;
|
||||
}
|
||||
}
|
||||
|
||||
export const Results = ({
|
||||
query,
|
||||
workspace,
|
||||
@@ -31,14 +56,20 @@ export const Results = ({
|
||||
const pageList = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
assertExists(blockSuiteWorkspace.id);
|
||||
const list = useSwitchToConfig(workspace.id);
|
||||
useAtomValue(getLoadAllPage(blockSuiteWorkspace));
|
||||
|
||||
const recentPageSetting = useAtomValue(recentPageSettingsAtom);
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToPage, jumpToSubPath } = useNavigateHelper();
|
||||
const results = blockSuiteWorkspace.search({ query });
|
||||
|
||||
// remove `space:` prefix
|
||||
const pageIds = [...results.values()].map(id => id.slice(6));
|
||||
const pageIds = [...blockSuiteWorkspace.search({ query }).values()].map(
|
||||
id => {
|
||||
if (id.startsWith('space:')) {
|
||||
return id.slice(6);
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const resultsPageMeta = pageList.filter(
|
||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||
@@ -53,7 +84,11 @@ export const Results = ({
|
||||
}
|
||||
});
|
||||
|
||||
setShowCreatePage(resultsPageMeta.length === 0);
|
||||
useEffect(() => {
|
||||
startTransition(() => {
|
||||
setShowCreatePage(resultsPageMeta.length === 0);
|
||||
});
|
||||
}, [resultsPageMeta.length, setShowCreatePage]);
|
||||
|
||||
if (!query) {
|
||||
return (
|
||||
@@ -117,7 +152,12 @@ export const Results = ({
|
||||
return (
|
||||
<StyledNotFound>
|
||||
<span>{t['Find 0 result']()}</span>
|
||||
<image href="/imgs/no-result.svg" width={200} height={200} />
|
||||
<img
|
||||
alt="no result"
|
||||
src="/imgs/no-result.svg"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</StyledNotFound>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,46 +6,80 @@ import {
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import {
|
||||
type ShortcutsInfo,
|
||||
useEdgelessShortcuts,
|
||||
useGeneralShortcuts,
|
||||
useMarkdownShortcuts,
|
||||
usePageShortcuts,
|
||||
} from '../../../hooks/affine/use-shortcuts';
|
||||
import { KeyboardIcon } from './icons';
|
||||
import {
|
||||
StyledListItem,
|
||||
StyledModalHeader,
|
||||
StyledShortcutsModal,
|
||||
StyledSubTitle,
|
||||
StyledTitle,
|
||||
} from './style';
|
||||
import * as styles from './style.css';
|
||||
// import {
|
||||
// StyledListItem,
|
||||
// StyledModalHeader,
|
||||
// StyledShortcutsModal,
|
||||
// StyledSubTitle,
|
||||
// StyledTitle,
|
||||
// } from './style';
|
||||
type ModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const ShortcutsPanel = ({
|
||||
shortcutsInfo,
|
||||
}: {
|
||||
shortcutsInfo: ShortcutsInfo;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.subtitle}>{shortcutsInfo.title}</div>
|
||||
|
||||
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div className={styles.listItem} key={title}>
|
||||
<span>{title}</span>
|
||||
<div className={styles.keyContainer}>
|
||||
{shortcuts.map(key => {
|
||||
return (
|
||||
<span className={styles.key} key={key}>
|
||||
{key}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const markdownShortcuts = useMarkdownShortcuts();
|
||||
const pageShortcuts = usePageShortcuts();
|
||||
const edgelessShortcuts = useEdgelessShortcuts();
|
||||
const generalShortcuts = useGeneralShortcuts();
|
||||
const markdownShortcutsInfo = useMarkdownShortcuts();
|
||||
const pageShortcutsInfo = usePageShortcuts();
|
||||
const edgelessShortcutsInfo = useEdgelessShortcuts();
|
||||
const generalShortcutsInfo = useGeneralShortcuts();
|
||||
|
||||
return (
|
||||
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||
<StyledShortcutsModal data-testid="shortcuts-modal">
|
||||
<div className={styles.shortcutsModal} data-testid="shortcuts-modal">
|
||||
<MuiClickAwayListener
|
||||
onClickAway={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<StyledModalHeader>
|
||||
<StyledTitle>
|
||||
<div
|
||||
className={styles.modalHeader}
|
||||
style={{ marginBottom: '-28px' }}
|
||||
>
|
||||
<div className={styles.title}>
|
||||
<KeyboardIcon />
|
||||
{t['Shortcuts']()}
|
||||
</StyledTitle>
|
||||
</div>
|
||||
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
@@ -54,48 +88,14 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</StyledModalHeader>
|
||||
<StyledSubTitle style={{ marginTop: 0 }}>
|
||||
{t['General']()}
|
||||
</StyledSubTitle>
|
||||
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<StyledListItem key={title}>
|
||||
<span>{title}</span>
|
||||
<span>{shortcuts}</span>
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
<StyledSubTitle>{t['Page']()}</StyledSubTitle>
|
||||
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<StyledListItem key={title}>
|
||||
<span>{title}</span>
|
||||
<span>{shortcuts}</span>
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
<StyledSubTitle>{t['Edgeless']()}</StyledSubTitle>
|
||||
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<StyledListItem key={title}>
|
||||
<span>{title}</span>
|
||||
<span>{shortcuts}</span>
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
<StyledSubTitle>{t['Markdown Syntax']()}</StyledSubTitle>
|
||||
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<StyledListItem key={title}>
|
||||
<span>{title}</span>
|
||||
<span>{shortcuts}</span>
|
||||
</StyledListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
|
||||
</div>
|
||||
</MuiClickAwayListener>
|
||||
</StyledShortcutsModal>
|
||||
</div>
|
||||
</MuiSlide>
|
||||
);
|
||||
};
|
||||
|
||||
89
apps/core/src/components/pure/shortcuts-modal/style.css.ts
Normal file
89
apps/core/src/components/pure/shortcuts-modal/style.css.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const shortcutsModal = style({
|
||||
width: '288px',
|
||||
height: '74vh',
|
||||
paddingBottom: '28px',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
boxShadow: 'var(--affine-popover-shadow)',
|
||||
borderRadius: `var(--affine-popover-radius)`,
|
||||
overflow: 'auto',
|
||||
position: 'fixed',
|
||||
right: '12px',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
margin: 'auto',
|
||||
zIndex: 'var(--affine-z-index-modal)',
|
||||
});
|
||||
// export const shortcutsModal = style({
|
||||
// color: 'var(--affine-text-primary-color)',
|
||||
// fontWeight: '500',
|
||||
// fontSize: 'var(--affine-font-sm)',
|
||||
// height: '44px',
|
||||
// display: 'flex',
|
||||
// justifyContent: 'center',
|
||||
// alignItems: 'center',
|
||||
// svg: {
|
||||
// width: '20px',
|
||||
// marginRight: '14px',
|
||||
// color: 'var(--affine-primary-color)',
|
||||
// },
|
||||
// });
|
||||
export const title = style({
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
height: '44px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
globalStyle(`${title} svg`, {
|
||||
width: '20px',
|
||||
marginRight: '14px',
|
||||
color: 'var(--affine-primary-color)',
|
||||
});
|
||||
|
||||
export const subtitle = style({
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
height: '34px',
|
||||
lineHeight: '36px',
|
||||
marginTop: '28px',
|
||||
padding: '0 16px',
|
||||
});
|
||||
export const modalHeader = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingTop: '8px 4px 0 4px',
|
||||
width: '100%',
|
||||
padding: '8px 16px 0 16px',
|
||||
position: 'sticky',
|
||||
left: '0',
|
||||
top: '0',
|
||||
background: 'var(--affine-white)',
|
||||
transition: 'background-color 0.5s',
|
||||
});
|
||||
|
||||
export const listItem = style({
|
||||
height: '34px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
padding: '0 16px',
|
||||
});
|
||||
export const keyContainer = style({
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
export const key = style({
|
||||
selectors: {
|
||||
'&:not(:last-child)::after': {
|
||||
content: '+',
|
||||
margin: '0 4px',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
|
||||
export const StyledShortcutsModal = styled('div')(() => ({
|
||||
width: '288px',
|
||||
height: '74vh',
|
||||
paddingBottom: '28px',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
boxShadow: 'var(--affine-popover-shadow)',
|
||||
borderRadius: `var(--affine-popover-radius)`,
|
||||
overflow: 'auto',
|
||||
boxRadius: '10px',
|
||||
position: 'fixed',
|
||||
right: '12px',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
margin: 'auto',
|
||||
zIndex: 'var(--affine-z-index-modal)',
|
||||
}));
|
||||
export const StyledTitle = styled('div')(() => ({
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
height: '44px',
|
||||
...displayFlex('center', 'center'),
|
||||
svg: {
|
||||
width: '20px',
|
||||
marginRight: '14px',
|
||||
color: 'var(--affine-primary-color)',
|
||||
},
|
||||
}));
|
||||
export const StyledSubTitle = styled('div')(() => ({
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
height: '34px',
|
||||
lineHeight: '36px',
|
||||
marginTop: '28px',
|
||||
padding: '0 16px',
|
||||
}));
|
||||
export const StyledModalHeader = styled('div')(() => ({
|
||||
...displayFlex('space-between', 'center'),
|
||||
paddingTop: '8px 4px 0 4px',
|
||||
width: '100%',
|
||||
padding: '8px 16px 0 16px',
|
||||
position: 'sticky',
|
||||
left: '0',
|
||||
top: '0',
|
||||
background: 'var(--affine-white)',
|
||||
transition: 'background-color 0.5s',
|
||||
}));
|
||||
|
||||
export const StyledListItem = styled('div')(() => ({
|
||||
height: '34px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
padding: '0 16px',
|
||||
}));
|
||||
@@ -11,6 +11,7 @@ import { useCallback, useState } from 'react';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import { toast } from '../../../utils';
|
||||
import { buttonContainer, group } from './styles.css';
|
||||
|
||||
export const TrashButtonGroup = () => {
|
||||
@@ -37,6 +38,7 @@ export const TrashButtonGroup = () => {
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
restoreFromTrash(pageId);
|
||||
toast(t['restored']({ title: pageMeta.title || 'Untitled' }));
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
@@ -63,7 +65,8 @@ export const TrashButtonGroup = () => {
|
||||
onConfirm={useCallback(() => {
|
||||
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
blockSuiteWorkspace.removePage(pageId);
|
||||
}, [blockSuiteWorkspace, jumpToSubPath, pageId, workspace.id])}
|
||||
toast(t['Permanently deleted']());
|
||||
}, [blockSuiteWorkspace, jumpToSubPath, pageId, workspace.id, t])}
|
||||
onCancel={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
|
||||
@@ -8,6 +8,7 @@ export const group = style({
|
||||
display: 'flex',
|
||||
gap: '24px',
|
||||
justifyContent: 'center',
|
||||
zIndex: 2,
|
||||
});
|
||||
export const buttonContainer = style({
|
||||
boxShadow: 'var(--affine-float-button-shadow-2)',
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
Modal,
|
||||
ModalCloseButton,
|
||||
ModalWrapper,
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { ScrollableContainer } from '@affine/component';
|
||||
import { Menu, MenuItem } from '@affine/component';
|
||||
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||
import type {
|
||||
AffineCloudWorkspace,
|
||||
@@ -15,28 +7,38 @@ import type {
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import { HelpIcon, ImportIcon, PlusIcon } from '@blocksuite/icons';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
import { Footer } from '../footer';
|
||||
import {
|
||||
StyledCreateWorkspaceCard,
|
||||
CloudWorkspaceIcon,
|
||||
ImportIcon,
|
||||
MoreHorizontalIcon,
|
||||
PlusIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { Popover } from '@mui/material';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { Divider } from '@toeverything/components/divider';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
import {
|
||||
StyledCreateWorkspaceCardPill,
|
||||
StyledCreateWorkspaceCardPillContainer,
|
||||
StyledCreateWorkspaceCardPillContent,
|
||||
StyledCreateWorkspaceCardPillIcon,
|
||||
StyledCreateWorkspaceCardPillTextSecondary,
|
||||
StyledHelperContainer,
|
||||
StyledImportWorkspaceCardPill,
|
||||
StyledModalBody,
|
||||
StyledModalContent,
|
||||
StyledModalFooterContent,
|
||||
StyledModalHeader,
|
||||
StyledModalHeaderContent,
|
||||
StyledModalHeaderLeft,
|
||||
StyledModalTitle,
|
||||
StyledOperationWrapper,
|
||||
StyleWorkspaceAdd,
|
||||
StyleWorkspaceInfo,
|
||||
StyleWorkspaceTitle,
|
||||
StyledSignInCardPill,
|
||||
StyledSignInCardPillTextCotainer,
|
||||
StyledSignInCardPillTextPrimary,
|
||||
StyledSignInCardPillTextSecondary,
|
||||
} from './styles';
|
||||
|
||||
interface WorkspaceModalProps {
|
||||
@@ -52,105 +54,85 @@ interface WorkspaceModalProps {
|
||||
onMoveWorkspace: (activeId: string, overId: string) => void;
|
||||
}
|
||||
|
||||
const CreateWorkspaceCard = ({
|
||||
onNewWorkspace,
|
||||
onAddWorkspace,
|
||||
}: {
|
||||
onNewWorkspace: () => void;
|
||||
onAddWorkspace: () => void;
|
||||
}) => {
|
||||
const AccountMenu = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const anchorEL = useRef<HTMLDivElement>(null);
|
||||
return (
|
||||
<div>
|
||||
<div>Unlimted</div>
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||
{t['com.affine.workspace.cloud.join']()}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||
{t['com.affine.workspace.cloud.account.settings']()}
|
||||
</MenuItem>
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
|
||||
{t['com.affine.workspace.cloud.account.logout']()}
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (runtimeConfig.enableSQLiteProvider && environment.isDesktop) {
|
||||
return (
|
||||
<Menu
|
||||
placement="auto"
|
||||
trigger={['click']}
|
||||
zIndex={1000}
|
||||
content={
|
||||
<StyledCreateWorkspaceCardPillContainer>
|
||||
<StyledCreateWorkspaceCardPill>
|
||||
<MenuItem
|
||||
style={{
|
||||
height: 'auto',
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
onClick={onNewWorkspace}
|
||||
data-testid="new-workspace"
|
||||
>
|
||||
<StyledCreateWorkspaceCardPillContent>
|
||||
<div>
|
||||
<p>{t['New Workspace']()}</p>
|
||||
<StyledCreateWorkspaceCardPillTextSecondary>
|
||||
<p>{t['Create your own workspace']()}</p>
|
||||
</StyledCreateWorkspaceCardPillTextSecondary>
|
||||
</div>
|
||||
<StyledCreateWorkspaceCardPillIcon>
|
||||
<PlusIcon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</MenuItem>
|
||||
</StyledCreateWorkspaceCardPill>
|
||||
<StyledCreateWorkspaceCardPill>
|
||||
<MenuItem
|
||||
onClick={onAddWorkspace}
|
||||
data-testid="add-workspace"
|
||||
style={{
|
||||
height: 'auto',
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
>
|
||||
<StyledCreateWorkspaceCardPillContent>
|
||||
<div>
|
||||
<p>{t['Add Workspace']()}</p>
|
||||
<StyledCreateWorkspaceCardPillTextSecondary>
|
||||
<p>{t['Add Workspace Hint']()}</p>
|
||||
</StyledCreateWorkspaceCardPillTextSecondary>
|
||||
</div>
|
||||
<StyledCreateWorkspaceCardPillIcon>
|
||||
<ImportIcon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</MenuItem>
|
||||
</StyledCreateWorkspaceCardPill>
|
||||
</StyledCreateWorkspaceCardPillContainer>
|
||||
}
|
||||
>
|
||||
<StyledCreateWorkspaceCard
|
||||
ref={anchorEL}
|
||||
data-testid="add-or-new-workspace"
|
||||
>
|
||||
<StyleWorkspaceAdd className="add-icon">
|
||||
<PlusIcon />
|
||||
</StyleWorkspaceAdd>
|
||||
const CloudWorkSpaceList = ({
|
||||
disabled,
|
||||
workspaces,
|
||||
onClickWorkspace,
|
||||
onClickWorkspaceSetting,
|
||||
currentWorkspaceId,
|
||||
onMoveWorkspace,
|
||||
}: WorkspaceModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
|
||||
<p>{t['Create Or Import']()}</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCreateWorkspaceCard>
|
||||
</Menu>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<StyledCreateWorkspaceCard
|
||||
onClick={onNewWorkspace}
|
||||
data-testid="new-workspace"
|
||||
>
|
||||
<StyleWorkspaceAdd className="add-icon">
|
||||
<PlusIcon />
|
||||
</StyleWorkspaceAdd>
|
||||
return (
|
||||
<>
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledModalTitle>
|
||||
{t['com.affine.workspace.cloud.sync']()}
|
||||
</StyledModalTitle>
|
||||
</StyledModalHeaderLeft>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
|
||||
<p>{t['Create Or Import']()}</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCreateWorkspaceCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<StyledOperationWrapper>
|
||||
<Menu
|
||||
placement="bottom-end"
|
||||
trigger={['click']}
|
||||
content={<AccountMenu />}
|
||||
zIndex={1000}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="previous-image-button"
|
||||
icon={<MoreHorizontalIcon />}
|
||||
type="plain"
|
||||
/>
|
||||
</Menu>
|
||||
</StyledOperationWrapper>
|
||||
</StyledModalHeader>
|
||||
<StyledModalContent>
|
||||
<WorkspaceList
|
||||
disabled={disabled}
|
||||
items={
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[]
|
||||
}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onClick={onClickWorkspace}
|
||||
onSettingClick={onClickWorkspaceSetting}
|
||||
onDragEnd={useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (active.id !== over?.id) {
|
||||
onMoveWorkspace(active.id as string, over?.id as string);
|
||||
}
|
||||
},
|
||||
[onMoveWorkspace]
|
||||
)}
|
||||
/>
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
</StyledModalContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceListModal = ({
|
||||
@@ -166,70 +148,143 @@ export const WorkspaceListModal = ({
|
||||
onMoveWorkspace,
|
||||
}: WorkspaceModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
||||
// TODO: AFFiNE Cloud support
|
||||
const isLoggedIn = false;
|
||||
const anchorEl = document.getElementById('current-workspace');
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper
|
||||
width={720}
|
||||
height={690}
|
||||
style={{
|
||||
<Popover
|
||||
sx={{
|
||||
color: 'success.main',
|
||||
zIndex: 999,
|
||||
'& .MuiPopover-paper': {
|
||||
borderRadius: '8px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledModalTitle>{t['My Workspaces']()}</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t['Workspace description']()}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
<StyledHelperContainer>
|
||||
<HelpIcon />
|
||||
</StyledHelperContainer>
|
||||
</Tooltip>
|
||||
</StyledModalHeaderLeft>
|
||||
|
||||
<StyledOperationWrapper>
|
||||
<ModalCloseButton
|
||||
data-testid="close-workspace-modal"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
absolute={false}
|
||||
/>
|
||||
</StyledOperationWrapper>
|
||||
</StyledModalHeader>
|
||||
<ScrollableContainer>
|
||||
<StyledModalContent>
|
||||
<WorkspaceList
|
||||
disabled={disabled}
|
||||
items={
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[]
|
||||
boxShadow: 'var(--affine-shadow-2)',
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
},
|
||||
maxHeight: '90vh',
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
>
|
||||
<StyledModalHeaderContent>
|
||||
<StyledSignInCardPill>
|
||||
<MenuItem
|
||||
style={{
|
||||
height: 'auto',
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
onClick={async () => {
|
||||
if (!runtimeConfig.enableCloud) {
|
||||
setOpen(true);
|
||||
}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onClick={onClickWorkspace}
|
||||
onSettingClick={onClickWorkspaceSetting}
|
||||
onDragEnd={useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (active.id !== over?.id) {
|
||||
onMoveWorkspace(active.id as string, over?.id as string);
|
||||
}
|
||||
},
|
||||
[onMoveWorkspace]
|
||||
)}
|
||||
/>
|
||||
<CreateWorkspaceCard
|
||||
onNewWorkspace={onNewWorkspace}
|
||||
onAddWorkspace={onAddWorkspace}
|
||||
/>
|
||||
</StyledModalContent>
|
||||
</ScrollableContainer>
|
||||
<Footer />
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
}}
|
||||
data-testid="cloud-signin-button"
|
||||
>
|
||||
<StyledCreateWorkspaceCardPillContent>
|
||||
<StyledCreateWorkspaceCardPillIcon>
|
||||
<CloudWorkspaceIcon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
<StyledSignInCardPillTextCotainer>
|
||||
<StyledSignInCardPillTextPrimary>
|
||||
{t['com.affine.workspace.cloud.auth']()}
|
||||
</StyledSignInCardPillTextPrimary>
|
||||
<StyledSignInCardPillTextSecondary>
|
||||
Sync with AFFiNE Cloud
|
||||
</StyledSignInCardPillTextSecondary>
|
||||
</StyledSignInCardPillTextCotainer>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</MenuItem>
|
||||
</StyledSignInCardPill>
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
</StyledModalHeaderContent>
|
||||
<StyledModalBody>
|
||||
{isLoggedIn ? (
|
||||
<CloudWorkSpaceList
|
||||
disabled={disabled}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
workspaces={workspaces}
|
||||
onClickWorkspace={onClickWorkspace}
|
||||
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
||||
onNewWorkspace={onNewWorkspace}
|
||||
onAddWorkspace={onAddWorkspace}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onMoveWorkspace={onMoveWorkspace}
|
||||
/>
|
||||
) : null}
|
||||
<StyledModalHeader>
|
||||
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
|
||||
</StyledModalHeader>
|
||||
<StyledModalContent>
|
||||
<WorkspaceList
|
||||
disabled={disabled}
|
||||
items={
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[]
|
||||
}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onClick={onClickWorkspace}
|
||||
onSettingClick={onClickWorkspaceSetting}
|
||||
onDragEnd={useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (active.id !== over?.id) {
|
||||
onMoveWorkspace(active.id as string, over?.id as string);
|
||||
}
|
||||
},
|
||||
[onMoveWorkspace]
|
||||
)}
|
||||
/>
|
||||
</StyledModalContent>
|
||||
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
|
||||
<StyledImportWorkspaceCardPill>
|
||||
<MenuItem
|
||||
onClick={onAddWorkspace}
|
||||
data-testid="add-workspace"
|
||||
style={{
|
||||
height: 'auto',
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
>
|
||||
<StyledCreateWorkspaceCardPillContent>
|
||||
<StyledCreateWorkspaceCardPillIcon>
|
||||
<ImportIcon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
<div>
|
||||
<p>{t['com.affine.workspace.local.import']()}</p>
|
||||
</div>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</MenuItem>
|
||||
</StyledImportWorkspaceCardPill>
|
||||
) : null}
|
||||
</StyledModalBody>
|
||||
<StyledModalFooterContent>
|
||||
<StyledCreateWorkspaceCardPill>
|
||||
<MenuItem
|
||||
style={{
|
||||
height: 'auto',
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
onClick={onNewWorkspace}
|
||||
data-testid="new-workspace"
|
||||
>
|
||||
<StyledCreateWorkspaceCardPillContent>
|
||||
<StyledCreateWorkspaceCardPillIcon>
|
||||
<PlusIcon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
<div>
|
||||
<p>{t['New Workspace']()}</p>
|
||||
</div>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</MenuItem>
|
||||
</StyledCreateWorkspaceCardPill>
|
||||
</StyledModalFooterContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -81,11 +81,29 @@ export const StyledCreateWorkspaceCardPillContainer = styled('div')(() => {
|
||||
});
|
||||
|
||||
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
|
||||
return {
|
||||
borderRadius: '8px',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '58px',
|
||||
border: `1px solid var(--affine-border-color)`,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInCardPill = styled('div')(() => {
|
||||
return {
|
||||
borderRadius: '8px',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '58px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledImportWorkspaceCardPill = styled('div')(() => {
|
||||
return {
|
||||
borderRadius: '5px',
|
||||
display: 'flex',
|
||||
boxShadow: '0px 0px 6px 0px rgba(0, 0, 0, 0.1)',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
width: '100%',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -94,7 +112,6 @@ export const StyledCreateWorkspaceCardPillContent = styled('div')(() => {
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -106,13 +123,30 @@ export const StyledCreateWorkspaceCardPillIcon = styled('div')(() => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCreateWorkspaceCardPillTextSecondary = styled('div')(() => {
|
||||
export const StyledSignInCardPillTextCotainer = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInCardPillTextSecondary = styled('div')(() => {
|
||||
return {
|
||||
fontSize: '12px',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInCardPillTextPrimary = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
maxWidth: '200px',
|
||||
...textEllipsis(1),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeaderLeft = styled('div')(() => {
|
||||
return { ...displayFlex('flex-start', 'center') };
|
||||
});
|
||||
@@ -120,6 +154,7 @@ export const StyledModalTitle = styled('div')(() => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -134,11 +169,30 @@ export const StyledHelperContainer = styled('div')(() => {
|
||||
});
|
||||
|
||||
export const StyledModalContent = styled('div')({
|
||||
height: '540px',
|
||||
padding: '8px 40px',
|
||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const StyledModalFooterContent = styled('div')({
|
||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
padding: '12px',
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
});
|
||||
|
||||
export const StyledModalHeaderContent = styled('div')({
|
||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
padding: '12px 12px 0px 12px',
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
});
|
||||
|
||||
export const StyledOperationWrapper = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('flex-end', 'center'),
|
||||
@@ -150,23 +204,34 @@ export const StyleWorkspaceAdd = styled('div')(() => {
|
||||
width: '58px',
|
||||
height: '58px',
|
||||
borderRadius: '100%',
|
||||
background: 'var(--affine-white-80)',
|
||||
background: 'var(--affine-background-overlay-panel-color)',
|
||||
border: '1.5px dashed #f4f5fa',
|
||||
transition: 'background .2s',
|
||||
fontSize: '24px',
|
||||
...displayFlex('center', 'center'),
|
||||
borderColor: 'var(--affine-white)',
|
||||
color: 'var(--affine-primary-color)',
|
||||
color: 'var(--affine-background-overlay-panel-color)',
|
||||
};
|
||||
});
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
marginTop: '10px',
|
||||
left: 0,
|
||||
top: 0,
|
||||
borderRadius: '24px 24px 0 0',
|
||||
padding: '10px 40px',
|
||||
padding: '12px 14px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalBody = styled('div')(() => {
|
||||
return {
|
||||
padding: '0px 12px',
|
||||
display: 'inline-flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
gap: '12px',
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ export const StyledSelectorContainer = styled('div')(() => {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 6px',
|
||||
margin: '0 -6px',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
':hover': {
|
||||
|
||||
@@ -32,6 +32,7 @@ export const WorkspaceSelector = ({
|
||||
|
||||
// Open dialog when `Enter` or `Space` pressed
|
||||
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
|
||||
// TODO: Delete this?
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
@@ -50,6 +51,7 @@ export const WorkspaceSelector = ({
|
||||
onClick={onClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
data-testid="current-workspace"
|
||||
id="current-workspace"
|
||||
>
|
||||
<WorkspaceAvatar
|
||||
data-testid="workspace-avatar"
|
||||
|
||||
@@ -211,7 +211,7 @@ const CollectionRenderer = ({
|
||||
}
|
||||
>
|
||||
<div data-testid="collection-options" className={styles.more}>
|
||||
<MoreHorizontalIcon></MoreHorizontalIcon>
|
||||
<MoreHorizontalIcon />
|
||||
</div>
|
||||
</Menu>
|
||||
}
|
||||
@@ -228,8 +228,8 @@ const CollectionRenderer = ({
|
||||
<div>{collection.name}</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<Collapsible.Content>
|
||||
<div style={{ marginLeft: 8 }}>
|
||||
<Collapsible.Content className={styles.collapsibleContent}>
|
||||
<div style={{ marginLeft: 20, marginTop: -4 }}>
|
||||
{pagesToRender.map(page => {
|
||||
return (
|
||||
<Page
|
||||
@@ -257,6 +257,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
|
||||
() => savedCollections.filter(v => v.pinned),
|
||||
[savedCollections]
|
||||
);
|
||||
const t = useAFFiNEI18N();
|
||||
if (pinedCollections.length === 0) {
|
||||
return (
|
||||
<MenuItem
|
||||
@@ -264,7 +265,7 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
|
||||
icon={<InformationIcon />}
|
||||
disabled
|
||||
>
|
||||
<span>Create a collection</span>
|
||||
<span>{t['Create a collection']()}</span>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -182,20 +182,18 @@ export const Page = ({
|
||||
>
|
||||
{page.title || t['Untitled']()}
|
||||
</MenuItem>
|
||||
<Collapsible.Content>
|
||||
<div style={{ marginLeft: 8 }}>
|
||||
{referencesToRender.map(id => {
|
||||
return (
|
||||
<ReferencePage
|
||||
key={id}
|
||||
workspace={workspace}
|
||||
pageId={id}
|
||||
metaMapping={allPageMeta}
|
||||
parentIds={new Set([pageId])}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Collapsible.Content className={styles.collapsibleContent}>
|
||||
{referencesToRender.map(id => {
|
||||
return (
|
||||
<ReferencePage
|
||||
key={id}
|
||||
workspace={workspace}
|
||||
pageId={id}
|
||||
metaMapping={allPageMeta}
|
||||
parentIds={new Set([pageId])}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
|
||||
|
||||
export const wrapper = style({
|
||||
userSelect: 'none',
|
||||
@@ -29,8 +29,9 @@ export const more = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 4,
|
||||
padding: 4,
|
||||
borderRadius: 2,
|
||||
fontSize: 16,
|
||||
color: 'var(--affine-icon-color)',
|
||||
':hover': {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
},
|
||||
@@ -52,3 +53,34 @@ export const menuDividerStyle = style({
|
||||
height: '1px',
|
||||
background: 'var(--affine-border-color)',
|
||||
});
|
||||
|
||||
const slideDown = keyframes({
|
||||
'0%': {
|
||||
height: '0px',
|
||||
},
|
||||
'100%': {
|
||||
height: 'var(--radix-collapsible-content-height)',
|
||||
},
|
||||
});
|
||||
|
||||
const slideUp = keyframes({
|
||||
'0%': {
|
||||
height: 'var(--radix-collapsible-content-height)',
|
||||
},
|
||||
'100%': {
|
||||
height: '0px',
|
||||
},
|
||||
});
|
||||
|
||||
export const collapsibleContent = style({
|
||||
overflow: 'hidden',
|
||||
marginTop: '4px',
|
||||
selectors: {
|
||||
'&[data-state="open"]': {
|
||||
animation: `${slideDown} 0.2s ease-out`,
|
||||
},
|
||||
'&[data-state="closed"]': {
|
||||
animation: `${slideUp} 0.2s ease-out`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -31,12 +31,10 @@ export const ReferencePage = ({
|
||||
const referencesToShow = useMemo(() => {
|
||||
return [
|
||||
...new Set(
|
||||
references.filter(
|
||||
ref => !parentIds.has(ref) && !metaMapping[ref]?.trash
|
||||
)
|
||||
references.filter(ref => metaMapping[ref] && !metaMapping[ref]?.trash)
|
||||
),
|
||||
];
|
||||
}, [references, parentIds, metaMapping]);
|
||||
}, [references, metaMapping]);
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const collapsible = referencesToShow.length > 0;
|
||||
const nestedItem = parentIds.size > 0;
|
||||
|
||||
@@ -11,11 +11,13 @@ export const label = style({
|
||||
export const favItemWrapper = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
selectors: {
|
||||
'&[data-nested="true"]': {
|
||||
marginLeft: '12px',
|
||||
width: 'calc(100% - 12px)',
|
||||
marginLeft: '20px',
|
||||
width: 'calc(100% - 20px)',
|
||||
},
|
||||
'&:not(:first-of-type)': {
|
||||
marginTop: '4px',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -40,6 +42,7 @@ const slideUp = keyframes({
|
||||
|
||||
export const collapsibleContent = style({
|
||||
overflow: 'hidden',
|
||||
marginTop: '4px',
|
||||
selectors: {
|
||||
'&[data-state="open"]': {
|
||||
animation: `${slideDown} 0.2s ease-out`,
|
||||
@@ -53,5 +56,4 @@ export const collapsibleContent = style({
|
||||
export const collapsibleContentInner = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4px',
|
||||
});
|
||||
|
||||
@@ -51,14 +51,14 @@ export type RootAppSidebarProps = {
|
||||
};
|
||||
|
||||
const RouteMenuLinkItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
HTMLButtonElement,
|
||||
{
|
||||
currentPath: string; // todo: pass through useRouter?
|
||||
path: string;
|
||||
icon: ReactElement;
|
||||
children?: ReactElement;
|
||||
isDraggedOver?: boolean;
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
} & React.HTMLAttributes<HTMLButtonElement>
|
||||
>(({ currentPath, path, icon, children, isDraggedOver, ...props }, ref) => {
|
||||
// Force active style when a page is dragged over
|
||||
const active = isDraggedOver || currentPath === path;
|
||||
@@ -196,6 +196,8 @@ export const RootAppSidebar = ({
|
||||
</CategoryDivider>
|
||||
<CollectionsList workspace={blockSuiteWorkspace} />
|
||||
<CategoryDivider label={t['others']()} />
|
||||
{/* fixme: remove the following spacer */}
|
||||
<div style={{ height: '4px' }} />
|
||||
<RouteMenuLinkItem
|
||||
ref={trashDroppable.setNodeRef}
|
||||
isDraggedOver={trashDroppable.isOver}
|
||||
@@ -211,7 +213,7 @@ export const RootAppSidebar = ({
|
||||
</SidebarScrollableContainer>
|
||||
<SidebarContainer>
|
||||
{isDesktop && <AppUpdaterButton />}
|
||||
<div />
|
||||
<div style={{ height: '4px' }} />
|
||||
<AddPageButton onClick={onClickNewPage} />
|
||||
</SidebarContainer>
|
||||
</AppSidebar>
|
||||
|
||||
@@ -100,7 +100,6 @@ export function WorkspaceHeader({
|
||||
/>
|
||||
}
|
||||
center={<WorkspaceModeFilterTab />}
|
||||
right={<PluginHeader />}
|
||||
/>
|
||||
{<FilterContainer workspaceId={currentWorkspaceId} />}
|
||||
</>
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
interface ShortcutTip {
|
||||
[x: string]: string;
|
||||
interface ShortcutMap {
|
||||
[x: string]: string[];
|
||||
}
|
||||
export interface ShortcutsInfo {
|
||||
title: string;
|
||||
shortcuts: ShortcutMap;
|
||||
}
|
||||
|
||||
export const useWinGeneralKeyboardShortcuts = (): ShortcutTip => {
|
||||
export const useWinGeneralKeyboardShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Cancel']()]: 'ESC',
|
||||
[t['Quick Search']()]: 'Ctrl + K',
|
||||
[t['New Page']()]: 'Ctrl + N',
|
||||
[t['Cancel']()]: ['ESC'],
|
||||
[t['Quick Search']()]: ['Ctrl', 'K'],
|
||||
[t['New Page']()]: ['Ctrl', 'N'],
|
||||
// not implement yet
|
||||
// [t['Append to Daily Note']()]: 'Ctrl + Alt + A',
|
||||
[t['Expand/Collapse Sidebar']()]: 'Ctrl + /',
|
||||
[t['Expand/Collapse Sidebar']()]: ['Ctrl', '/'],
|
||||
// not implement yet
|
||||
// [t['Go Back']()]: 'Ctrl + [',
|
||||
// [t['Go Forward']()]: 'Ctrl + ]',
|
||||
@@ -22,16 +26,16 @@ export const useWinGeneralKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t]
|
||||
);
|
||||
};
|
||||
export const useMacGeneralKeyboardShortcuts = (): ShortcutTip => {
|
||||
export const useMacGeneralKeyboardShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Cancel']()]: 'ESC',
|
||||
[t['Quick Search']()]: '⌘ + K',
|
||||
[t['New Page']()]: '⌘ + N',
|
||||
[t['Cancel']()]: ['ESC'],
|
||||
[t['Quick Search']()]: ['⌘', 'K'],
|
||||
[t['New Page']()]: ['⌘', 'N'],
|
||||
// not implement yet
|
||||
// [t['Append to Daily Note']()]: '⌘ + ⌥ + A',
|
||||
[t['Expand/Collapse Sidebar']()]: '⌘ + /',
|
||||
[t['Expand/Collapse Sidebar']()]: ['⌘', '/'],
|
||||
// not implement yet
|
||||
// [t['Go Back']()]: '⌘ + [',
|
||||
// [t['Go Forward']()]: '⌘ + ]',
|
||||
@@ -40,28 +44,28 @@ export const useMacGeneralKeyboardShortcuts = (): ShortcutTip => {
|
||||
);
|
||||
};
|
||||
|
||||
export const useMacEdgelessKeyboardShortcuts = (): ShortcutTip => {
|
||||
export const useMacEdgelessKeyboardShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Select All']()]: '⌘ + A',
|
||||
[t['Undo']()]: '⌘ + Z',
|
||||
[t['Redo']()]: '⌘ + ⇧ + Z',
|
||||
[t['Zoom in']()]: '⌘ + +',
|
||||
[t['Zoom out']()]: '⌘ + -',
|
||||
[t['Zoom to 100%']()]: '⌘ + 0',
|
||||
[t['Zoom to fit']()]: '⌘ + 1',
|
||||
[t['Select']()]: 'V',
|
||||
[t['Text']()]: 'T',
|
||||
[t['Shape']()]: 'S',
|
||||
[t['Image']()]: 'I',
|
||||
[t['Straight Connector']()]: 'L',
|
||||
[t['Elbowed Connector']()]: 'X',
|
||||
[t['Select All']()]: ['⌘', 'A'],
|
||||
[t['Undo']()]: ['⌘', 'Z'],
|
||||
[t['Redo']()]: ['⌘', '⇧', 'Z'],
|
||||
[t['Zoom in']()]: ['⌘', '+'],
|
||||
[t['Zoom out']()]: ['⌘', '-'],
|
||||
[t['Zoom to 100%']()]: ['⌘', '0'],
|
||||
[t['Zoom to fit']()]: ['⌘', '1'],
|
||||
[t['Select']()]: ['V'],
|
||||
[t['Text']()]: ['T'],
|
||||
[t['Shape']()]: ['S'],
|
||||
[t['Image']()]: ['I'],
|
||||
[t['Straight Connector']()]: ['L'],
|
||||
[t['Elbowed Connector']()]: ['X'],
|
||||
// not implement yet
|
||||
// [t['Curve Connector']()]: 'C',
|
||||
[t['Pen']()]: 'P',
|
||||
[t['Hand']()]: 'H',
|
||||
[t['Note']()]: 'N',
|
||||
[t['Pen']()]: ['P'],
|
||||
[t['Hand']()]: ['H'],
|
||||
[t['Note']()]: ['N'],
|
||||
// not implement yet
|
||||
// [t['Group']()]: '⌘ + G',
|
||||
// [t['Ungroup']()]: '⌘ + ⇧ + G',
|
||||
@@ -69,29 +73,29 @@ export const useMacEdgelessKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t]
|
||||
);
|
||||
};
|
||||
export const useWinEdgelessKeyboardShortcuts = (): ShortcutTip => {
|
||||
export const useWinEdgelessKeyboardShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Select All']()]: 'Ctrl + A',
|
||||
[t['Undo']()]: 'Ctrl + Z',
|
||||
[t['Redo']()]: 'Ctrl + Y/Ctrl + Shift + Z',
|
||||
[t['Zoom in']()]: 'Ctrl + +',
|
||||
[t['Zoom out']()]: 'Ctrl + -',
|
||||
[t['Zoom to 100%']()]: 'Ctrl + 0',
|
||||
[t['Zoom to fit']()]: 'Ctrl + 1',
|
||||
[t['Select']()]: 'V',
|
||||
[t['Text']()]: 'T',
|
||||
[t['Shape']()]: 'S',
|
||||
[t['Image']()]: 'I',
|
||||
[t['Straight Connector']()]: 'L',
|
||||
[t['Elbowed Connector']()]: 'X',
|
||||
[t['Select All']()]: ['Ctrl', 'A'],
|
||||
[t['Undo']()]: ['Ctrl', 'Z'],
|
||||
[t['Redo']()]: ['Ctrl', 'Y/Ctrl', 'Shift', 'Z'],
|
||||
[t['Zoom in']()]: ['Ctrl', '+'],
|
||||
[t['Zoom out']()]: ['Ctrl', '-'],
|
||||
[t['Zoom to 100%']()]: ['Ctrl', '0'],
|
||||
[t['Zoom to fit']()]: ['Ctrl', '1'],
|
||||
[t['Select']()]: ['V'],
|
||||
[t['Text']()]: ['T'],
|
||||
[t['Shape']()]: ['S'],
|
||||
[t['Image']()]: ['I'],
|
||||
[t['Straight Connector']()]: ['L'],
|
||||
[t['Elbowed Connector']()]: ['X'],
|
||||
// not implement yet
|
||||
// [t['Curve Connector']()]: 'C',
|
||||
[t['Pen']()]: 'P',
|
||||
[t['Hand']()]: 'H',
|
||||
[t['Note']()]: 'N',
|
||||
[t['Switch']()]: 'Alt + S',
|
||||
[t['Pen']()]: ['P'],
|
||||
[t['Hand']()]: ['H'],
|
||||
[t['Note']()]: ['N'],
|
||||
[t['Switch']()]: ['Alt ', ''],
|
||||
// not implement yet
|
||||
// [t['Group']()]: 'Ctrl + G',
|
||||
// [t['Ungroup']()]: 'Ctrl + Shift + G',
|
||||
@@ -99,31 +103,31 @@ export const useWinEdgelessKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t]
|
||||
);
|
||||
};
|
||||
export const useMacPageKeyboardShortcuts = (): ShortcutTip => {
|
||||
export const useMacPageKeyboardShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Undo']()]: '⌘+Z',
|
||||
[t['Redo']()]: '⌘+⇧+Z',
|
||||
[t['Bold']()]: '⌘+B',
|
||||
[t['Italic']()]: '⌘+I',
|
||||
[t['Underline']()]: '⌘+U',
|
||||
[t['Strikethrough']()]: '⌘+⇧+S',
|
||||
[t['Inline code']()]: ' ⌘+E',
|
||||
[t['Code block']()]: '⌘+⌥+C',
|
||||
[t['Link']()]: '⌘+K',
|
||||
[t['Quick search']()]: '⌘+K',
|
||||
[t['Body text']()]: '⌘+⌥+0',
|
||||
[t['Heading']({ number: '1' })]: '⌘+⌥+1',
|
||||
[t['Heading']({ number: '2' })]: '⌘+⌥+2',
|
||||
[t['Heading']({ number: '3' })]: '⌘+⌥+3',
|
||||
[t['Heading']({ number: '4' })]: '⌘+⌥+4',
|
||||
[t['Heading']({ number: '5' })]: '⌘+⌥+5',
|
||||
[t['Heading']({ number: '6' })]: '⌘+⌥+6',
|
||||
[t['Increase indent']()]: 'Tab',
|
||||
[t['Reduce indent']()]: '⇧+Tab',
|
||||
[t['Group as Database']()]: '⌘ + G',
|
||||
[t['Switch']()]: '⌥ + S',
|
||||
[t['Undo']()]: ['⌘', 'Z'],
|
||||
[t['Redo']()]: ['⌘', '⇧', 'Z'],
|
||||
[t['Bold']()]: ['⌘', 'B'],
|
||||
[t['Italic']()]: ['⌘', 'I'],
|
||||
[t['Underline']()]: ['⌘', 'U'],
|
||||
[t['Strikethrough']()]: ['⌘', '⇧', 'S'],
|
||||
[t['Inline code']()]: ['⌘', 'E'],
|
||||
[t['Code block']()]: ['⌘', '⌥', 'C'],
|
||||
[t['Link']()]: ['⌘', 'K'],
|
||||
[t['Quick search']()]: ['⌘', 'K'],
|
||||
[t['Body text']()]: ['⌘', '⌥', '0'],
|
||||
[t['Heading']({ number: '1' })]: ['⌘', '⌥', '1'],
|
||||
[t['Heading']({ number: '2' })]: ['⌘', '⌥', '2'],
|
||||
[t['Heading']({ number: '3' })]: ['⌘', '⌥', '3'],
|
||||
[t['Heading']({ number: '4' })]: ['⌘', '⌥', '4'],
|
||||
[t['Heading']({ number: '5' })]: ['⌘', '⌥', '5'],
|
||||
[t['Heading']({ number: '6' })]: ['⌘', '⌥', '6'],
|
||||
[t['Increase indent']()]: ['Tab'],
|
||||
[t['Reduce indent']()]: ['⇧', 'Tab'],
|
||||
[t['Group as Database']()]: ['⌘', 'G'],
|
||||
[t['Switch']()]: ['⌥', 'S'],
|
||||
// not implement yet
|
||||
// [t['Move Up']()]: '⌘ + ⌥ + ↑',
|
||||
// [t['Move Down']()]: '⌘ + ⌥ + ↓',
|
||||
@@ -132,53 +136,53 @@ export const useMacPageKeyboardShortcuts = (): ShortcutTip => {
|
||||
);
|
||||
};
|
||||
|
||||
export const useMacMarkdownShortcuts = (): ShortcutTip => {
|
||||
export const useMacMarkdownShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Bold']()]: '**Text** ',
|
||||
[t['Italic']()]: '*Text* ',
|
||||
[t['Underline']()]: '~Text~ ',
|
||||
[t['Strikethrough']()]: '~~Text~~ ',
|
||||
[t['Divider']()]: '***',
|
||||
[t['Inline code']()]: '`Text` ',
|
||||
[t['Code block']()]: '``` Space',
|
||||
[t['Heading']({ number: '1' })]: '# Text',
|
||||
[t['Heading']({ number: '2' })]: '## Text',
|
||||
[t['Heading']({ number: '3' })]: '### Text',
|
||||
[t['Heading']({ number: '4' })]: '#### Text',
|
||||
[t['Heading']({ number: '5' })]: '##### Text',
|
||||
[t['Heading']({ number: '6' })]: '###### Text',
|
||||
[t['Bold']()]: ['**Text**'],
|
||||
[t['Italic']()]: ['*Text*'],
|
||||
[t['Underline']()]: ['~Text~'],
|
||||
[t['Strikethrough']()]: ['~~Text~~'],
|
||||
[t['Divider']()]: ['***'],
|
||||
[t['Inline code']()]: ['`Text` '],
|
||||
[t['Code block']()]: ['``` Space'],
|
||||
[t['Heading']({ number: '1' })]: ['# Text'],
|
||||
[t['Heading']({ number: '2' })]: ['## Text'],
|
||||
[t['Heading']({ number: '3' })]: ['### Text'],
|
||||
[t['Heading']({ number: '4' })]: ['#### Text'],
|
||||
[t['Heading']({ number: '5' })]: ['##### Text'],
|
||||
[t['Heading']({ number: '6' })]: ['###### Text'],
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export const useWinPageKeyboardShortcuts = (): ShortcutTip => {
|
||||
export const useWinPageKeyboardShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Undo']()]: 'Ctrl+Z',
|
||||
[t['Redo']()]: 'Ctrl+Y',
|
||||
[t['Bold']()]: 'Ctrl+B',
|
||||
[t['Italic']()]: 'Ctrl+I',
|
||||
[t['Underline']()]: 'Ctrl+U',
|
||||
[t['Strikethrough']()]: 'Ctrl+Shift+S',
|
||||
[t['Inline code']()]: ' Ctrl+E',
|
||||
[t['Code block']()]: 'Ctrl+Alt+C',
|
||||
[t['Link']()]: 'Ctrl+K',
|
||||
[t['Quick search']()]: 'Ctrl+K',
|
||||
[t['Body text']()]: 'Ctrl+Shift+0',
|
||||
[t['Heading']({ number: '1' })]: 'Ctrl+Shift+1',
|
||||
[t['Heading']({ number: '2' })]: 'Ctrl+Shift+2',
|
||||
[t['Heading']({ number: '3' })]: 'Ctrl+Shift+3',
|
||||
[t['Heading']({ number: '4' })]: 'Ctrl+Shift+4',
|
||||
[t['Heading']({ number: '5' })]: 'Ctrl+Shift+5',
|
||||
[t['Heading']({ number: '6' })]: 'Ctrl+Shift+6',
|
||||
[t['Increase indent']()]: 'Tab',
|
||||
[t['Reduce indent']()]: 'Shift+Tab',
|
||||
[t['Group as Database']()]: 'Ctrl + G',
|
||||
['Switch']: 'Alt + S',
|
||||
[t['Undo']()]: ['Ctrl', 'Z'],
|
||||
[t['Redo']()]: ['Ctrl', 'Y'],
|
||||
[t['Bold']()]: ['Ctrl', 'B'],
|
||||
[t['Italic']()]: ['Ctrl', 'I'],
|
||||
[t['Underline']()]: ['Ctrl', 'U'],
|
||||
[t['Strikethrough']()]: ['Ctrl', 'Shift', 'S'],
|
||||
[t['Inline code']()]: [' Ctrl', 'E'],
|
||||
[t['Code block']()]: ['Ctrl', 'Alt', 'C'],
|
||||
[t['Link']()]: ['Ctr', 'K'],
|
||||
[t['Quick search']()]: ['Ctrl', 'K'],
|
||||
[t['Body text']()]: ['Ctrl', 'Shift', '0'],
|
||||
[t['Heading']({ number: '1' })]: ['Ctrl', 'Shift', '1'],
|
||||
[t['Heading']({ number: '2' })]: ['Ctrl', 'Shift', '2'],
|
||||
[t['Heading']({ number: '3' })]: ['Ctrl', 'Shift', '3'],
|
||||
[t['Heading']({ number: '4' })]: ['Ctrl', 'Shift', '4'],
|
||||
[t['Heading']({ number: '5' })]: ['Ctrl', 'Shift', '5'],
|
||||
[t['Heading']({ number: '6' })]: ['Ctrl', 'Shift', '6'],
|
||||
[t['Increase indent']()]: ['Tab'],
|
||||
[t['Reduce indent']()]: ['Shift+Tab'],
|
||||
[t['Group as Database']()]: ['Ctrl + G'],
|
||||
['Switch']: ['Alt + S'],
|
||||
// not implement yet
|
||||
// [t['Move Up']()]: 'Ctrl + Alt + ↑',
|
||||
// [t['Move Down']()]: 'Ctrl + Alt + ↓',
|
||||
@@ -186,54 +190,73 @@ export const useWinPageKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t]
|
||||
);
|
||||
};
|
||||
export const useWinMarkdownShortcuts = (): ShortcutTip => {
|
||||
export const useWinMarkdownShortcuts = (): ShortcutMap => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[t['Bold']()]: '**Text** ',
|
||||
[t['Italic']()]: '*Text* ',
|
||||
[t['Underline']()]: '~Text~ ',
|
||||
[t['Strikethrough']()]: '~~Text~~ ',
|
||||
[t['Divider']()]: '***',
|
||||
[t['Inline code']()]: '`Text` ',
|
||||
[t['Code block']()]: '``` Text',
|
||||
[t['Heading']({ number: '1' })]: '# Text',
|
||||
[t['Heading']({ number: '2' })]: '## Text',
|
||||
[t['Heading']({ number: '3' })]: '### Text',
|
||||
[t['Heading']({ number: '4' })]: '#### Text',
|
||||
[t['Heading']({ number: '5' })]: '##### Text',
|
||||
[t['Heading']({ number: '6' })]: '###### Text',
|
||||
[t['Bold']()]: ['**Text** '],
|
||||
[t['Italic']()]: ['*Text* '],
|
||||
[t['Underline']()]: ['~Text~ '],
|
||||
[t['Strikethrough']()]: ['~~Text~~ '],
|
||||
[t['Divider']()]: ['***'],
|
||||
[t['Inline code']()]: ['`Text` '],
|
||||
[t['Code block']()]: ['``` Text'],
|
||||
[t['Heading']({ number: '1' })]: ['# Text'],
|
||||
[t['Heading']({ number: '2' })]: ['## Text'],
|
||||
[t['Heading']({ number: '3' })]: ['### Text'],
|
||||
[t['Heading']({ number: '4' })]: ['#### Text'],
|
||||
[t['Heading']({ number: '5' })]: ['##### Text'],
|
||||
[t['Heading']({ number: '6' })]: ['###### Text'],
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export const useMarkdownShortcuts = (): ShortcutTip => {
|
||||
export const useMarkdownShortcuts = (): ShortcutsInfo => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const macMarkdownShortcuts = useMacMarkdownShortcuts();
|
||||
const winMarkdownShortcuts = useWinMarkdownShortcuts();
|
||||
const isMac = environment.isBrowser && environment.isMacOs;
|
||||
return isMac ? macMarkdownShortcuts : winMarkdownShortcuts;
|
||||
return {
|
||||
title: t['Markdown Syntax'](),
|
||||
shortcuts: isMac ? macMarkdownShortcuts : winMarkdownShortcuts,
|
||||
};
|
||||
};
|
||||
|
||||
export const usePageShortcuts = (): ShortcutTip => {
|
||||
export const usePageShortcuts = (): ShortcutsInfo => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const macPageShortcuts = useMacPageKeyboardShortcuts();
|
||||
const winPageShortcuts = useWinPageKeyboardShortcuts();
|
||||
const isMac = environment.isBrowser && environment.isMacOs;
|
||||
return isMac ? macPageShortcuts : winPageShortcuts;
|
||||
return {
|
||||
title: t['Page'](),
|
||||
shortcuts: isMac ? macPageShortcuts : winPageShortcuts,
|
||||
};
|
||||
};
|
||||
|
||||
export const useEdgelessShortcuts = (): ShortcutTip => {
|
||||
export const useEdgelessShortcuts = (): ShortcutsInfo => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const macEdgelessShortcuts = useMacEdgelessKeyboardShortcuts();
|
||||
const winEdgelessShortcuts = useWinEdgelessKeyboardShortcuts();
|
||||
const isMac = environment.isBrowser && environment.isMacOs;
|
||||
|
||||
return isMac ? macEdgelessShortcuts : winEdgelessShortcuts;
|
||||
return {
|
||||
title: t['Edgeless'](),
|
||||
shortcuts: isMac ? macEdgelessShortcuts : winEdgelessShortcuts,
|
||||
};
|
||||
};
|
||||
|
||||
export const useGeneralShortcuts = (): ShortcutTip => {
|
||||
export const useGeneralShortcuts = (): ShortcutsInfo => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const macGeneralShortcuts = useMacGeneralKeyboardShortcuts();
|
||||
const winGeneralShortcuts = useWinGeneralKeyboardShortcuts();
|
||||
const isMac = environment.isBrowser && environment.isMacOs;
|
||||
|
||||
return isMac ? macGeneralShortcuts : winGeneralShortcuts;
|
||||
return {
|
||||
title: t['General'](),
|
||||
shortcuts: isMac ? macGeneralShortcuts : winGeneralShortcuts,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,12 +5,14 @@ import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { LocalAdapter } from '../adapters/local';
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
import { setPageModeAtom } from '../atoms';
|
||||
|
||||
const logger = new DebugLogger('use-workspaces');
|
||||
|
||||
@@ -52,7 +54,12 @@ export function useAppHelper() {
|
||||
id,
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
await buildShowcaseWorkspace(blockSuiteWorkspace);
|
||||
await buildShowcaseWorkspace(blockSuiteWorkspace, {
|
||||
store: getCurrentStore(),
|
||||
atoms: {
|
||||
pageMode: setPageModeAtom,
|
||||
},
|
||||
});
|
||||
}
|
||||
set(workspaces => [
|
||||
...workspaces,
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { WorkspaceFallback } from '@affine/component/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { StrictMode, Suspense } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { bootstrapPluginSystem } from './bootstrap/register-plugins';
|
||||
|
||||
async function main() {
|
||||
const { setup } = await import('./bootstrap/setup');
|
||||
await setup();
|
||||
const rootStore = getCurrentStore();
|
||||
await setup(rootStore);
|
||||
bootstrapPluginSystem(rootStore).catch(err => {
|
||||
console.error('Failed to bootstrap plugin system', err);
|
||||
});
|
||||
const { App } = await import('./app');
|
||||
const root = document.getElementById('app');
|
||||
assertExists(root);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Content, displayFlex } from '@affine/component';
|
||||
import { appSidebarResizingAtom } from '@affine/component/app-sidebar';
|
||||
import {
|
||||
AppSidebarFallback,
|
||||
appSidebarResizingAtom,
|
||||
} from '@affine/component/app-sidebar';
|
||||
import { BlockHubWrapper } from '@affine/component/block-hub';
|
||||
import { NotificationCenter } from '@affine/component/notification-center';
|
||||
import type { DraggableTitleCellData } from '@affine/component/page-list';
|
||||
@@ -135,11 +138,9 @@ export const WorkspaceLayout = function WorkspacesSuspense({
|
||||
</CurrentWorkspaceContext>
|
||||
</Suspense>
|
||||
<CurrentWorkspaceContext>
|
||||
<Suspense fallback={<WorkspaceFallback />}>
|
||||
<Provider>
|
||||
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
|
||||
</Provider>
|
||||
</Suspense>
|
||||
<Provider>
|
||||
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
|
||||
</Provider>
|
||||
</CurrentWorkspaceContext>
|
||||
</>
|
||||
);
|
||||
@@ -231,30 +232,34 @@ export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => {
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<AppContainer resizing={resizing}>
|
||||
<RootAppSidebar
|
||||
isPublicWorkspace={false}
|
||||
onOpenQuickSearchModal={handleOpenQuickSearchModal}
|
||||
onOpenSettingModal={handleOpenSettingModal}
|
||||
currentWorkspace={currentWorkspace}
|
||||
onOpenWorkspaceListModal={handleOpenWorkspaceListModal}
|
||||
openPage={useCallback(
|
||||
(pageId: string) => {
|
||||
assertExists(currentWorkspace);
|
||||
return openPage(currentWorkspace.id, pageId);
|
||||
},
|
||||
[currentWorkspace, openPage]
|
||||
)}
|
||||
createPage={handleCreatePage}
|
||||
currentPath={location.pathname.split('?')[0]}
|
||||
paths={pathGenerator}
|
||||
/>
|
||||
<MainContainer padding={appSetting.clientBorder}>
|
||||
{children}
|
||||
<ToolContainer>
|
||||
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
|
||||
<HelpIsland showList={pageId ? undefined : showList} />
|
||||
</ToolContainer>
|
||||
</MainContainer>
|
||||
<Suspense fallback={<AppSidebarFallback />}>
|
||||
<RootAppSidebar
|
||||
isPublicWorkspace={false}
|
||||
onOpenQuickSearchModal={handleOpenQuickSearchModal}
|
||||
onOpenSettingModal={handleOpenSettingModal}
|
||||
currentWorkspace={currentWorkspace}
|
||||
onOpenWorkspaceListModal={handleOpenWorkspaceListModal}
|
||||
openPage={useCallback(
|
||||
(pageId: string) => {
|
||||
assertExists(currentWorkspace);
|
||||
return openPage(currentWorkspace.id, pageId);
|
||||
},
|
||||
[currentWorkspace, openPage]
|
||||
)}
|
||||
createPage={handleCreatePage}
|
||||
currentPath={location.pathname.split('?')[0]}
|
||||
paths={pathGenerator}
|
||||
/>
|
||||
</Suspense>
|
||||
<Suspense fallback={<MainContainer />}>
|
||||
<MainContainer padding={appSetting.clientBorder}>
|
||||
{children}
|
||||
<ToolContainer>
|
||||
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
|
||||
<HelpIsland showList={pageId ? undefined : showList} />
|
||||
</ToolContainer>
|
||||
</MainContainer>
|
||||
</Suspense>
|
||||
</AppContainer>
|
||||
<PageListTitleCellDragOverlay />
|
||||
</DndContext>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DebugLogger } from '@affine/debug';
|
||||
import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { lazy } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
@@ -16,6 +16,7 @@ const AllWorkspaceModals = lazy(() =>
|
||||
const logger = new DebugLogger('index-page');
|
||||
|
||||
export const loader: LoaderFunction = async () => {
|
||||
const rootStore = getCurrentStore();
|
||||
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
|
||||
const lastId = localStorage.getItem('last_workspace_id');
|
||||
const lastPageId = localStorage.getItem('last_page_id');
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DEFAULT_HELLO_WORLD_PAGE_ID_SUFFIX } from '@affine/env/constant';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { useCallback } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
@@ -13,6 +13,7 @@ import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
|
||||
export const loader: LoaderFunction = async args => {
|
||||
const rootStore = getCurrentStore();
|
||||
const workspaceId = args.params.workspaceId;
|
||||
assertExists(workspaceId);
|
||||
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
|
||||
|
||||
@@ -8,17 +8,20 @@ import type { EditorContainer } from '@blocksuite/editor';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import {
|
||||
contentLayoutAtom,
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
getCurrentStore,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { type ReactElement, useCallback } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
import { getUIAdapter } from '../../adapters/workspace';
|
||||
import { setPageModeAtom } from '../../atoms';
|
||||
import { currentModeAtom } from '../../atoms/mode';
|
||||
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||
|
||||
@@ -30,8 +33,12 @@ const DetailPageImpl = (): ReactElement => {
|
||||
assertExists(currentPageId);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const collectionManager = useCollectionManager(currentWorkspace.id);
|
||||
const mode = useAtomValue(currentModeAtom);
|
||||
const setPageMode = useSetAtom(setPageModeAtom);
|
||||
|
||||
const onLoad = useCallback(
|
||||
(_: Page, editor: EditorContainer) => {
|
||||
setPageMode(currentPageId, mode);
|
||||
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
||||
return openPage(blockSuiteWorkspace.id, pageId);
|
||||
});
|
||||
@@ -48,9 +55,12 @@ const DetailPageImpl = (): ReactElement => {
|
||||
[
|
||||
blockSuiteWorkspace.id,
|
||||
collectionManager,
|
||||
currentPageId,
|
||||
currentWorkspace.id,
|
||||
jumpToSubPath,
|
||||
mode,
|
||||
openPage,
|
||||
setPageMode,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -86,6 +96,8 @@ export const DetailPage = (): ReactElement => {
|
||||
};
|
||||
|
||||
export const loader: LoaderFunction = async args => {
|
||||
const rootStore = getCurrentStore();
|
||||
rootStore.set(contentLayoutAtom, 'editor');
|
||||
if (args.params.workspaceId) {
|
||||
localStorage.setItem('last_workspace_id', args.params.workspaceId);
|
||||
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
rootStore,
|
||||
getCurrentStore,
|
||||
} from '@toeverything/infra/atom';
|
||||
import type { ReactElement } from 'react';
|
||||
import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
|
||||
@@ -10,6 +10,7 @@ import { type LoaderFunction, Outlet, redirect } from 'react-router-dom';
|
||||
import { WorkspaceLayout } from '../../layouts/workspace-layout';
|
||||
|
||||
export const loader: LoaderFunction = async args => {
|
||||
const rootStore = getCurrentStore();
|
||||
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
|
||||
if (!meta.some(({ id }) => id === args.params.workspaceId)) {
|
||||
return redirect('/404');
|
||||
|
||||
@@ -126,6 +126,7 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
);
|
||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [, startCloseTransition] = useTransition();
|
||||
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
||||
|
||||
const handleOpenSettingModal = useCallback(
|
||||
@@ -153,7 +154,9 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
isOpenCreateWorkspaceModal === false
|
||||
}
|
||||
onClose={useCallback(() => {
|
||||
setOpenWorkspacesModal(false);
|
||||
startCloseTransition(() => {
|
||||
setOpenWorkspacesModal(false);
|
||||
});
|
||||
}, [setOpenWorkspacesModal])}
|
||||
onMoveWorkspace={useCallback(
|
||||
(activeId, overId) => {
|
||||
@@ -169,10 +172,12 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
)}
|
||||
onClickWorkspace={useCallback(
|
||||
workspaceId => {
|
||||
setOpenWorkspacesModal(false);
|
||||
setCurrentWorkspaceId(workspaceId);
|
||||
setCurrentPageId(null);
|
||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||
startCloseTransition(() => {
|
||||
setOpenWorkspacesModal(false);
|
||||
setCurrentWorkspaceId(workspaceId);
|
||||
setCurrentPageId(null);
|
||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||
});
|
||||
},
|
||||
[
|
||||
jumpToSubPath,
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
|
||||
export const router = createBrowserRouter(
|
||||
[
|
||||
{
|
||||
path: '/',
|
||||
lazy: () => import('./pages/index'),
|
||||
},
|
||||
{
|
||||
path: '/workspace/:workspaceId',
|
||||
lazy: () => import('./pages/workspace/index'),
|
||||
children: [
|
||||
{
|
||||
path: 'all',
|
||||
lazy: () => import('./pages/workspace/all-page'),
|
||||
},
|
||||
{
|
||||
path: 'trash',
|
||||
lazy: () => import('./pages/workspace/trash-page'),
|
||||
},
|
||||
{
|
||||
path: ':pageId',
|
||||
lazy: () => import('./pages/workspace/detail-page'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
],
|
||||
export const routes = [
|
||||
{
|
||||
future: {
|
||||
v7_normalizeFormMethod: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
path: '/',
|
||||
lazy: () => import('./pages/index'),
|
||||
},
|
||||
{
|
||||
path: '/workspace/:workspaceId',
|
||||
lazy: () => import('./pages/workspace/index'),
|
||||
children: [
|
||||
{
|
||||
path: 'all',
|
||||
lazy: () => import('./pages/workspace/all-page'),
|
||||
},
|
||||
{
|
||||
path: 'trash',
|
||||
lazy: () => import('./pages/workspace/trash-page'),
|
||||
},
|
||||
{
|
||||
path: ':pageId',
|
||||
lazy: () => import('./pages/workspace/detail-page'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
lazy: () => import('./pages/404'),
|
||||
},
|
||||
] satisfies [RouteObject, ...RouteObject[]];
|
||||
|
||||
export const router = createBrowserRouter(routes, {
|
||||
future: {
|
||||
v7_normalizeFormMethod: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"typeRoots": ["../../node_modules", "../../node_modules/@types"],
|
||||
"types": ["webpack-env", "ses", "affine__env"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"],
|
||||
"exclude": ["node_modules"],
|
||||
"references": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/docs",
|
||||
"version": "0.8.0-canary.21",
|
||||
"version": "0.8.0-beta.3",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,12 +10,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"express": "^4.18.2",
|
||||
"jotai": "^2.3.1",
|
||||
"react": "18.3.0-canary-7118f5dd7-20230705",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vanilla-extract/css": "^1.12.0",
|
||||
"@vanilla-extract/vite-plugin": "^3.8.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ test('affine cloud disabled', async ({ page }) => {
|
||||
});
|
||||
await page.waitForSelector('v-line');
|
||||
await page.getByTestId('current-workspace').click();
|
||||
await page.getByTestId('sign-in-button').click();
|
||||
await page.getByTestId('cloud-signin-button').click();
|
||||
await page.getByTestId('disable-affine-cloud-modal').waitFor({
|
||||
state: 'visible',
|
||||
});
|
||||
@@ -153,13 +153,23 @@ test('windows only check', async ({ page }) => {
|
||||
|
||||
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('create-workspace-default-location-button').click({
|
||||
delay: 100,
|
||||
});
|
||||
await page.getByTestId('create-workspace-input').type('Delete Me', {
|
||||
delay: 100,
|
||||
});
|
||||
await page.getByTestId('create-workspace-create-button').click({
|
||||
delay: 100,
|
||||
});
|
||||
await page.getByTestId('create-workspace-continue-button').click({
|
||||
delay: 100,
|
||||
});
|
||||
await page.waitForTimeout(1000);
|
||||
await page.getByTestId('current-workspace').click();
|
||||
await page.getByTestId('workspace-card').nth(1).hover();
|
||||
await page.getByTestId('workspace-card-setting-button').nth(1).click();
|
||||
await page.getByTestId('current-workspace-label').click();
|
||||
expect(await page.getByTestId('workspace-name-input').inputValue()).toBe(
|
||||
'Delete Me'
|
||||
|
||||
@@ -1,37 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const { z } = require('zod');
|
||||
|
||||
const {
|
||||
utils: { fromBuildIdentifier },
|
||||
} = require('@electron-forge/core');
|
||||
|
||||
const path = require('node:path');
|
||||
|
||||
const ReleaseTypeSchema = z.enum(['stable', 'beta', 'canary', 'internal']);
|
||||
|
||||
const envBuildType = (process.env.BUILD_TYPE || 'canary').trim().toLowerCase();
|
||||
const buildType = ReleaseTypeSchema.parse(envBuildType);
|
||||
const stableBuild = buildType === 'stable';
|
||||
const productName = !stableBuild ? `AFFiNE-${buildType}` : 'AFFiNE';
|
||||
const icoPath = !stableBuild
|
||||
? `./resources/icons/icon_${buildType}.ico`
|
||||
: './resources/icons/icon.ico';
|
||||
const icnsPath = !stableBuild
|
||||
? `./resources/icons/icon_${buildType}.icns`
|
||||
: './resources/icons/icon.icns';
|
||||
|
||||
const arch =
|
||||
process.argv.indexOf('--arch') > 0
|
||||
? process.argv[process.argv.indexOf('--arch') + 1]
|
||||
: process.arch;
|
||||
|
||||
const platform =
|
||||
process.argv.indexOf('--platform') > 0
|
||||
? process.argv[process.argv.indexOf('--platform') + 1]
|
||||
: process.platform;
|
||||
|
||||
const windowsIconUrl = `https://cdn.affine.pro/app-icons/icon_${buildType}.ico`;
|
||||
const {
|
||||
arch,
|
||||
buildType,
|
||||
icnsPath,
|
||||
icoPath,
|
||||
platform,
|
||||
productName,
|
||||
iconUrl,
|
||||
} = require('./scripts/make-env');
|
||||
|
||||
const makers = [
|
||||
!process.env.SKIP_BUNDLE &&
|
||||
@@ -82,9 +65,9 @@ const makers = [
|
||||
!process.env.SKIP_BUNDLE && {
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
config: {
|
||||
name: 'AFFiNE',
|
||||
name: productName,
|
||||
setupIcon: icoPath,
|
||||
iconUrl: windowsIconUrl,
|
||||
iconUrl: iconUrl,
|
||||
loadingGif: './resources/icons/affine_installing.gif',
|
||||
},
|
||||
},
|
||||
@@ -130,6 +113,7 @@ module.exports = {
|
||||
: undefined,
|
||||
// We need the following line for updater
|
||||
extraResource: ['./resources/app-update.yml'],
|
||||
ignore: ['e2e', 'tests'],
|
||||
},
|
||||
makers,
|
||||
hooks: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.21",
|
||||
"version": "0.8.0-beta.3",
|
||||
"author": "affine",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -17,7 +17,8 @@
|
||||
"generate-assets": "zx scripts/generate-assets.mjs",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"test": "DEBUG=pw:browser yarn -T run playwright test -c ./playwright.config.ts"
|
||||
"test": "DEBUG=pw:browser yarn -T run playwright test -c ./playwright.config.ts",
|
||||
"make-squirrel": "yarn ts-node-esm -T scripts/make-squirrel.mts"
|
||||
},
|
||||
"config": {
|
||||
"forge": "./forge.config.js"
|
||||
@@ -28,27 +29,27 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@electron-forge/cli": "^6.3.0",
|
||||
"@electron-forge/core": "^6.3.0",
|
||||
"@electron-forge/core-utils": "^6.3.0",
|
||||
"@electron-forge/maker-deb": "^6.3.0",
|
||||
"@electron-forge/maker-squirrel": "^6.3.0",
|
||||
"@electron-forge/maker-zip": "^6.3.0",
|
||||
"@electron-forge/shared-types": "^6.3.0",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@electron-forge/cli": "^6.4.0",
|
||||
"@electron-forge/core": "^6.4.0",
|
||||
"@electron-forge/core-utils": "^6.4.0",
|
||||
"@electron-forge/maker-deb": "^6.4.0",
|
||||
"@electron-forge/maker-squirrel": "^6.4.0",
|
||||
"@electron-forge/maker-zip": "^6.4.0",
|
||||
"@electron-forge/shared-types": "^6.4.0",
|
||||
"@electron/remote": "2.0.10",
|
||||
"@reforged/maker-appimage": "^3.3.1",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "^25.5.0",
|
||||
"electron": "^26.0.0",
|
||||
"electron-log": "^5.0.0-beta.25",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esbuild": "^0.18.20",
|
||||
"esbuild": "^0.19.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"jotai": "^2.3.1",
|
||||
"ts-node": "^10.9.1",
|
||||
@@ -62,7 +63,7 @@
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"electron-updater": "^6.1.4",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"link-preview-js": "^3.0.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import { spawn } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import electronPath from 'electron';
|
||||
import * as esbuild from 'esbuild';
|
||||
|
||||
import { config, electronDir } from './common.mjs';
|
||||
import { config } from './common.mjs';
|
||||
|
||||
// this means we don't spawn electron windows, mainly for testing
|
||||
const watchMode = process.argv.includes('--watch');
|
||||
@@ -19,20 +17,6 @@ const stderrFilterPatterns = [
|
||||
/ExtensionLoadWarning/,
|
||||
];
|
||||
|
||||
// these are set before calling `config`, so we have a chance to override them
|
||||
try {
|
||||
const devJson = readFileSync(
|
||||
path.resolve(electronDir, './dev.json'),
|
||||
'utf-8'
|
||||
);
|
||||
const devEnv = JSON.parse(devJson);
|
||||
Object.assign(process.env, devEnv);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`Could not read dev.json. Some functions may not work as expected.`
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {ChildProcessWithoutNullStreams | null} */
|
||||
let spawnProcess = null;
|
||||
|
||||
|
||||
@@ -37,12 +37,14 @@ if (process.platform === 'win32') {
|
||||
$.prefix = '';
|
||||
}
|
||||
|
||||
$.env.DISTRIBUTION = 'desktop';
|
||||
|
||||
cd(repoRootDir);
|
||||
|
||||
// step 1: build web (nextjs) dist
|
||||
if (!process.env.SKIP_WEB_BUILD) {
|
||||
await $`yarn -T run build:plugins`;
|
||||
await $`DISTRIBUTION=desktop yarn nx build @affine/core`;
|
||||
await $`yarn nx build @affine/core`;
|
||||
await fs.move(affineCoreOutDir, publicAffineOutDir, { overwrite: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { readdir } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const outputRoot = fileURLToPath(
|
||||
new URL(
|
||||
|
||||
49
apps/electron/scripts/make-env.js
Normal file
49
apps/electron/scripts/make-env.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const { z } = require('zod');
|
||||
|
||||
const path = require('node:path');
|
||||
|
||||
const ReleaseTypeSchema = z.enum(['stable', 'beta', 'canary', 'internal']);
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
const envBuildType = (process.env.BUILD_TYPE || 'canary').trim().toLowerCase();
|
||||
const buildType = ReleaseTypeSchema.parse(envBuildType);
|
||||
const stableBuild = buildType === 'stable';
|
||||
const productName = !stableBuild ? `AFFiNE-${buildType}` : 'AFFiNE';
|
||||
const icoPath = path.join(
|
||||
ROOT,
|
||||
!stableBuild
|
||||
? `./resources/icons/icon_${buildType}.ico`
|
||||
: './resources/icons/icon.ico'
|
||||
);
|
||||
const icnsPath = path.join(
|
||||
ROOT,
|
||||
!stableBuild
|
||||
? `./resources/icons/icon_${buildType}.icns`
|
||||
: './resources/icons/icon.icns'
|
||||
);
|
||||
|
||||
const iconUrl = `https://cdn.affine.pro/app-icons/icon_${buildType}.ico`;
|
||||
const arch =
|
||||
process.argv.indexOf('--arch') > 0
|
||||
? process.argv[process.argv.indexOf('--arch') + 1]
|
||||
: process.arch;
|
||||
|
||||
const platform =
|
||||
process.argv.indexOf('--platform') > 0
|
||||
? process.argv[process.argv.indexOf('--platform') + 1]
|
||||
: process.platform;
|
||||
|
||||
module.exports = {
|
||||
ROOT,
|
||||
buildType,
|
||||
productName,
|
||||
icoPath,
|
||||
icnsPath,
|
||||
iconUrl,
|
||||
arch,
|
||||
platform,
|
||||
stableBuild,
|
||||
};
|
||||
82
apps/electron/scripts/make-squirrel.mts
Normal file
82
apps/electron/scripts/make-squirrel.mts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { Options as ElectronWinstallerOptions } from 'electron-winstaller';
|
||||
import { convertVersion, createWindowsInstaller } from 'electron-winstaller';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
arch,
|
||||
buildType,
|
||||
iconUrl,
|
||||
platform,
|
||||
productName,
|
||||
ROOT,
|
||||
} from './make-env';
|
||||
|
||||
async function ensureDirectory(dir: string) {
|
||||
if (await fs.pathExists(dir)) {
|
||||
await fs.remove(dir);
|
||||
}
|
||||
return fs.mkdirs(dir);
|
||||
}
|
||||
|
||||
// taking from https://github.com/electron/forge/blob/main/packages/maker/squirrel/src/MakerSquirrel.ts
|
||||
// it was for forge's maker, but can be used standalone as well
|
||||
async function make() {
|
||||
const appName = productName;
|
||||
const makeDir = path.resolve(ROOT, 'out', buildType, 'make');
|
||||
const outPath = path.resolve(makeDir, `squirrel.windows/${arch}`);
|
||||
const appDirectory = path.resolve(
|
||||
ROOT,
|
||||
'out',
|
||||
buildType,
|
||||
`${appName}-${platform}-${arch}`
|
||||
);
|
||||
await ensureDirectory(outPath);
|
||||
|
||||
const packageJSON = await fs.readJson(path.resolve(ROOT, 'package.json'));
|
||||
|
||||
const winstallerConfig: ElectronWinstallerOptions = {
|
||||
name: appName,
|
||||
title: appName,
|
||||
noMsi: true,
|
||||
exe: `${appName}.exe`,
|
||||
setupExe: `${appName}-${packageJSON.version} Setup.exe`,
|
||||
version: packageJSON.version,
|
||||
appDirectory: appDirectory,
|
||||
outputDirectory: outPath,
|
||||
iconUrl: iconUrl,
|
||||
loadingGif: path.resolve(ROOT, './resources/icons/affine_installing.gif'),
|
||||
};
|
||||
|
||||
await createWindowsInstaller(winstallerConfig);
|
||||
const nupkgVersion = convertVersion(packageJSON.version);
|
||||
const artifacts = [
|
||||
path.resolve(outPath, 'RELEASES'),
|
||||
path.resolve(outPath, winstallerConfig.setupExe || `${appName}Setup.exe`),
|
||||
path.resolve(
|
||||
outPath,
|
||||
`${winstallerConfig.name}-${nupkgVersion}-full.nupkg`
|
||||
),
|
||||
];
|
||||
const deltaPath = path.resolve(
|
||||
outPath,
|
||||
`${winstallerConfig.name}-${nupkgVersion}-delta.nupkg`
|
||||
);
|
||||
if (
|
||||
(winstallerConfig.remoteReleases && !winstallerConfig.noDelta) ||
|
||||
(await fs.pathExists(deltaPath))
|
||||
) {
|
||||
artifacts.push(deltaPath);
|
||||
}
|
||||
const msiPath = path.resolve(
|
||||
outPath,
|
||||
winstallerConfig.setupMsi || `${appName}Setup.msi`
|
||||
);
|
||||
if (!winstallerConfig.noMsi && (await fs.pathExists(msiPath))) {
|
||||
artifacts.push(msiPath);
|
||||
}
|
||||
console.log('making squirrel.windows done:', artifacts);
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
make();
|
||||
@@ -91,7 +91,8 @@ test('db should be removed in db$Map after destroyed', async () => {
|
||||
expect(db$Map.has(workspaceId)).toBe(false);
|
||||
});
|
||||
|
||||
test('if db has a secondary db path, we should also poll that', async () => {
|
||||
// we have removed secondary db feature
|
||||
test.skip('if db has a secondary db path, we should also poll that', async () => {
|
||||
const { ensureSQLiteDB } = await import('../ensure-db');
|
||||
const { storeWorkspaceMeta } = await import('../../workspace');
|
||||
const workspaceId = v4();
|
||||
|
||||
@@ -44,10 +44,10 @@ export abstract class BaseSQLiteAdapter {
|
||||
try {
|
||||
if (!this.db) {
|
||||
logger.warn(`${this.path} is not connected`);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
const blob = await this.db.getBlob(key);
|
||||
return blob?.data;
|
||||
return blob?.data ?? null;
|
||||
} catch (error) {
|
||||
logger.error('getBlob', error);
|
||||
return null;
|
||||
|
||||
@@ -128,7 +128,7 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
||||
if (doc) {
|
||||
return encodeStateAsUpdate(doc);
|
||||
}
|
||||
return null;
|
||||
return false;
|
||||
};
|
||||
|
||||
// non-blocking and use yDoc to validate the update
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import type {
|
||||
DBHandlers,
|
||||
DialogHandlers,
|
||||
WorkspaceHandlers,
|
||||
} from '@toeverything/infra/type';
|
||||
|
||||
import { dbEvents, dbHandlers } from './db';
|
||||
import { dialogHandlers } from './dialog';
|
||||
import { workspaceEvents, workspaceHandlers } from './workspace';
|
||||
|
||||
type AllHandlers = {
|
||||
db: DBHandlers;
|
||||
workspace: WorkspaceHandlers;
|
||||
dialog: DialogHandlers;
|
||||
};
|
||||
|
||||
export const handlers = {
|
||||
db: dbHandlers,
|
||||
workspace: workspaceHandlers,
|
||||
dialog: dialogHandlers,
|
||||
};
|
||||
} satisfies AllHandlers;
|
||||
|
||||
export const events = {
|
||||
db: dbEvents,
|
||||
|
||||
@@ -34,7 +34,7 @@ async function createWindow() {
|
||||
: isWindows()
|
||||
? 'hidden'
|
||||
: 'default',
|
||||
trafficLightPosition: { x: 24, y: 18 },
|
||||
trafficLightPosition: { x: 20, y: 18 },
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
@@ -98,6 +98,13 @@ async function createWindow() {
|
||||
// TODO: gracefully close the app, for example, ask user to save unsaved changes
|
||||
});
|
||||
|
||||
browserWindow.on('leave-full-screen', () => {
|
||||
// FIXME: workaround for theme bug in full screen mode
|
||||
const size = browserWindow.getSize();
|
||||
browserWindow.setSize(size[0] + 1, size[1] + 1);
|
||||
browserWindow.setSize(size[0], size[1]);
|
||||
});
|
||||
|
||||
/**
|
||||
* URL for main window.
|
||||
*/
|
||||
|
||||
@@ -35,8 +35,8 @@ export const checkForUpdates = async (force = true) => {
|
||||
};
|
||||
|
||||
export const registerUpdater = async () => {
|
||||
// skip auto update in dev mode
|
||||
if (isDev) {
|
||||
// skip auto update in dev mode & internal
|
||||
if (isDev || buildType === 'internal') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export const registerUpdater = async () => {
|
||||
const feedUrl: Parameters<typeof autoUpdater.setFeedURL>[0] = {
|
||||
channel: buildType,
|
||||
provider: 'github',
|
||||
// @ts-expect-error - just ignore for now
|
||||
repo: buildType !== 'internal' ? 'AFFiNE' : 'AFFiNE-Releases',
|
||||
owner: 'toeverything',
|
||||
releaseType: buildType === 'stable' ? 'release' : 'prerelease',
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noEmit": false,
|
||||
"outDir": "./lib/scripts"
|
||||
"outDir": "./lib/scripts",
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["./scripts", "esbuild.main.config.ts", "esbuild.plugin.config.ts"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/prototype",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.21",
|
||||
"version": "0.8.0-beta.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host --port 3003",
|
||||
@@ -18,13 +18,13 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230811201552-f37162ea-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@toeverything/hooks": "workspace:*",
|
||||
"@toeverything/y-indexeddb": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -25,11 +25,11 @@ const App = () => {
|
||||
<button
|
||||
data-testid="start-button"
|
||||
onClick={useCallback(() => {
|
||||
disposeRef.current = setInterval(() => {
|
||||
disposeRef.current = window.setInterval(() => {
|
||||
const counter = counterRef.current;
|
||||
map.set('counter', counter + 1);
|
||||
counterRef.current = counter + 1;
|
||||
}, 0) as any;
|
||||
}, 0);
|
||||
}, [])}
|
||||
>
|
||||
start writing
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.8.0-canary.21",
|
||||
"version": "0.8.0-beta.3",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -18,7 +18,7 @@
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.9.1",
|
||||
"@auth/prisma-adapter": "^1.0.1",
|
||||
"@aws-sdk/client-s3": "^3.388.0",
|
||||
"@aws-sdk/client-s3": "^3.391.0",
|
||||
"@nestjs/apollo": "^12.0.7",
|
||||
"@nestjs/common": "^10.1.3",
|
||||
"@nestjs/core": "^10.1.3",
|
||||
@@ -31,11 +31,11 @@
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"graphql": "^16.7.1",
|
||||
"graphql": "^16.8.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"graphql-upload": "^16.0.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"next-auth": "4.22.1",
|
||||
"next-auth": "4.22.5",
|
||||
"nodemailer": "^6.9.4",
|
||||
"parse-duration": "^1.1.0",
|
||||
"prisma": "^5.1.1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^18.17.4",
|
||||
"@types/node": "^18.17.5",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"c8": "^8.0.1",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { mergeConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
||||
import { getRuntimeConfig } from '../../core/.webpack/runtime-config';
|
||||
import turbosnap from 'vite-plugin-turbosnap';
|
||||
|
||||
runCli(
|
||||
{
|
||||
@@ -28,11 +29,12 @@ export default {
|
||||
'@storybook/addon-interactions',
|
||||
'@storybook/addon-storysource',
|
||||
'storybook-dark-mode',
|
||||
'storybook-addon-react-router-v6',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
},
|
||||
async viteFinal(config, _) {
|
||||
async viteFinal(config, { configType }) {
|
||||
return mergeConfig(config, {
|
||||
assetsInclude: ['**/*.md'],
|
||||
plugins: [
|
||||
@@ -40,6 +42,11 @@ export default {
|
||||
tsconfigPaths({
|
||||
root: fileURLToPath(new URL('../../../', import.meta.url)),
|
||||
}),
|
||||
configType === 'PRODUCTION'
|
||||
? turbosnap({
|
||||
rootDir: fileURLToPath(new URL('../../../', import.meta.url)),
|
||||
})
|
||||
: null,
|
||||
],
|
||||
define: {
|
||||
'process.env': {},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user