mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-08 10:33:44 +00:00
Compare commits
39 Commits
v0.8.0-bet
...
v0.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4543ef3b7 | ||
|
|
b7f024eeb2 | ||
|
|
ebc98002ba | ||
|
|
9a89c08fd1 | ||
|
|
d749e8a284 | ||
|
|
0b54d82ddb | ||
|
|
a76d99381d | ||
|
|
d89be5804f | ||
|
|
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 |
@@ -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
|
||||
238
.github/workflows/build.yml
vendored
238
.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
|
||||
@@ -239,7 +220,6 @@ jobs:
|
||||
name: E2E Plugin Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
@@ -247,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
|
||||
@@ -328,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
|
||||
@@ -337,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 }}
|
||||
@@ -373,7 +341,6 @@ jobs:
|
||||
name: E2E Migration Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
strategy:
|
||||
matrix:
|
||||
spec:
|
||||
@@ -387,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 }}
|
||||
@@ -409,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
|
||||
@@ -552,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 }}
|
||||
|
||||
45
.github/workflows/nightly-build.yml
vendored
45
.github/workflows/nightly-build.yml
vendored
@@ -73,34 +73,27 @@ 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
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
needs:
|
||||
- before-make
|
||||
- set-build-version
|
||||
|
||||
212
.github/workflows/release-desktop-app.yml
vendored
212
.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,8 +360,6 @@ jobs:
|
||||
./*.zip
|
||||
./*.dmg
|
||||
./*.exe
|
||||
./*.nupkg
|
||||
./RELEASES
|
||||
./*.AppImage
|
||||
./*.apk
|
||||
./*.yml
|
||||
|
||||
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
|
||||
@@ -30,7 +30,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enableTestProperties: false,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0818',
|
||||
changelogUrl: 'https://affine.pro/blog/affine-080-launch-week-day5',
|
||||
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
"name": "@affine/core",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"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",
|
||||
@@ -24,13 +24,13 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-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-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-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",
|
||||
@@ -39,7 +39,7 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.14.5",
|
||||
"@react-hookz/web": "^23.1.0",
|
||||
"@toeverything/components": "^0.0.11",
|
||||
"@toeverything/components": "^0.0.12",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"cmdk": "^0.2.0",
|
||||
"css-spring": "^4.1.0",
|
||||
@@ -84,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,9 @@ 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 { rootStore } from '@toeverything/infra/atom';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { setPageModeAtom } from '../../atoms';
|
||||
import {
|
||||
@@ -46,7 +47,7 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||
if (runtimeConfig.enablePreloading) {
|
||||
buildShowcaseWorkspace(blockSuiteWorkspace, {
|
||||
store: rootStore,
|
||||
store: getCurrentStore(),
|
||||
atoms: {
|
||||
pageMode: setPageModeAtom,
|
||||
},
|
||||
@@ -91,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,20 +18,22 @@ 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,
|
||||
@@ -39,7 +41,11 @@ const pushLayoutAtom = atom<
|
||||
[
|
||||
pluginName: string,
|
||||
create: (root: HTMLElement) => () => void,
|
||||
options: { maxWidth: (number | undefined)[] } | undefined,
|
||||
options:
|
||||
| {
|
||||
maxWidth: (number | undefined)[];
|
||||
}
|
||||
| undefined,
|
||||
],
|
||||
void
|
||||
>(null, (_, set, pluginName, callback, options) => {
|
||||
@@ -106,438 +112,469 @@ const deleteLayoutAtom = atom<null, [string], void>(null, (_, set, id) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 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'),
|
||||
'@blocksuite/blocks': import('@blocksuite/blocks'),
|
||||
'@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,107 +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) => {
|
||||
const loadedAssetName = `${pluginName}_${asset}`;
|
||||
// todo(himself65): add assets into shadow dom
|
||||
if (loadedAssets.has(loadedAssetName)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
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);
|
||||
});
|
||||
// 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,18 +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);
|
||||
}
|
||||
|
||||
let isSetup = false;
|
||||
|
||||
export async function setup() {
|
||||
if (isSetup) {
|
||||
console.warn('already setup');
|
||||
return;
|
||||
}
|
||||
isSetup = true;
|
||||
rootStore.set(
|
||||
export async function setup(store: ReturnType<typeof createStore>) {
|
||||
store.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
@@ -188,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"
|
||||
|
||||
@@ -82,7 +82,7 @@ export const WorkspaceDeleteModal = ({
|
||||
<Input
|
||||
ref={ref => {
|
||||
if (ref) {
|
||||
setTimeout(() => ref.focus(), 0);
|
||||
window.setTimeout(() => ref.focus(), 0);
|
||||
}
|
||||
}}
|
||||
onChange={setDeleteStr}
|
||||
|
||||
@@ -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,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,7 +13,7 @@ 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';
|
||||
@@ -103,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);
|
||||
@@ -122,7 +123,7 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
return () => {
|
||||
dispose();
|
||||
clearTimeout(renderTimeout);
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
disposes.forEach(dispose => dispose());
|
||||
});
|
||||
};
|
||||
@@ -164,7 +165,7 @@ const PluginContentAdapter = memo<PluginContentAdapterProps>(
|
||||
};
|
||||
const dispose = addCleanup(pluginName, cl);
|
||||
abortController.signal.addEventListener('abort', () => {
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
dispose();
|
||||
cl();
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Menu, MenuItem, Tooltip } from '@affine/component';
|
||||
import { Menu, MenuItem } from '@affine/component';
|
||||
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||
import type {
|
||||
AffineCloudWorkspace,
|
||||
@@ -9,7 +9,6 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
CloudWorkspaceIcon,
|
||||
HelpIcon,
|
||||
ImportIcon,
|
||||
MoreHorizontalIcon,
|
||||
PlusIcon,
|
||||
@@ -27,7 +26,6 @@ import {
|
||||
StyledCreateWorkspaceCardPill,
|
||||
StyledCreateWorkspaceCardPillContent,
|
||||
StyledCreateWorkspaceCardPillIcon,
|
||||
StyledHelperContainer,
|
||||
StyledImportWorkspaceCardPill,
|
||||
StyledModalBody,
|
||||
StyledModalContent,
|
||||
@@ -61,14 +59,14 @@ const AccountMenu = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>Unlimted</div>
|
||||
<Divider></Divider>
|
||||
<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></Divider>
|
||||
<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>
|
||||
@@ -93,15 +91,6 @@ const CloudWorkSpaceList = ({
|
||||
<StyledModalTitle>
|
||||
{t['com.affine.workspace.cloud.sync']()}
|
||||
</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t['Workspace description']()}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
<StyledHelperContainer>
|
||||
<HelpIcon />
|
||||
</StyledHelperContainer>
|
||||
</Tooltip>
|
||||
</StyledModalHeaderLeft>
|
||||
|
||||
<StyledOperationWrapper>
|
||||
@@ -140,7 +129,7 @@ const CloudWorkSpaceList = ({
|
||||
[onMoveWorkspace]
|
||||
)}
|
||||
/>
|
||||
<Divider />
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
</StyledModalContent>
|
||||
</>
|
||||
);
|
||||
@@ -211,7 +200,7 @@ export const WorkspaceListModal = ({
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</MenuItem>
|
||||
</StyledSignInCardPill>
|
||||
<Divider />
|
||||
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
|
||||
</StyledModalHeaderContent>
|
||||
<StyledModalBody>
|
||||
{isLoggedIn ? (
|
||||
@@ -230,15 +219,6 @@ export const WorkspaceListModal = ({
|
||||
) : null}
|
||||
<StyledModalHeader>
|
||||
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t['Workspace description']()}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
<StyledHelperContainer>
|
||||
<HelpIcon />
|
||||
</StyledHelperContainer>
|
||||
</Tooltip>
|
||||
</StyledModalHeader>
|
||||
<StyledModalContent>
|
||||
<WorkspaceList
|
||||
|
||||
@@ -29,7 +29,11 @@ export const ReferencePage = ({
|
||||
const icon = setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
|
||||
const references = useBlockSuitePageReferences(workspace, pageId);
|
||||
const referencesToShow = useMemo(() => {
|
||||
return [...new Set(references.filter(ref => !metaMapping[ref]?.trash))];
|
||||
return [
|
||||
...new Set(
|
||||
references.filter(ref => metaMapping[ref] && !metaMapping[ref]?.trash)
|
||||
),
|
||||
];
|
||||
}, [references, metaMapping]);
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const collapsible = referencesToShow.length > 0;
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 { rootStore } from '@toeverything/infra/atom';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
@@ -55,7 +55,7 @@ export function useAppHelper() {
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
await buildShowcaseWorkspace(blockSuiteWorkspace, {
|
||||
store: rootStore,
|
||||
store: getCurrentStore(),
|
||||
atoms: {
|
||||
pageMode: setPageModeAtom,
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -12,14 +12,16 @@ import {
|
||||
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';
|
||||
|
||||
@@ -31,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);
|
||||
});
|
||||
@@ -49,9 +55,12 @@ const DetailPageImpl = (): ReactElement => {
|
||||
[
|
||||
blockSuiteWorkspace.id,
|
||||
collectionManager,
|
||||
currentPageId,
|
||||
currentWorkspace.id,
|
||||
jumpToSubPath,
|
||||
mode,
|
||||
openPage,
|
||||
setPageMode,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -87,6 +96,7 @@ 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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/docs",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,12 +10,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-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",
|
||||
|
||||
@@ -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 &&
|
||||
@@ -84,7 +67,7 @@ const makers = [
|
||||
config: {
|
||||
name: productName,
|
||||
setupIcon: icoPath,
|
||||
iconUrl: windowsIconUrl,
|
||||
iconUrl: iconUrl,
|
||||
loadingGif: './resources/icons/affine_installing.gif',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/electron",
|
||||
"private": true,
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"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,17 +29,17 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-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",
|
||||
@@ -50,6 +51,7 @@
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esbuild": "^0.19.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"glob": "^10.3.3",
|
||||
"jotai": "^2.3.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"undici": "^5.23.0",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// do not run in your local machine
|
||||
/* eslint-disable */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
/* eslint-enable */
|
||||
|
||||
const yml = {
|
||||
@@ -10,18 +10,10 @@ const yml = {
|
||||
files: [],
|
||||
};
|
||||
|
||||
let fileList = [];
|
||||
// TODO: maybe add `beta` and `stable`
|
||||
const BUILD_TYPE = process.env.BUILD_TYPE || 'canary';
|
||||
|
||||
const generateYml = async () => {
|
||||
fileList = [
|
||||
`affine-${BUILD_TYPE}-macos-arm64.dmg`,
|
||||
`affine-${BUILD_TYPE}-macos-arm64.zip`,
|
||||
`affine-${BUILD_TYPE}-macos-x64.zip`,
|
||||
`affine-${BUILD_TYPE}-macos-x64.dmg`,
|
||||
];
|
||||
fileList.forEach(fileName => {
|
||||
const generateYml = platform => {
|
||||
const regex = new RegExp(`^affine-.*-${platform}-.*.(exe|zip|dmg|AppImage)$`);
|
||||
const files = fs.readdirSync(__dirname).filter(file => regex.test(file));
|
||||
files.forEach(fileName => {
|
||||
const filePath = path.join(__dirname, './', fileName);
|
||||
try {
|
||||
const fileData = fs.readFileSync(filePath);
|
||||
@@ -58,6 +50,9 @@ const generateYml = async () => {
|
||||
`sha512: ${yml.sha512}\n` +
|
||||
`releaseDate: ${yml.releaseDate}\n`;
|
||||
|
||||
fs.writeFileSync(`./latest-mac.yml`, ymlStr);
|
||||
const fileName = platform === 'windows' ? 'latest.yml' : 'latest-mac.yml';
|
||||
|
||||
fs.writeFileSync(fileName, ymlStr);
|
||||
};
|
||||
generateYml();
|
||||
generateYml('windows');
|
||||
generateYml('macos');
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
84
apps/electron/scripts/make-squirrel.mts
Normal file
84
apps/electron/scripts/make-squirrel.mts
Normal file
@@ -0,0 +1,84 @@
|
||||
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,
|
||||
icoPath,
|
||||
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,
|
||||
setupIcon: icoPath,
|
||||
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();
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@ import { app } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { isMacOS } from '../../shared/utils';
|
||||
import { isMacOS, isWindows } from '../../shared/utils';
|
||||
import { logger } from '../logger';
|
||||
import { updaterSubjects } from './event';
|
||||
|
||||
@@ -40,8 +40,8 @@ export const registerUpdater = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: support auto update on windows and linux
|
||||
const allowAutoUpdate = isMacOS();
|
||||
// TODO: support auto update on linux
|
||||
const allowAutoUpdate = isMacOS() || isWindows();
|
||||
|
||||
autoUpdater.logger = logger;
|
||||
autoUpdater.autoDownload = false;
|
||||
|
||||
@@ -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-beta.0",
|
||||
"version": "0.8.0",
|
||||
"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-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-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-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-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-beta.0",
|
||||
"version": "0.8.0",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -5,14 +5,16 @@ import '@toeverything/components/style.css';
|
||||
import { createI18n } from '@affine/i18n';
|
||||
import { ThemeProvider, useTheme } from 'next-themes';
|
||||
import { useDarkMode } from 'storybook-dark-mode';
|
||||
import { setup } from '@affine/core/bootstrap/setup';
|
||||
import { AffineContext } from '@affine/component/context';
|
||||
import { use } from 'foxact/use';
|
||||
import useSWR from 'swr';
|
||||
import type { Decorator } from '@storybook/react';
|
||||
import { createStore } from 'jotai/vanilla';
|
||||
import { setup } from '@affine/core/bootstrap/setup';
|
||||
import { _setCurrentStore } from '@toeverything/infra/atom';
|
||||
import { bootstrapPluginSystem } from '@affine/core/bootstrap/register-plugins';
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
|
||||
const setupPromise = setup();
|
||||
|
||||
setupGlobal();
|
||||
export const parameters = {
|
||||
backgrounds: { disable: true },
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
@@ -50,11 +52,30 @@ const ThemeChange = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const storeMap = new Map<string, ReturnType<typeof createStore>>();
|
||||
|
||||
const withContextDecorator: Decorator = (Story, context) => {
|
||||
use(setupPromise);
|
||||
const { data: store } = useSWR(
|
||||
context.id,
|
||||
async () => {
|
||||
if (storeMap.has(context.id)) {
|
||||
return storeMap.get(context.id);
|
||||
}
|
||||
localStorage.clear();
|
||||
const store = createStore();
|
||||
_setCurrentStore(store);
|
||||
await setup(store);
|
||||
await bootstrapPluginSystem(store);
|
||||
storeMap.set(context.id, store);
|
||||
return store;
|
||||
},
|
||||
{
|
||||
suspense: true,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<AffineContext>
|
||||
<AffineContext store={store}>
|
||||
<ThemeChange />
|
||||
<Story {...context} />
|
||||
</AffineContext>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@storybook/addon-storysource": "^7.3.1",
|
||||
"@storybook/blocks": "^7.3.1",
|
||||
"@storybook/builder-vite": "^7.3.1",
|
||||
"@storybook/jest": "^0.1.0",
|
||||
"@storybook/jest": "^0.2.1",
|
||||
"@storybook/react": "^7.3.1",
|
||||
"@storybook/react-vite": "^7.3.1",
|
||||
"@storybook/test-runner": "^0.13.0",
|
||||
@@ -31,13 +31,13 @@
|
||||
"wait-on": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-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-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"chromatic": "^6.22.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
@@ -52,5 +52,5 @@
|
||||
"@blocksuite/lit": "*",
|
||||
"@blocksuite/store": "*"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { pluginRegisterPromise } from '@affine/core/bootstrap/register-plugins';
|
||||
import { routes } from '@affine/core/router';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Decorator, StoryFn } from '@storybook/react';
|
||||
import { userEvent, waitFor } from '@storybook/testing-library';
|
||||
import { use } from 'foxact/use';
|
||||
import type { StoryFn } from '@storybook/react';
|
||||
import { userEvent, waitFor, within } from '@storybook/testing-library';
|
||||
import { Outlet, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
reactRouterOutlets,
|
||||
@@ -11,11 +9,6 @@ import {
|
||||
withRouter,
|
||||
} from 'storybook-addon-react-router-v6';
|
||||
|
||||
const withCleanLocalStorage: Decorator = (Story, context) => {
|
||||
localStorage.clear();
|
||||
return <Story {...context} />;
|
||||
};
|
||||
|
||||
const FakeApp = () => {
|
||||
const location = useLocation();
|
||||
// fixme: `key` is a hack to force the storybook to re-render the outlet
|
||||
@@ -30,10 +23,9 @@ export default {
|
||||
};
|
||||
|
||||
export const Index: StoryFn = () => {
|
||||
use(pluginRegisterPromise);
|
||||
return <FakeApp />;
|
||||
};
|
||||
Index.decorators = [withRouter, withCleanLocalStorage];
|
||||
Index.decorators = [withRouter];
|
||||
Index.parameters = {
|
||||
reactRouter: reactRouterParameters({
|
||||
routing: reactRouterOutlets(routes),
|
||||
@@ -43,23 +35,28 @@ Index.parameters = {
|
||||
export const SettingPage: StoryFn = () => {
|
||||
return <FakeApp />;
|
||||
};
|
||||
SettingPage.play = async ({ canvasElement }) => {
|
||||
await waitFor(
|
||||
() => {
|
||||
assertExists(
|
||||
canvasElement.querySelector('[data-testid="settings-modal-trigger"]')
|
||||
);
|
||||
},
|
||||
{
|
||||
timeout: 5000,
|
||||
}
|
||||
);
|
||||
const settingModalBtn = canvasElement.querySelector(
|
||||
'[data-testid="settings-modal-trigger"]'
|
||||
) as Element;
|
||||
await userEvent.click(settingModalBtn);
|
||||
SettingPage.play = async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await waitFor(async () => {
|
||||
assertExists(canvasElement.querySelector('v-line'));
|
||||
});
|
||||
await step('click setting modal button', async () => {
|
||||
await userEvent.click(canvas.getByTestId('settings-modal-trigger'));
|
||||
});
|
||||
await waitFor(async () => {
|
||||
assertExists(
|
||||
document.body.querySelector('[data-testid="language-menu-button"]')
|
||||
);
|
||||
});
|
||||
await step('click language menu button', async () => {
|
||||
await userEvent.click(
|
||||
document.body.querySelector(
|
||||
'[data-testid="language-menu-button"]'
|
||||
) as HTMLElement
|
||||
);
|
||||
});
|
||||
};
|
||||
SettingPage.decorators = [withRouter, withCleanLocalStorage];
|
||||
SettingPage.decorators = [withRouter];
|
||||
SettingPage.parameters = {
|
||||
reactRouter: reactRouterParameters({
|
||||
routing: reactRouterOutlets(routes),
|
||||
@@ -69,7 +66,7 @@ SettingPage.parameters = {
|
||||
export const NotFoundPage: StoryFn = () => {
|
||||
return <FakeApp />;
|
||||
};
|
||||
NotFoundPage.decorators = [withRouter, withCleanLocalStorage];
|
||||
NotFoundPage.decorators = [withRouter];
|
||||
NotFoundPage.parameters = {
|
||||
reactRouter: reactRouterParameters({
|
||||
routing: reactRouterOutlets(routes),
|
||||
@@ -99,7 +96,7 @@ WorkspaceList.play = async ({ canvasElement }) => {
|
||||
) as Element;
|
||||
await userEvent.click(currentWorkspace);
|
||||
};
|
||||
WorkspaceList.decorators = [withRouter, withCleanLocalStorage];
|
||||
WorkspaceList.decorators = [withRouter];
|
||||
WorkspaceList.parameters = {
|
||||
reactRouter: reactRouterParameters({
|
||||
routing: reactRouterOutlets(routes),
|
||||
@@ -108,3 +105,23 @@ WorkspaceList.parameters = {
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export const SearchPage: StoryFn = () => {
|
||||
return <FakeApp />;
|
||||
};
|
||||
SearchPage.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await waitFor(async () => {
|
||||
assertExists(canvasElement.querySelector('v-line'));
|
||||
});
|
||||
await userEvent.click(canvas.getByTestId('slider-bar-quick-search-button'));
|
||||
};
|
||||
SearchPage.decorators = [withRouter];
|
||||
SearchPage.parameters = {
|
||||
reactRouter: reactRouterParameters({
|
||||
routing: reactRouterOutlets(routes),
|
||||
location: {
|
||||
path: '/',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ImagePreviewModal } from '@affine/image-preview-plugin/src/component';
|
||||
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
export default {
|
||||
@@ -53,7 +54,11 @@ export const Default = () => {
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<BlockSuiteEditor mode="page" page={page} onInit={initEmptyPage} />
|
||||
<BlockSuiteEditor
|
||||
mode="page"
|
||||
page={page}
|
||||
onInit={useCallback(async page => initEmptyPage(page), [])}
|
||||
/>
|
||||
{createPortal(
|
||||
<ImagePreviewModal pageId={page.id} workspace={page.workspace} />,
|
||||
document.body
|
||||
|
||||
@@ -91,7 +91,7 @@ Basic.play = async ({ canvasElement }) => {
|
||||
expect(button).not.toBeNull();
|
||||
button.click();
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise(resolve => window.setTimeout(resolve, 100));
|
||||
{
|
||||
const button = canvasElement.querySelector(
|
||||
'[data-testid="share-menu-enable-affine-cloud-button"]'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/monorepo",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"private": true,
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
|
||||
2
packages/@types/env/package.json
vendored
2
packages/@types/env/package.json
vendored
@@ -7,5 +7,5 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@toeverything/infra": "workspace:*"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"peerDependencies": {
|
||||
"ts-node": "*"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,41 @@ import { config } from 'dotenv';
|
||||
import { type BuildFlags, projectRoot } from '../config/index.js';
|
||||
import { watchI18N } from '../util/i18n.js';
|
||||
|
||||
const cwd = path.resolve(projectRoot, 'apps', 'core');
|
||||
|
||||
const flags: BuildFlags = {
|
||||
distribution: 'browser',
|
||||
mode: 'development',
|
||||
channel: 'canary',
|
||||
coverage: process.env.COVERAGE === 'true',
|
||||
localBlockSuite: undefined,
|
||||
};
|
||||
|
||||
if (process.argv.includes('--static')) {
|
||||
await awaitChildProcess(
|
||||
spawn(
|
||||
'node',
|
||||
[
|
||||
'--loader',
|
||||
'ts-node/esm/transpile-only',
|
||||
'../../node_modules/webpack/bin/webpack.js',
|
||||
'serve',
|
||||
'--mode',
|
||||
'development',
|
||||
'--env',
|
||||
'flags=' + Buffer.from(JSON.stringify(flags), 'utf-8').toString('hex'),
|
||||
].filter((v): v is string => !!v),
|
||||
{
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: process.env,
|
||||
}
|
||||
)
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const files = ['.env', '.env.local'];
|
||||
|
||||
for (const file of files) {
|
||||
@@ -21,16 +56,6 @@ for (const file of files) {
|
||||
}
|
||||
}
|
||||
|
||||
const cwd = path.resolve(projectRoot, 'apps', 'core');
|
||||
|
||||
const flags: BuildFlags = {
|
||||
distribution: 'browser',
|
||||
mode: 'development',
|
||||
channel: 'canary',
|
||||
coverage: false,
|
||||
localBlockSuite: undefined,
|
||||
};
|
||||
|
||||
const buildFlags = await p.group(
|
||||
{
|
||||
distribution: () =>
|
||||
|
||||
@@ -51,12 +51,12 @@
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-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-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-datepicker": "^4.15.0",
|
||||
"@types/react-dnd": "^3.0.2",
|
||||
@@ -66,5 +66,5 @@
|
||||
"vite": "^4.4.9",
|
||||
"yjs": "^13.6.7"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export type AppSidebarProps = PropsWithChildren<
|
||||
function useEnableAnimation() {
|
||||
const [enable, setEnable] = useState(false);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
setEnable(true);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { ProviderComposer } from '@affine/component/provider-composer';
|
||||
import { ThemeProvider } from '@affine/component/theme-provider';
|
||||
import { rootStore } from '@toeverything/infra/atom';
|
||||
import type { createStore } from 'jotai';
|
||||
import { Provider } from 'jotai';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function AffineContext(props: PropsWithChildren) {
|
||||
export type AffineContextProps = PropsWithChildren<{
|
||||
store?: ReturnType<typeof createStore>;
|
||||
}>;
|
||||
|
||||
export function AffineContext(props: AffineContextProps) {
|
||||
return (
|
||||
<ProviderComposer
|
||||
contexts={useMemo(
|
||||
() =>
|
||||
[
|
||||
<Provider key="JotaiProvider" store={rootStore} />,
|
||||
<Provider key="JotaiProvider" store={props.store} />,
|
||||
<ThemeProvider key="ThemeProvider" />,
|
||||
].filter(Boolean),
|
||||
[]
|
||||
[props.store]
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -80,7 +80,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
const [animationKey, setAnimationKey] = useState(0);
|
||||
const animationRef = useRef<SVGAnimateElement>(null);
|
||||
const notificationRef = useRef<HTMLLIElement>(null);
|
||||
const timerIdRef = useRef<NodeJS.Timeout>();
|
||||
const timerIdRef = useRef<number>();
|
||||
const isFront = index === 0;
|
||||
const isVisible = index + 1 <= 3;
|
||||
const progressDuration = notification.timeout || 3000;
|
||||
@@ -164,7 +164,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
setHeights(h =>
|
||||
h.filter(height => height.notificationKey !== notification.key)
|
||||
);
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
if (!notification.key) {
|
||||
return;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
||||
clearTimeout(timerIdRef.current);
|
||||
}
|
||||
if (!expand) {
|
||||
timerIdRef.current = setTimeout(() => {
|
||||
timerIdRef.current = window.setTimeout(() => {
|
||||
onClickRemove();
|
||||
}, duration);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const DefaultAvatar = ({ name: propsName }: { name: string }) => {
|
||||
return colorsSchema[index % colorsSchema.length];
|
||||
}, [name]);
|
||||
|
||||
const timer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const timer = useRef<number>();
|
||||
|
||||
const [topColor, middleColor, bottomColor] = colors;
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
@@ -38,7 +38,7 @@ export const DefaultAvatar = ({ name: propsName }: { name: string }) => {
|
||||
<div
|
||||
className={DefaultAvatarContainerStyle}
|
||||
onMouseEnter={() => {
|
||||
timer.current = setTimeout(() => {
|
||||
timer.current = window.setTimeout(() => {
|
||||
setIsHover(true);
|
||||
}, 300);
|
||||
}}
|
||||
|
||||
@@ -97,6 +97,28 @@ export const mainContainerStyle = style({
|
||||
},
|
||||
} as ComplexStyleRule);
|
||||
|
||||
// These styles override the default styles of the react-resizable-panels
|
||||
// as the default styles make the overflow part hidden when printing to PDF.
|
||||
// See https://github.com/toeverything/AFFiNE/pull/3893
|
||||
globalStyle(`${mainContainerStyle} > div[data-panel-group]`, {
|
||||
'@media': {
|
||||
print: {
|
||||
overflow: 'visible !important',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// These styles override the default styles of the react-resizable-panels
|
||||
// as the default styles make the overflow part hidden when printing to PDF.
|
||||
// See https://github.com/toeverything/AFFiNE/pull/3893
|
||||
globalStyle(`${mainContainerStyle} > div[data-panel-group] > div[data-panel]`, {
|
||||
'@media': {
|
||||
print: {
|
||||
overflow: 'visible !important',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const toolStyle = style({
|
||||
position: 'fixed',
|
||||
right: '30px',
|
||||
|
||||
@@ -69,7 +69,7 @@ export const Popper = ({
|
||||
}
|
||||
window.clearTimeout(pointerLeaveTimer.current);
|
||||
|
||||
pointerEnterTimer.current = window.setTimeout(() => {
|
||||
pointerEnterTimer.current = window.window.setTimeout(() => {
|
||||
setVisible(true);
|
||||
}, pointerEnterDelay);
|
||||
};
|
||||
@@ -81,7 +81,7 @@ export const Popper = ({
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(pointerEnterTimer.current);
|
||||
pointerLeaveTimer.current = window.setTimeout(() => {
|
||||
pointerLeaveTimer.current = window.window.setTimeout(() => {
|
||||
setVisible(false);
|
||||
}, pointerLeaveDelay);
|
||||
};
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.8"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
4
packages/env/package.json
vendored
4
packages/env/package.json
vendored
@@ -5,7 +5,7 @@
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"devDependencies": {
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"zod": "^3.22.1"
|
||||
@@ -27,5 +27,5 @@
|
||||
"dependencies": {
|
||||
"lit": "^2.8.0"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
4
packages/env/src/blocksuite/index.ts
vendored
4
packages/env/src/blocksuite/index.ts
vendored
@@ -3,10 +3,10 @@ import type { Page } from '@blocksuite/store';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export async function initEmptyPage(page: Page) {
|
||||
export async function initEmptyPage(page: Page, title?: string) {
|
||||
await page.waitForLoaded();
|
||||
const pageBlockId = page.addBlock('affine:page', {
|
||||
title: new page.Text(''),
|
||||
title: new page.Text(title || ''),
|
||||
});
|
||||
page.addBlock('affine:surface', {}, pageBlockId);
|
||||
const noteBlockId = page.addBlock('affine:note', {}, pageBlockId);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/graphql",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"description": "Autogenerated GraphQL client for affine.pro",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
"devDependencies": {
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/y-provider": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-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"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@affine/y-provider": "workspace:*",
|
||||
@@ -53,5 +53,5 @@
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { Page, Workspace } from '@blocksuite/store';
|
||||
import { type Atom, atom, useAtomValue } from 'jotai';
|
||||
|
||||
@@ -14,7 +13,11 @@ function getPageReferences(page: Page): string[] {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
const getPageReferencesAtom = (page: Page) => {
|
||||
const getPageReferencesAtom = (page: Page | null) => {
|
||||
if (!page) {
|
||||
return atom([]);
|
||||
}
|
||||
|
||||
if (!weakMap.has(page)) {
|
||||
const baseAtom = atom<string[]>(getPageReferences(page));
|
||||
baseAtom.onMount = set => {
|
||||
@@ -35,6 +38,5 @@ export function useBlockSuitePageReferences(
|
||||
pageId: string
|
||||
): string[] {
|
||||
const page = useBlockSuiteWorkspacePage(blockSuiteWorkspace, pageId);
|
||||
assertExists(page);
|
||||
return useAtomValue(getPageReferencesAtom(page));
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -317,6 +317,7 @@
|
||||
"Zoom to 100%": "Zoom to 100%",
|
||||
"Zoom to fit": "Zoom to fit",
|
||||
"all": "all",
|
||||
"com.affine.loading": "Loading...",
|
||||
"com.affine.banner.content": "This demo is limited. <1>Download the AFFiNE Client</1> for the latest features and Performance.",
|
||||
"com.affine.cloudTempDisable.description": "We are upgrading the AFFiNE Cloud service and it is temporarily unavailable on the client side. If you wish to stay updated on the progress and be notified on availability, you can fill out the <1>AFFiNE Cloud Signup</1>.",
|
||||
"com.affine.cloudTempDisable.title": "AFFiNE Cloud is upgrading now.",
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"jotai": "^2.3.1",
|
||||
"zod": "^3.22.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230822230555-98129627-nightly",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"electron": "link:../../apps/electron/node_modules/electron",
|
||||
"react": "^18.2.0",
|
||||
@@ -93,5 +93,5 @@
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,19 @@ import { atom, createStore } from 'jotai/vanilla';
|
||||
import { getWorkspace, waitForWorkspace } from './__internal__/workspace.js';
|
||||
|
||||
// global store
|
||||
export const rootStore = createStore();
|
||||
let rootStore = createStore();
|
||||
|
||||
export function getCurrentStore() {
|
||||
return rootStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal do not use this function unless you know what you are doing
|
||||
*/
|
||||
export function _setCurrentStore(store: ReturnType<typeof createStore>) {
|
||||
rootStore = store;
|
||||
}
|
||||
|
||||
export const loadedPluginNameAtom = atom<string[]>([]);
|
||||
|
||||
export const currentWorkspaceIdAtom = atom<string | null>(null);
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
"jotai": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-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",
|
||||
"lottie-web": "^5.12.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -23,5 +23,5 @@
|
||||
"@blocksuite/store": "*",
|
||||
"lottie-web": "*"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"test": "cross-env TS_NODE_TRANSPILE_ONLY=1 TS_NODE_PROJECT=./tsconfig.json node --test --loader ts-node/esm --experimental-specifier-resolution=node ./__tests__/**/*.mts",
|
||||
"version": "napi version"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/plugin-cli",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"bin": {
|
||||
"af": "./src/af.mjs"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/sdk",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
@@ -22,9 +22,9 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"jotai": "^2.3.1",
|
||||
"zod": "^3.22.1"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/storage",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0 < 11 || >= 11.8.0"
|
||||
},
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"./v1/*.json": "./v1/*.json",
|
||||
"./preloading.json": "./preloading.json"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/workers",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "wrangler dev"
|
||||
|
||||
@@ -34,5 +34,5 @@
|
||||
"@types/ws": "^8.5.5",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@toeverything/y-indexeddb",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"description": "IndexedDB database adapter for Yjs",
|
||||
"repository": "toeverything/AFFiNE",
|
||||
"author": "toeverything",
|
||||
@@ -37,8 +37,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@affine/y-provider": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-dts": "3.5.2",
|
||||
"y-indexeddb": "^9.0.11"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/y-provider",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"description": "Yjs provider utilities for AFFiNE",
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
@@ -9,7 +9,7 @@
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly"
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"yjs": "^13.5.51"
|
||||
|
||||
@@ -52,7 +52,7 @@ export const createLazyProvider = (
|
||||
const changeStatus = (newStatus: Status) => {
|
||||
// simulate a stack, each syncing and synced should be paired
|
||||
if (newStatus.type === 'idle') {
|
||||
if (syncingStack !== 0) {
|
||||
if (connected && syncingStack !== 0) {
|
||||
console.error('syncingStatus !== 0, this should not happen');
|
||||
}
|
||||
syncingStack = 0;
|
||||
@@ -79,6 +79,9 @@ export const createLazyProvider = (
|
||||
|
||||
async function syncDoc(doc: Doc) {
|
||||
const guid = doc.guid;
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
changeStatus({
|
||||
type: 'syncing',
|
||||
@@ -87,6 +90,18 @@ export const createLazyProvider = (
|
||||
.queryDocState(guid, {
|
||||
stateVector: encodeStateVector(doc),
|
||||
})
|
||||
.then(remoteUpdate => {
|
||||
if (!connected) {
|
||||
changeStatus({
|
||||
type: 'idle',
|
||||
});
|
||||
return;
|
||||
}
|
||||
changeStatus({
|
||||
type: 'synced',
|
||||
});
|
||||
return remoteUpdate;
|
||||
})
|
||||
.catch(error => {
|
||||
changeStatus({
|
||||
type: 'error',
|
||||
@@ -94,9 +109,6 @@ export const createLazyProvider = (
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
changeStatus({
|
||||
type: 'synced',
|
||||
});
|
||||
|
||||
pendingMap.set(guid, []);
|
||||
|
||||
@@ -171,6 +183,9 @@ export const createLazyProvider = (
|
||||
*/
|
||||
function setupDatasourceListeners() {
|
||||
datasourceUnsub = datasource.onDocUpdate?.((guid, update) => {
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
changeStatus({
|
||||
type: 'syncing',
|
||||
});
|
||||
@@ -244,16 +259,25 @@ export const createLazyProvider = (
|
||||
});
|
||||
// root doc should be already loaded,
|
||||
// but we want to populate the cache for later update events
|
||||
connectDoc(rootDoc).catch(error => {
|
||||
changeStatus({
|
||||
type: 'error',
|
||||
error,
|
||||
connectDoc(rootDoc)
|
||||
.then(() => {
|
||||
if (!connected) {
|
||||
changeStatus({
|
||||
type: 'idle',
|
||||
});
|
||||
return;
|
||||
}
|
||||
changeStatus({
|
||||
type: 'synced',
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
changeStatus({
|
||||
type: 'error',
|
||||
error,
|
||||
});
|
||||
console.error(error);
|
||||
});
|
||||
console.error(error);
|
||||
});
|
||||
changeStatus({
|
||||
type: 'synced',
|
||||
});
|
||||
setupDatasourceListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/bookmark-plugin",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"description": "Bookmark Plugin",
|
||||
"affinePlugin": {
|
||||
"release": true,
|
||||
|
||||
@@ -183,7 +183,7 @@ const BookMarkUI = ({ page }: BookMarkProps) => {
|
||||
if (!shouldShowBookmarkMenu(pastedBlocks)) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
setAnchor(getCurrentNativeRange());
|
||||
}, 100);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@toeverything/components": "^0.0.11",
|
||||
"@toeverything/components": "^0.0.12",
|
||||
"idb": "^7.1.1",
|
||||
"langchain": "^0.0.129",
|
||||
"marked": "^7.0.3",
|
||||
@@ -35,5 +35,5 @@
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "Hello world plugin",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"scripts": {
|
||||
"dev": "af dev",
|
||||
"build": "af build"
|
||||
@@ -18,7 +18,7 @@
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@toeverything/components": "^0.0.11"
|
||||
"@toeverything/components": "^0.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@affine/plugin-cli": "workspace:*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/image-preview-plugin",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"description": "Image preview plugin",
|
||||
"affinePlugin": {
|
||||
"release": true,
|
||||
@@ -17,7 +17,7 @@
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@toeverything/components": "^0.0.11",
|
||||
"@toeverything/components": "^0.0.12",
|
||||
"@toeverything/theme": "^0.7.12",
|
||||
"clsx": "^2.0.0",
|
||||
"foxact": "^0.2.20",
|
||||
|
||||
@@ -72,10 +72,10 @@ const ImagePreviewModalImpl = (
|
||||
const [hasPlayedAnimation, setHasPlayedAnimation] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
let timeoutId: number;
|
||||
|
||||
if (!isOpen) {
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = window.setTimeout(() => {
|
||||
props.onClose();
|
||||
setIsOpen(true);
|
||||
}, 300);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "Outline plugin",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"scripts": {
|
||||
"dev": "af dev",
|
||||
"build": "af build"
|
||||
@@ -18,7 +18,7 @@
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@toeverything/components": "^0.0.11"
|
||||
"@toeverything/components": "^0.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@affine/plugin-cli": "workspace:*",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "Vue hello world plugin",
|
||||
"version": "0.8.0-beta.0",
|
||||
"version": "0.8.0",
|
||||
"scripts": {
|
||||
"dev": "af dev",
|
||||
"build": "af build"
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
"devDependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@playwright/test": "^1.37.0",
|
||||
"express": "^4.18.2",
|
||||
"http-proxy-middleware": "^3.0.0-beta.1",
|
||||
"serve": "^14.2.0"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
"devDependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230818053411-bc330b35-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230822230555-98129627-nightly",
|
||||
"@playwright/test": "^1.37.0",
|
||||
"express": "^4.18.2",
|
||||
"http-proxy-middleware": "^3.0.0-beta.1",
|
||||
"serve": "^14.2.0"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { test } from '@affine-test/kit/playwright';
|
||||
import { withCtrlOrMeta } from '@affine-test/kit/utils/keyboard';
|
||||
import { openHomePage } from '@affine-test/kit/utils/load-page';
|
||||
import { newPage, waitEditorLoad } from '@affine-test/kit/utils/page-logic';
|
||||
import {
|
||||
getBlockSuiteEditorTitle,
|
||||
newPage,
|
||||
waitEditorLoad,
|
||||
} from '@affine-test/kit/utils/page-logic';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
const openQuickSearchByShortcut = async (page: Page) =>
|
||||
@@ -116,6 +120,16 @@ test('Create a new page and search this page', async ({ page }) => {
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForTimeout(300);
|
||||
await assertTitle(page, 'test123456');
|
||||
|
||||
await page.reload();
|
||||
await waitEditorLoad(page);
|
||||
await openQuickSearchByShortcut(page);
|
||||
await page.keyboard.insertText('test123456');
|
||||
await page.waitForTimeout(300);
|
||||
await assertResultList(page, ['test123456']);
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForTimeout(300);
|
||||
await assertTitle(page, 'test123456');
|
||||
});
|
||||
test('Navigate to the 404 page and try to open quick search', async ({
|
||||
page,
|
||||
@@ -174,3 +188,77 @@ test('Not show navigation path if page is not a subpage or current page is not i
|
||||
await openQuickSearchByShortcut(page);
|
||||
expect(await page.getByTestId('navigation-path').count()).toBe(0);
|
||||
});
|
||||
|
||||
test('assert the recent browse pages are on the recent list', async ({
|
||||
page,
|
||||
}) => {
|
||||
await openHomePage(page);
|
||||
await waitEditorLoad(page);
|
||||
|
||||
// create first page
|
||||
await newPage(page);
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.type('sgtokidoki', {
|
||||
delay: 50,
|
||||
});
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// create second page
|
||||
await openQuickSearchByShortcut(page);
|
||||
const addNewPage = page.getByTestId('quick-search-add-new-page');
|
||||
await addNewPage.click();
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.type('theliquidhorse', {
|
||||
delay: 50,
|
||||
});
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// create thrid page
|
||||
await openQuickSearchByShortcut(page);
|
||||
await addNewPage.click();
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.type('battlekot', {
|
||||
delay: 50,
|
||||
});
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await openQuickSearchByShortcut(page);
|
||||
await page.waitForTimeout(200);
|
||||
{
|
||||
// check does all 3 pages exists on recent page list
|
||||
const quickSearchItems = page.locator('[cmdk-item]');
|
||||
expect(await quickSearchItems.nth(0).textContent()).toBe('battlekot');
|
||||
expect(await quickSearchItems.nth(1).textContent()).toBe('theliquidhorse');
|
||||
expect(await quickSearchItems.nth(2).textContent()).toBe('sgtokidoki');
|
||||
}
|
||||
|
||||
// create forth page, and check does the recent page list only contains three pages
|
||||
await page.reload();
|
||||
await waitEditorLoad(page);
|
||||
await openQuickSearchByShortcut(page);
|
||||
{
|
||||
const addNewPage = page.getByTestId('quick-search-add-new-page');
|
||||
await addNewPage.click();
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
{
|
||||
const title = getBlockSuiteEditorTitle(page);
|
||||
await title.type('affine is the best', {
|
||||
delay: 50,
|
||||
});
|
||||
}
|
||||
await page.waitForTimeout(1000);
|
||||
await openQuickSearchByShortcut(page);
|
||||
{
|
||||
const quickSearchItems = page.locator('[cmdk-item]');
|
||||
expect(await quickSearchItems.nth(0).textContent()).toBe(
|
||||
'affine is the best'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@playwright/test": "^1.37.0"
|
||||
},
|
||||
"version": "0.8.0-beta.0"
|
||||
"version": "0.8.0"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user