mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-08 10:33:44 +00:00
Compare commits
38 Commits
v0.8.0-bet
...
v0.8.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c78e7983f3 | ||
|
|
10aaaeb1d7 | ||
|
|
13323bcbc0 | ||
|
|
06455bee5e | ||
|
|
aa40d4be8f | ||
|
|
448496c245 | ||
|
|
92714a588b | ||
|
|
d5d73db4d6 | ||
|
|
3d887abefc | ||
|
|
14d0c8ba30 | ||
|
|
391ca0b934 | ||
|
|
3a9265b280 | ||
|
|
4f93e7dbc8 | ||
|
|
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 |
@@ -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
|
||||
|
||||
116
.github/workflows/build.yml
vendored
116
.github/workflows/build.yml
vendored
@@ -134,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
|
||||
@@ -240,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
|
||||
@@ -248,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
|
||||
@@ -329,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
|
||||
@@ -338,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 }}
|
||||
@@ -374,7 +341,6 @@ jobs:
|
||||
name: E2E Migration Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
strategy:
|
||||
matrix:
|
||||
spec:
|
||||
@@ -388,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 }}
|
||||
@@ -432,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
|
||||
|
||||
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
|
||||
28
README.md
28
README.md
@@ -146,14 +146,42 @@ Thanks a lot to the community for providing such powerful and simple libraries,
|
||||
|
||||
# Contributors
|
||||
|
||||
## Current Core members
|
||||
|
||||
Team members who are currently maintaining the project:
|
||||
|
||||
- [JimmFly](https://github.com/JimmFly) - Jinfei Yang <yangjinfei001@gmail.com> (he/him)
|
||||
- [pengx17](https://github.com/pengx17) - Peng Xiao <pengxiao@outlook.com> (he/him)
|
||||
- [QiShaoXuan](https://github.com/QiSHaoXuan) - Shaoxuan Qi <qishaoxuan777@gmail.com> (he/him)
|
||||
- [himself65](https://github.com/himself65) - Zeyu "Alex" Yang <himself65@outlook.com> (he/him)
|
||||
|
||||
## All Contributors
|
||||
|
||||
We would like to express our gratitude to all the individuals who have already contributed to AFFiNE! If you have any AFFiNE-related project, documentation, tool or template, please feel free to contribute it by submitting a pull request to our curated list on GitHub: [awesome-affine](https://github.com/toeverything/awesome-affine).
|
||||
|
||||
<a href="https://github.com/toeverything/affine/graphs/contributors">
|
||||
<img alt="contributors" src="https://opencollective.com/affine/contributors.svg?width=890&button=false" />
|
||||
</a>
|
||||
|
||||
## Data Compatibility
|
||||
|
||||
Data compatibility is a very important issue for us. We will try our best to ensure that the data is compatible with the previous version.
|
||||
|
||||
If you encounter any problems when upgrading the version, please feel free to [contact us](mailto:developer@toeverything.info).
|
||||
|
||||
| AFFiNE Version | Export/Import workspace | Data auto migration |
|
||||
| -------------- | ----------------------- | ------------------- |
|
||||
| <= 0.5.4 | ❌️ | ❌ |
|
||||
| 0.6.x | ✅️ | ✅ |
|
||||
| 0.7.x | ✅️ | ✅ |
|
||||
| 0.8.x | ✅ | ✅ |
|
||||
|
||||
## Self-Host
|
||||
|
||||
> We know that the self-host version has been out of date for a long time.
|
||||
>
|
||||
> We are working hard to get this updated to the latest version, you can try our desktop version first.
|
||||
|
||||
Get started with Docker and deploy your own feature-rich, restriction-free deployment of AFFiNE.
|
||||
We are working hard to get this updated to the latest version, you can keep an eye on the [latest packages].
|
||||
|
||||
|
||||
@@ -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.1",
|
||||
"version": "0.8.3",
|
||||
"scripts": {
|
||||
"build": "yarn -T run build-core",
|
||||
"dev": "yarn -T run dev-core",
|
||||
"static-server": "ts-node-esm ./server.mts"
|
||||
"static-server": "yarn -T run dev-core --static"
|
||||
},
|
||||
"exports": {
|
||||
"./app": "./src/app.tsx",
|
||||
@@ -24,13 +24,13 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@emotion/cache": "^11.11.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}`);
|
||||
});
|
||||
@@ -22,6 +22,7 @@ import { nanoid } from '@blocksuite/store';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { setPageModeAtom } from '../../atoms';
|
||||
import {
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import {
|
||||
migrateDatabaseBlockTo3,
|
||||
migrateToSubdoc,
|
||||
} from '@affine/env/blocksuite';
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import type {
|
||||
LocalIndexedDBDownloadProvider,
|
||||
WorkspaceAdapter,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import type { WorkspaceAdapter } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
type RootWorkspaceMetadataV2,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import { globalBlockSuiteSchema } from '@affine/workspace/manager';
|
||||
import {
|
||||
getOrCreateWorkspace,
|
||||
globalBlockSuiteSchema,
|
||||
} from '@affine/workspace/manager';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import {
|
||||
migrateLocalBlobStorage,
|
||||
upgradeV1ToV2,
|
||||
} from '@affine/workspace/migration';
|
||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
migrateWorkspace,
|
||||
WorkspaceVersion,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { downloadBinary } from '@toeverything/y-indexeddb';
|
||||
import type { createStore } from 'jotai/vanilla';
|
||||
import { Doc } from 'yjs';
|
||||
import { applyUpdate } from 'yjs';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
@@ -33,94 +33,57 @@ async function tryMigration() {
|
||||
const promises: Promise<void>[] = [];
|
||||
const newMetadata = [...metadata];
|
||||
metadata.forEach(oldMeta => {
|
||||
if (!('version' in oldMeta)) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
const upgrade = async () => {
|
||||
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
}
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (!workspace) {
|
||||
console.warn('cannot find workspace', oldMeta.id);
|
||||
return;
|
||||
}
|
||||
const doc = workspace.blockSuiteWorkspace.doc;
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
workspace.id,
|
||||
doc,
|
||||
{
|
||||
awareness:
|
||||
workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}
|
||||
) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
if (doc === newDoc) {
|
||||
console.log('doc not changed');
|
||||
return;
|
||||
}
|
||||
const newWorkspace = upgradeV1ToV2(workspace);
|
||||
await migrateDatabaseBlockTo3(
|
||||
newWorkspace.blockSuiteWorkspace.doc,
|
||||
globalBlockSuiteSchema
|
||||
);
|
||||
|
||||
const newId = await adapter.CRUD.create(
|
||||
newWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
await adapter.CRUD.delete(workspace as any);
|
||||
console.log('migrated', oldMeta.id, newId);
|
||||
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
};
|
||||
await migrateLocalBlobStorage(workspace.id, newId);
|
||||
console.log('migrate to v2');
|
||||
};
|
||||
|
||||
// create a new workspace and push it to metadata
|
||||
promises.push(upgrade());
|
||||
} else if (oldMeta.version < WorkspaceVersion.DatabaseV3) {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
assertExists(adapter);
|
||||
if (oldMeta.flavour === WorkspaceFlavour.LOCAL) {
|
||||
promises.push(
|
||||
(async () => {
|
||||
if (oldMeta.flavour !== WorkspaceFlavour.LOCAL) {
|
||||
console.warn('not supported');
|
||||
return;
|
||||
migrateWorkspace(
|
||||
'version' in oldMeta ? oldMeta.version : undefined,
|
||||
{
|
||||
getCurrentRootDoc: async () => {
|
||||
const doc = new Doc({
|
||||
guid: oldMeta.id,
|
||||
});
|
||||
const downloadWorkspace = async (doc: Doc): Promise<void> => {
|
||||
const binary = await downloadBinary(doc.guid);
|
||||
if (binary) {
|
||||
applyUpdate(doc, binary);
|
||||
}
|
||||
return Promise.all(
|
||||
[...doc.subdocs.values()].map(subdoc =>
|
||||
downloadWorkspace(subdoc)
|
||||
)
|
||||
).then();
|
||||
};
|
||||
await downloadWorkspace(doc);
|
||||
return doc;
|
||||
},
|
||||
createWorkspace: async () =>
|
||||
getOrCreateWorkspace(nanoid(), WorkspaceFlavour.LOCAL),
|
||||
getSchema: () => globalBlockSuiteSchema,
|
||||
}
|
||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||
if (workspace) {
|
||||
const provider = createIndexedDBDownloadProvider(
|
||||
workspace.id,
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
{
|
||||
awareness:
|
||||
workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||
}
|
||||
) as LocalIndexedDBDownloadProvider;
|
||||
provider.sync();
|
||||
await provider.whenReady;
|
||||
await migrateDatabaseBlockTo3(
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
globalBlockSuiteSchema
|
||||
).then(async workspace => {
|
||||
if (typeof workspace !== 'boolean') {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
const oldWorkspace = await adapter.CRUD.get(oldMeta.id);
|
||||
const newId = await adapter.CRUD.create(workspace);
|
||||
assertExists(
|
||||
oldWorkspace,
|
||||
'workspace should exist after migrate'
|
||||
);
|
||||
await adapter.CRUD.delete(oldWorkspace.blockSuiteWorkspace);
|
||||
const index = newMetadata.findIndex(
|
||||
meta => meta.id === oldMeta.id
|
||||
);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
};
|
||||
await migrateLocalBlobStorage(workspace.id, newId);
|
||||
console.log('workspace migrated', oldMeta.id, newId);
|
||||
} else if (workspace) {
|
||||
console.log('workspace migrated', oldMeta.id);
|
||||
}
|
||||
const index = newMetadata.findIndex(
|
||||
meta => meta.id === oldMeta.id
|
||||
);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
};
|
||||
console.log('migrate to v3');
|
||||
})()
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
|
||||
@@ -146,9 +146,11 @@ export const BlockSuitePageList = ({
|
||||
const tagOptionMap = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
blockSuiteWorkspace.meta.properties.tags.options.map(v => [v.id, v])
|
||||
(
|
||||
blockSuiteWorkspace.meta.properties.tags ?? { options: [] }
|
||||
).options.map(v => [v.id, v])
|
||||
),
|
||||
[blockSuiteWorkspace.meta.properties.tags.options]
|
||||
[blockSuiteWorkspace.meta.properties.tags]
|
||||
);
|
||||
const list = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -22,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]
|
||||
|
||||
@@ -106,7 +106,7 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
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);
|
||||
@@ -123,7 +123,7 @@ const EditorWrapper = memo(function EditorWrapper({
|
||||
return () => {
|
||||
dispose();
|
||||
clearTimeout(renderTimeout);
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
disposes.forEach(dispose => dispose());
|
||||
});
|
||||
};
|
||||
@@ -165,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,7 +1,7 @@
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { WorkspaceRegistry } from '@affine/env/workspace';
|
||||
import { WorkspaceVersion } from '@affine/env/workspace';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { saveWorkspaceToLocalStorage } from '@affine/workspace/local/crud';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
|
||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||
import {
|
||||
buildShowcaseWorkspace,
|
||||
WorkspaceVersion,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/reac
|
||||
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { lazy, Suspense, useCallback, useMemo } from 'react';
|
||||
import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Map as YMap } from 'yjs';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
import {
|
||||
@@ -150,6 +151,23 @@ export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => {
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const { openPage } = useNavigateHelper();
|
||||
|
||||
useEffect(() => {
|
||||
// hotfix for blockVersions
|
||||
// this is a mistake in the
|
||||
// 0.8.0 ~ 0.8.1
|
||||
// 0.8.0-beta.0 ~ 0.8.0-beta.3
|
||||
// 0.9.0-canary.0 ~ 0.9.0-canary.3
|
||||
const meta = currentWorkspace.blockSuiteWorkspace.doc.getMap('meta');
|
||||
const blockVersions = meta.get('blockVersions');
|
||||
if (!(blockVersions instanceof YMap)) {
|
||||
console.log('fixing blockVersions');
|
||||
meta.set(
|
||||
'blockVersions',
|
||||
new YMap(Object.entries(blockVersions as Record<string, number>))
|
||||
);
|
||||
}
|
||||
}, [currentWorkspace.blockSuiteWorkspace.doc]);
|
||||
|
||||
usePassiveWorkspaceEffect(currentWorkspace.blockSuiteWorkspace);
|
||||
|
||||
const [, setOpenWorkspacesModal] = useAtom(openWorkspacesModalAtom);
|
||||
|
||||
@@ -23,6 +23,7 @@ export const loader: LoaderFunction = async () => {
|
||||
const target = (lastId && meta.find(({ id }) => id === lastId)) || meta.at(0);
|
||||
if (target) {
|
||||
const targetWorkspace = getWorkspace(target.id);
|
||||
|
||||
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
|
||||
({ trash }) => !trash
|
||||
);
|
||||
|
||||
@@ -14,12 +14,14 @@ import {
|
||||
currentWorkspaceIdAtom,
|
||||
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,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/docs",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -10,12 +10,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/component": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"express": "^4.18.2",
|
||||
"jotai": "^2.3.1",
|
||||
"react": "18.3.0-canary-7118f5dd7-20230705",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
owner: toeverything
|
||||
repo: AFFiNE
|
||||
provider: github
|
||||
provider: custom
|
||||
private: false
|
||||
|
||||
@@ -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.1",
|
||||
"version": "0.8.3",
|
||||
"author": "affine",
|
||||
"repository": {
|
||||
"url": "https://github.com/toeverything/AFFiNE",
|
||||
@@ -17,7 +17,8 @@
|
||||
"generate-assets": "zx scripts/generate-assets.mjs",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"test": "DEBUG=pw:browser yarn -T run playwright test -c ./playwright.config.ts"
|
||||
"test": "DEBUG=pw:browser yarn -T run playwright test -c ./playwright.config.ts",
|
||||
"make-squirrel": "yarn ts-node-esm -T scripts/make-squirrel.mts"
|
||||
},
|
||||
"config": {
|
||||
"forge": "./forge.config.js"
|
||||
@@ -28,10 +29,10 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@electron-forge/cli": "^6.4.0",
|
||||
"@electron-forge/core": "^6.4.0",
|
||||
"@electron-forge/core-utils": "^6.4.0",
|
||||
@@ -41,6 +42,7 @@
|
||||
"@electron-forge/shared-types": "^6.4.0",
|
||||
"@electron/remote": "2.0.10",
|
||||
"@reforged/maker-appimage": "^3.3.1",
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"cross-env": "7.0.3",
|
||||
@@ -50,7 +52,10 @@
|
||||
"electron-window-state": "^5.0.3",
|
||||
"esbuild": "^0.19.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"glob": "^10.3.3",
|
||||
"jotai": "^2.3.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"rxjs": "^7.8.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"undici": "^5.23.0",
|
||||
"uuid": "^9.0.0",
|
||||
@@ -59,13 +64,10 @@
|
||||
"zx": "^7.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"electron-updater": "^6.1.4",
|
||||
"link-preview-js": "^3.0.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.7"
|
||||
},
|
||||
"build": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
owner: toeverything
|
||||
repo: AFFiNE
|
||||
provider: github
|
||||
provider: custom
|
||||
private: false
|
||||
|
||||
@@ -44,6 +44,7 @@ export const config = () => {
|
||||
'electron-updater',
|
||||
'@toeverything/plugin-infra',
|
||||
'yjs',
|
||||
'semver',
|
||||
],
|
||||
define: define,
|
||||
format: 'cjs',
|
||||
|
||||
@@ -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();
|
||||
@@ -1,7 +1,11 @@
|
||||
import { equal } from 'node:assert';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||
import { SqliteConnection } from '@affine/native';
|
||||
import {
|
||||
migrateToSubdoc,
|
||||
WorkspaceVersion,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { applyUpdate, Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
||||
@@ -30,6 +34,74 @@ export const migrateToSubdocAndReplaceDatabase = async (path: string) => {
|
||||
await db.close();
|
||||
};
|
||||
|
||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import { Schema, Workspace } from '@blocksuite/store';
|
||||
import { migrateWorkspace } from '@toeverything/infra/blocksuite';
|
||||
|
||||
// v1 v2 -> v3
|
||||
export const migrateToLatestDatabase = async (path: string) => {
|
||||
const connection = new SqliteConnection(path);
|
||||
await connection.connect();
|
||||
await connection.initVersion();
|
||||
const schema = new Schema();
|
||||
schema.register(AffineSchemas).register(__unstableSchemas);
|
||||
const rootDoc = new YDoc();
|
||||
const downloadBinary = async (doc: YDoc, isRoot: boolean): Promise<void> => {
|
||||
const update = (
|
||||
await connection.getUpdates(isRoot ? undefined : doc.guid)
|
||||
).map(update => update.data);
|
||||
// Buffer[] -> Uint8Array[]
|
||||
const data = update.map(update => new Uint8Array(update));
|
||||
data.forEach(data => {
|
||||
applyUpdate(doc, data);
|
||||
});
|
||||
// trigger data manually
|
||||
if (isRoot) {
|
||||
doc.getMap('meta');
|
||||
doc.getMap('spaces');
|
||||
} else {
|
||||
doc.getMap('blocks');
|
||||
}
|
||||
await Promise.all(
|
||||
[...doc.subdocs].map(subdoc => {
|
||||
return downloadBinary(subdoc, false);
|
||||
})
|
||||
);
|
||||
};
|
||||
await downloadBinary(rootDoc, true);
|
||||
const result = await migrateWorkspace(WorkspaceVersion.SubDoc, {
|
||||
getSchema: () => schema,
|
||||
getCurrentRootDoc: () => Promise.resolve(rootDoc),
|
||||
createWorkspace: () =>
|
||||
Promise.resolve(
|
||||
new Workspace({
|
||||
id: nanoid(10),
|
||||
schema,
|
||||
blobStorages: [],
|
||||
providerCreators: [],
|
||||
})
|
||||
),
|
||||
});
|
||||
equal(
|
||||
typeof result,
|
||||
'boolean',
|
||||
'migrateWorkspace should return boolean value'
|
||||
);
|
||||
const uploadBinary = async (doc: YDoc, isRoot: boolean) => {
|
||||
await connection.replaceUpdates(doc.guid, [
|
||||
{ docId: isRoot ? undefined : doc.guid, data: encodeStateAsUpdate(doc) },
|
||||
]);
|
||||
// connection..applyUpdate(encodeStateAsUpdate(doc), 'self', doc.guid)
|
||||
await Promise.all(
|
||||
[...doc.subdocs].map(subdoc => {
|
||||
return uploadBinary(subdoc, false);
|
||||
})
|
||||
);
|
||||
};
|
||||
await uploadBinary(rootDoc, true);
|
||||
await connection.close();
|
||||
};
|
||||
|
||||
export const copyToTemp = async (path: string) => {
|
||||
const tmpDirPath = resolve(await mainRPC.getPath('sessionData'), 'tmp');
|
||||
const tmpFilePath = resolve(tmpDirPath, nanoid());
|
||||
|
||||
@@ -12,7 +12,11 @@ import fs from 'fs-extra';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { ensureSQLiteDB } from '../db/ensure-db';
|
||||
import { copyToTemp, migrateToSubdocAndReplaceDatabase } from '../db/migration';
|
||||
import {
|
||||
copyToTemp,
|
||||
migrateToLatestDatabase,
|
||||
migrateToSubdocAndReplaceDatabase,
|
||||
} from '../db/migration';
|
||||
import type { WorkspaceSQLiteDB } from '../db/workspace-db-adapter';
|
||||
import { logger } from '../logger';
|
||||
import { mainRPC } from '../main-rpc';
|
||||
@@ -197,7 +201,22 @@ export async function loadDBFile(): Promise<LoadDBFileResult> {
|
||||
}
|
||||
}
|
||||
|
||||
if (validationResult === ValidationResult.MissingVersionColumn) {
|
||||
try {
|
||||
const tmpDBPath = await copyToTemp(originalPath);
|
||||
await migrateToLatestDatabase(tmpDBPath);
|
||||
originalPath = tmpDBPath;
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`loadDBFile, migration version column failed: ${originalPath}`,
|
||||
error
|
||||
);
|
||||
return { error: 'DB_FILE_MIGRATION_FAILED' };
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
validationResult !== ValidationResult.MissingVersionColumn &&
|
||||
validationResult !== ValidationResult.MissingDocIdColumn &&
|
||||
validationResult !== ValidationResult.Valid
|
||||
) {
|
||||
|
||||
250
apps/electron/src/main/updater/custom-github-provider.ts
Normal file
250
apps/electron/src/main/updater/custom-github-provider.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
// credits: migrated from https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/providers/GitHubProvider.ts
|
||||
import type {
|
||||
CustomPublishOptions,
|
||||
GithubOptions,
|
||||
ReleaseNoteInfo,
|
||||
XElement,
|
||||
} from 'builder-util-runtime';
|
||||
import { HttpError, newError, parseXml } from 'builder-util-runtime';
|
||||
import {
|
||||
type AppUpdater,
|
||||
CancellationToken,
|
||||
type ResolvedUpdateFileInfo,
|
||||
type UpdateInfo,
|
||||
} from 'electron-updater';
|
||||
import { BaseGitHubProvider } from 'electron-updater/out/providers/GitHubProvider';
|
||||
import {
|
||||
parseUpdateInfo,
|
||||
type ProviderRuntimeOptions,
|
||||
resolveFiles,
|
||||
} from 'electron-updater/out/providers/Provider';
|
||||
import * as semver from 'semver';
|
||||
|
||||
interface GithubUpdateInfo extends UpdateInfo {
|
||||
tag: string;
|
||||
}
|
||||
|
||||
const hrefRegExp = /\/tag\/([^/]+)$/;
|
||||
|
||||
export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
|
||||
constructor(
|
||||
options: CustomPublishOptions,
|
||||
private updater: AppUpdater,
|
||||
runtimeOptions: ProviderRuntimeOptions
|
||||
) {
|
||||
super(options as unknown as GithubOptions, 'github.com', runtimeOptions);
|
||||
}
|
||||
|
||||
async getLatestVersion(): Promise<GithubUpdateInfo> {
|
||||
const cancellationToken = new CancellationToken();
|
||||
|
||||
const feedXml = await this.httpRequest(
|
||||
newUrlFromBase(`${this.basePath}.atom`, this.baseUrl),
|
||||
{
|
||||
accept: 'application/xml, application/atom+xml, text/xml, */*',
|
||||
},
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
if (!feedXml) {
|
||||
throw new Error(
|
||||
`Cannot find feed in the remote server (${this.baseUrl.href})`
|
||||
);
|
||||
}
|
||||
|
||||
const feed = parseXml(feedXml);
|
||||
// noinspection TypeScriptValidateJSTypes
|
||||
const latestRelease = feed.element(
|
||||
'entry',
|
||||
false,
|
||||
`No published versions on GitHub`
|
||||
);
|
||||
let tag: string | null = null;
|
||||
try {
|
||||
const currentChannel =
|
||||
this.options.channel ||
|
||||
this.updater?.channel ||
|
||||
(semver.prerelease(this.updater.currentVersion)?.[0] as string) ||
|
||||
null;
|
||||
|
||||
if (currentChannel === null) {
|
||||
throw newError(
|
||||
`Cannot parse channel from version: ${this.updater.currentVersion}`,
|
||||
'ERR_UPDATER_INVALID_VERSION'
|
||||
);
|
||||
}
|
||||
|
||||
for (const element of feed.getElements('entry')) {
|
||||
// noinspection TypeScriptValidateJSTypes
|
||||
const hrefElement = hrefRegExp.exec(
|
||||
element.element('link').attribute('href')
|
||||
);
|
||||
|
||||
// If this is null then something is wrong and skip this release
|
||||
if (hrefElement === null) continue;
|
||||
|
||||
// This Release's Tag
|
||||
const hrefTag = hrefElement[1];
|
||||
// Get Channel from this release's tag
|
||||
// If it is null, we believe it is stable version
|
||||
const hrefChannel =
|
||||
(semver.prerelease(hrefTag)?.[0] as string) || 'stable';
|
||||
|
||||
const isNextPreRelease = hrefChannel === currentChannel;
|
||||
if (isNextPreRelease) {
|
||||
tag = hrefTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw newError(
|
||||
`Cannot parse releases feed: ${
|
||||
e.stack || e.message
|
||||
},\nXML:\n${feedXml}`,
|
||||
'ERR_UPDATER_INVALID_RELEASE_FEED'
|
||||
);
|
||||
}
|
||||
|
||||
if (tag == null) {
|
||||
throw newError(
|
||||
`No published versions on GitHub`,
|
||||
'ERR_UPDATER_NO_PUBLISHED_VERSIONS'
|
||||
);
|
||||
}
|
||||
|
||||
let rawData: string | null = null;
|
||||
let channelFile = '';
|
||||
let channelFileUrl: any = '';
|
||||
const fetchData = async (channelName: string) => {
|
||||
channelFile = getChannelFilename(channelName);
|
||||
channelFileUrl = newUrlFromBase(
|
||||
this.getBaseDownloadPath(String(tag), channelFile),
|
||||
this.baseUrl
|
||||
);
|
||||
const requestOptions = this.createRequestOptions(channelFileUrl);
|
||||
try {
|
||||
return await this.executor.request(requestOptions, cancellationToken);
|
||||
} catch (e: any) {
|
||||
if (e instanceof HttpError && e.statusCode === 404) {
|
||||
throw newError(
|
||||
`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${
|
||||
e.stack || e.message
|
||||
}`,
|
||||
'ERR_UPDATER_CHANNEL_FILE_NOT_FOUND'
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const channel = this.updater.allowPrerelease
|
||||
? this.getCustomChannelName(
|
||||
String(semver.prerelease(tag)?.[0] || 'latest')
|
||||
)
|
||||
: this.getDefaultChannelName();
|
||||
rawData = await fetchData(channel);
|
||||
} catch (e: any) {
|
||||
if (this.updater.allowPrerelease) {
|
||||
// Allow fallback to `latest.yml`
|
||||
rawData = await fetchData(this.getDefaultChannelName());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const result = parseUpdateInfo(rawData, channelFile, channelFileUrl);
|
||||
if (result.releaseName == null) {
|
||||
result.releaseName = latestRelease.elementValueOrEmpty('title');
|
||||
}
|
||||
|
||||
if (result.releaseNotes == null) {
|
||||
result.releaseNotes = computeReleaseNotes(
|
||||
this.updater.currentVersion,
|
||||
this.updater.fullChangelog,
|
||||
feed,
|
||||
latestRelease
|
||||
);
|
||||
}
|
||||
return {
|
||||
tag: tag,
|
||||
...result,
|
||||
};
|
||||
}
|
||||
|
||||
private get basePath(): string {
|
||||
return `/${this.options.owner}/${this.options.repo}/releases`;
|
||||
}
|
||||
|
||||
resolveFiles(updateInfo: GithubUpdateInfo): Array<ResolvedUpdateFileInfo> {
|
||||
// still replace space to - due to backward compatibility
|
||||
return resolveFiles(updateInfo, this.baseUrl, p =>
|
||||
this.getBaseDownloadPath(updateInfo.tag, p.replace(/ /g, '-'))
|
||||
);
|
||||
}
|
||||
|
||||
private getBaseDownloadPath(tag: string, fileName: string): string {
|
||||
return `${this.basePath}/download/${tag}/${fileName}`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomGitHubOptions {
|
||||
channel: string;
|
||||
repo: string;
|
||||
owner: string;
|
||||
releaseType: 'release' | 'prerelease';
|
||||
}
|
||||
|
||||
function getNoteValue(parent: XElement): string {
|
||||
const result = parent.elementValueOrEmpty('content');
|
||||
// GitHub reports empty notes as <content>No content.</content>
|
||||
return result === 'No content.' ? '' : result;
|
||||
}
|
||||
|
||||
export function computeReleaseNotes(
|
||||
currentVersion: semver.SemVer,
|
||||
isFullChangelog: boolean,
|
||||
feed: XElement,
|
||||
latestRelease: any
|
||||
): string | Array<ReleaseNoteInfo> | null {
|
||||
if (!isFullChangelog) {
|
||||
return getNoteValue(latestRelease);
|
||||
}
|
||||
|
||||
const releaseNotes: Array<ReleaseNoteInfo> = [];
|
||||
for (const release of feed.getElements('entry')) {
|
||||
// noinspection TypeScriptValidateJSTypes
|
||||
const versionRelease = /\/tag\/v?([^/]+)$/.exec(
|
||||
release.element('link').attribute('href')
|
||||
)?.[1];
|
||||
if (versionRelease && semver.lt(currentVersion, versionRelease)) {
|
||||
releaseNotes.push({
|
||||
version: versionRelease,
|
||||
note: getNoteValue(release),
|
||||
});
|
||||
}
|
||||
}
|
||||
return releaseNotes.sort((a, b) => semver.rcompare(a.version, b.version));
|
||||
}
|
||||
|
||||
// addRandomQueryToAvoidCaching is false by default because in most cases URL already contains version number,
|
||||
// so, it makes sense only for Generic Provider for channel files
|
||||
function newUrlFromBase(
|
||||
pathname: string,
|
||||
baseUrl: URL,
|
||||
addRandomQueryToAvoidCaching = false
|
||||
): URL {
|
||||
const result = new URL(pathname, baseUrl);
|
||||
// search is not propagated (search is an empty string if not specified)
|
||||
const search = baseUrl.search;
|
||||
if (search != null && search.length !== 0) {
|
||||
result.search = search;
|
||||
} else if (addRandomQueryToAvoidCaching) {
|
||||
result.search = `noCache=${Date.now().toString(32)}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getChannelFilename(channel: string): string {
|
||||
return `${channel}.yml`;
|
||||
}
|
||||
@@ -2,8 +2,9 @@ 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 { CustomGitHubProvider } from './custom-github-provider';
|
||||
import { updaterSubjects } from './event';
|
||||
|
||||
export const ReleaseTypeSchema = z.enum([
|
||||
@@ -36,12 +37,12 @@ export const checkForUpdates = async (force = true) => {
|
||||
|
||||
export const registerUpdater = async () => {
|
||||
// skip auto update in dev mode & internal
|
||||
if (isDev || buildType === 'internal') {
|
||||
if (buildType === 'internal') {
|
||||
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;
|
||||
@@ -51,11 +52,14 @@ export const registerUpdater = async () => {
|
||||
|
||||
const feedUrl: Parameters<typeof autoUpdater.setFeedURL>[0] = {
|
||||
channel: buildType,
|
||||
provider: 'github',
|
||||
// hack for custom provider
|
||||
provider: 'custom' as 'github',
|
||||
// @ts-expect-error - just ignore for now
|
||||
repo: buildType !== 'internal' ? 'AFFiNE' : 'AFFiNE-Releases',
|
||||
owner: 'toeverything',
|
||||
releaseType: buildType === 'stable' ? 'release' : 'prerelease',
|
||||
// @ts-expect-error hack for custom provider
|
||||
updateProvider: CustomGitHubProvider,
|
||||
};
|
||||
|
||||
logger.debug('auto-updater feed config', feedUrl);
|
||||
|
||||
@@ -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.1",
|
||||
"version": "0.8.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host --port 3003",
|
||||
@@ -18,13 +18,13 @@
|
||||
"@affine/jotai": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@affine/workspace": "workspace:*",
|
||||
"@blocksuite/block-std": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-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.1",
|
||||
"version": "0.8.3",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -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-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-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.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -105,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.1",
|
||||
"version": "0.8.3",
|
||||
"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.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"peerDependencies": {
|
||||
"ts-node": "*"
|
||||
},
|
||||
"version": "0.8.0-beta.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -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-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/icons": "^2.1.31",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-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.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export type AppSidebarProps = PropsWithChildren<
|
||||
function useEnableAnimation() {
|
||||
const [enable, setEnable] = useState(false);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
setEnable(true);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export const variableDefineMap = {
|
||||
icon: <FavoriteIcon />,
|
||||
},
|
||||
Tags: {
|
||||
type: meta => tArray(tTag.create({ tags: meta.tags.options })),
|
||||
type: meta => tArray(tTag.create({ tags: meta.tags?.options ?? [] })),
|
||||
icon: <TagsIcon />,
|
||||
},
|
||||
// Imported: {
|
||||
|
||||
@@ -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.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
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-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-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.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { Array as YArray, Map as YMap } from 'yjs';
|
||||
import { applyUpdate, Doc } from 'yjs';
|
||||
|
||||
import { migrateToSubdoc } from '../blocksuite/index.js';
|
||||
|
||||
const fixturePath = resolve(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'workspace.ydoc'
|
||||
);
|
||||
const yDocBuffer = readFileSync(fixturePath);
|
||||
const doc = new Doc();
|
||||
applyUpdate(doc, new Uint8Array(yDocBuffer));
|
||||
const migratedDoc = migrateToSubdoc(doc);
|
||||
|
||||
describe('subdoc', () => {
|
||||
test('Migration to subdoc', async () => {
|
||||
const { default: json } = await import('@affine-test/fixtures/output.json');
|
||||
const length = Object.keys(json).length;
|
||||
const binary = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
binary[i] = (json as any)[i];
|
||||
}
|
||||
const doc = new Doc();
|
||||
applyUpdate(doc, binary);
|
||||
{
|
||||
// invoke data
|
||||
doc.getMap('space:hello-world');
|
||||
doc.getMap('space:meta');
|
||||
}
|
||||
const blocks = doc.getMap('space:hello-world').toJSON();
|
||||
const newDoc = migrateToSubdoc(doc);
|
||||
const subDoc = newDoc.getMap('spaces').get('space:hello-world') as Doc;
|
||||
const data = (subDoc.toJSON() as any).blocks;
|
||||
Object.keys(data).forEach(id => {
|
||||
if (id === 'xyWNqindHH') {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
blocks[id]['sys:flavour'] === 'affine:surface' &&
|
||||
!blocks[id]['prop:elements']
|
||||
) {
|
||||
blocks[id]['prop:elements'] = data[id]['prop:elements'];
|
||||
}
|
||||
expect(data[id]).toEqual(blocks[id]);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test fixture should be set correctly', () => {
|
||||
const meta = doc.getMap('space:meta');
|
||||
const versions = meta.get('versions') as YMap<unknown>;
|
||||
expect(versions.get('affine:code')).toBeTypeOf('number');
|
||||
});
|
||||
|
||||
test('Meta data should be migrated correctly', () => {
|
||||
const originalMeta = doc.getMap('space:meta');
|
||||
const originalVersions = originalMeta.get('versions') as YMap<unknown>;
|
||||
|
||||
const meta = migratedDoc.getMap('meta');
|
||||
const blockVersions = meta.get('blockVersions') as YMap<unknown>;
|
||||
|
||||
expect(meta.get('workspaceVersion')).toBe(1);
|
||||
expect(blockVersions.get('affine:code')).toBe(
|
||||
originalVersions.get('affine:code')
|
||||
);
|
||||
expect((meta.get('pages') as YArray<unknown>).length).toBe(
|
||||
(originalMeta.get('pages') as YArray<unknown>).length
|
||||
);
|
||||
|
||||
expect(blockVersions.get('affine:embed')).toBeUndefined();
|
||||
expect(blockVersions.get('affine:image')).toBe(
|
||||
originalVersions.get('affine:embed')
|
||||
);
|
||||
|
||||
expect(blockVersions.get('affine:frame')).toBeUndefined();
|
||||
expect(blockVersions.get('affine:note')).toBe(
|
||||
originalVersions.get('affine:frame')
|
||||
);
|
||||
});
|
||||
});
|
||||
BIN
packages/env/src/__tests__/workspace.ydoc
vendored
BIN
packages/env/src/__tests__/workspace.ydoc
vendored
Binary file not shown.
6
packages/env/src/blocksuite/index.ts
vendored
6
packages/env/src/blocksuite/index.ts
vendored
@@ -3,14 +3,12 @@ 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);
|
||||
page.addBlock('affine:paragraph', {}, noteBlockId);
|
||||
}
|
||||
|
||||
export * from './subdoc-migration.js';
|
||||
|
||||
267
packages/env/src/blocksuite/subdoc-migration.ts
vendored
267
packages/env/src/blocksuite/subdoc-migration.ts
vendored
@@ -1,267 +0,0 @@
|
||||
import type { Schema } from '@blocksuite/store';
|
||||
import { Array as YArray, Doc as YDoc, Map as YMap } from 'yjs';
|
||||
|
||||
type XYWH = [number, number, number, number];
|
||||
|
||||
function deserializeXYWH(xywh: string): XYWH {
|
||||
return JSON.parse(xywh) as XYWH;
|
||||
}
|
||||
|
||||
function migrateDatabase(data: YMap<unknown>) {
|
||||
data.delete('prop:mode');
|
||||
data.set('prop:views', new YArray());
|
||||
const columns = (data.get('prop:columns') as YArray<unknown>).toJSON() as {
|
||||
id: string;
|
||||
name: string;
|
||||
hide: boolean;
|
||||
type: string;
|
||||
width: number;
|
||||
selection?: unknown[];
|
||||
}[];
|
||||
const views = [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Table',
|
||||
columns: columns.map(col => ({
|
||||
id: col.id,
|
||||
width: col.width,
|
||||
hide: col.hide,
|
||||
})),
|
||||
filter: { type: 'group', op: 'and', conditions: [] },
|
||||
mode: 'table',
|
||||
},
|
||||
];
|
||||
const cells = (data.get('prop:cells') as YMap<unknown>).toJSON() as Record<
|
||||
string,
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
value: unknown;
|
||||
}
|
||||
>
|
||||
>;
|
||||
const convertColumn = (
|
||||
id: string,
|
||||
update: (cell: { id: string; value: unknown }) => void
|
||||
) => {
|
||||
Object.values(cells).forEach(row => {
|
||||
if (row[id] != null) {
|
||||
update(row[id]);
|
||||
}
|
||||
});
|
||||
};
|
||||
const newColumns = columns.map(v => {
|
||||
let data: Record<string, unknown> = {};
|
||||
if (v.type === 'select' || v.type === 'multi-select') {
|
||||
data = { options: v.selection };
|
||||
if (v.type === 'select') {
|
||||
convertColumn(v.id, cell => {
|
||||
if (Array.isArray(cell.value)) {
|
||||
cell.value = cell.value[0]?.id;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
convertColumn(v.id, cell => {
|
||||
if (Array.isArray(cell.value)) {
|
||||
cell.value = cell.value.map(v => v.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (v.type === 'number') {
|
||||
convertColumn(v.id, cell => {
|
||||
if (typeof cell.value === 'string') {
|
||||
cell.value = Number.parseFloat(cell.value.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
id: v.id,
|
||||
type: v.type,
|
||||
name: v.name,
|
||||
data,
|
||||
};
|
||||
});
|
||||
data.set('prop:columns', newColumns);
|
||||
data.set('prop:views', views);
|
||||
data.set('prop:cells', cells);
|
||||
}
|
||||
|
||||
function runBlockMigration(
|
||||
flavour: string,
|
||||
data: YMap<unknown>,
|
||||
version: number
|
||||
) {
|
||||
if (flavour === 'affine:frame') {
|
||||
data.set('sys:flavour', 'affine:note');
|
||||
return;
|
||||
}
|
||||
if (flavour === 'affine:surface' && version <= 3) {
|
||||
if (data.has('elements')) {
|
||||
const elements = data.get('elements') as YMap<unknown>;
|
||||
migrateSurface(elements);
|
||||
data.set('prop:elements', elements.clone());
|
||||
data.delete('elements');
|
||||
} else {
|
||||
data.set('prop:elements', new YMap());
|
||||
}
|
||||
}
|
||||
if (flavour === 'affine:embed') {
|
||||
data.set('sys:flavour', 'affine:image');
|
||||
data.delete('prop:type');
|
||||
}
|
||||
if (flavour === 'affine:database' && version < 2) {
|
||||
migrateDatabase(data);
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSurface(data: YMap<unknown>) {
|
||||
for (const [, value] of <IterableIterator<[string, YMap<unknown>]>>(
|
||||
data.entries()
|
||||
)) {
|
||||
if (value.get('type') === 'connector') {
|
||||
migrateSurfaceConnector(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSurfaceConnector(data: YMap<any>) {
|
||||
let id = data.get('startElement')?.id;
|
||||
const controllers = data.get('controllers');
|
||||
const length = controllers.length;
|
||||
const xywh = deserializeXYWH(data.get('xywh'));
|
||||
if (id) {
|
||||
data.set('source', { id });
|
||||
} else {
|
||||
data.set('source', {
|
||||
position: [controllers[0].x + xywh[0], controllers[0].y + xywh[1]],
|
||||
});
|
||||
}
|
||||
|
||||
id = data.get('endElement')?.id;
|
||||
if (id) {
|
||||
data.set('target', { id });
|
||||
} else {
|
||||
data.set('target', {
|
||||
position: [
|
||||
controllers[length - 1].x + xywh[0],
|
||||
controllers[length - 1].y + xywh[1],
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const width = data.get('lineWidth') ?? 4;
|
||||
data.set('strokeWidth', width);
|
||||
const color = data.get('color');
|
||||
data.set('stroke', color);
|
||||
|
||||
data.delete('startElement');
|
||||
data.delete('endElement');
|
||||
data.delete('controllers');
|
||||
data.delete('lineWidth');
|
||||
data.delete('color');
|
||||
data.delete('xywh');
|
||||
}
|
||||
|
||||
function updateBlockVersions(versions: YMap<number>) {
|
||||
const frameVersion = versions.get('affine:frame');
|
||||
if (frameVersion !== undefined) {
|
||||
versions.set('affine:note', frameVersion);
|
||||
versions.delete('affine:frame');
|
||||
}
|
||||
const embedVersion = versions.get('affine:embed');
|
||||
if (embedVersion !== undefined) {
|
||||
versions.set('affine:image', embedVersion);
|
||||
versions.delete('affine:embed');
|
||||
}
|
||||
const databaseVersion = versions.get('affine:database');
|
||||
if (databaseVersion !== undefined && databaseVersion < 2) {
|
||||
versions.set('affine:database', 2);
|
||||
}
|
||||
}
|
||||
|
||||
function migrateMeta(oldDoc: YDoc, newDoc: YDoc) {
|
||||
const originalMeta = oldDoc.getMap('space:meta');
|
||||
const originalVersions = originalMeta.get('versions') as YMap<number>;
|
||||
const originalPages = originalMeta.get('pages') as YArray<YMap<unknown>>;
|
||||
const meta = newDoc.getMap('meta');
|
||||
const pages = new YArray();
|
||||
const blockVersions = originalVersions.clone();
|
||||
|
||||
meta.set('workspaceVersion', 1);
|
||||
meta.set('blockVersions', blockVersions);
|
||||
meta.set('pages', pages);
|
||||
meta.set('name', originalMeta.get('name') as string);
|
||||
|
||||
updateBlockVersions(blockVersions);
|
||||
const mapList = originalPages.map(page => {
|
||||
const map = new YMap();
|
||||
Array.from(page.entries())
|
||||
.filter(([key]) => key !== 'subpageIds')
|
||||
.forEach(([key, value]) => {
|
||||
map.set(key, value);
|
||||
});
|
||||
return map;
|
||||
});
|
||||
pages.push(mapList);
|
||||
}
|
||||
|
||||
function migrateBlocks(oldDoc: YDoc, newDoc: YDoc) {
|
||||
const spaces = newDoc.getMap('spaces');
|
||||
const originalMeta = oldDoc.getMap('space:meta');
|
||||
const originalVersions = originalMeta.get('versions') as YMap<number>;
|
||||
const originalPages = originalMeta.get('pages') as YArray<YMap<unknown>>;
|
||||
originalPages.forEach(page => {
|
||||
const id = page.get('id') as string;
|
||||
const spaceId = id.startsWith('space:') ? id : `space:${id}`;
|
||||
const originalBlocks = oldDoc.getMap(spaceId) as YMap<unknown>;
|
||||
const subdoc = new YDoc();
|
||||
spaces.set(spaceId, subdoc);
|
||||
const blocks = subdoc.getMap('blocks');
|
||||
Array.from(originalBlocks.entries()).forEach(([key, value]) => {
|
||||
const blockData = value.clone();
|
||||
blocks.set(key, blockData);
|
||||
const flavour = blockData.get('sys:flavour') as string;
|
||||
const version = originalVersions.get(flavour);
|
||||
if (version !== undefined) {
|
||||
runBlockMigration(flavour, blockData, version);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function migrateToSubdoc(doc: YDoc): YDoc {
|
||||
const needMigration = Array.from(doc.getMap('space:meta').keys()).length > 0;
|
||||
if (!needMigration) {
|
||||
return doc;
|
||||
}
|
||||
const output = new YDoc();
|
||||
migrateMeta(doc, output);
|
||||
migrateBlocks(doc, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function migrateDatabaseBlockTo3(rootDoc: YDoc, schema: Schema) {
|
||||
const spaces = rootDoc.getMap('spaces') as YMap<any>;
|
||||
spaces.forEach(space => {
|
||||
schema.upgradePage(
|
||||
{
|
||||
'affine:note': 1,
|
||||
'affine:bookmark': 1,
|
||||
'affine:database': 2,
|
||||
'affine:divider': 1,
|
||||
'affine:image': 1,
|
||||
'affine:list': 1,
|
||||
'affine:code': 1,
|
||||
'affine:page': 2,
|
||||
'affine:paragraph': 1,
|
||||
'affine:surface': 3,
|
||||
},
|
||||
space
|
||||
);
|
||||
});
|
||||
const meta = rootDoc.getMap('meta') as YMap<unknown>;
|
||||
const versions = meta.get('blockVersions') as YMap<number>;
|
||||
versions.set('affine:database', 3);
|
||||
}
|
||||
5
packages/env/src/workspace.ts
vendored
5
packages/env/src/workspace.ts
vendored
@@ -10,11 +10,6 @@ import type { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import type { Collection } from './filter.js';
|
||||
|
||||
export enum WorkspaceVersion {
|
||||
SubDoc = 2,
|
||||
DatabaseV3 = 3,
|
||||
}
|
||||
|
||||
export enum WorkspaceSubPath {
|
||||
ALL = 'all',
|
||||
SETTING = 'setting',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/graphql",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"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-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly"
|
||||
"@blocksuite/block-std": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-nightly"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@affine/y-provider": "workspace:*",
|
||||
@@ -53,5 +53,5 @@
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"version": "0.8.0-beta.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"version": "0.8.0-beta.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -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,20 +50,21 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@affine/sdk": "workspace:*",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"jotai": "^2.3.1",
|
||||
"zod": "^3.22.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"async-call-rpc": "^6.3.1",
|
||||
"electron": "link:../../apps/electron/node_modules/electron",
|
||||
"react": "^18.2.0",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-dts": "3.5.2"
|
||||
"vite-plugin-dts": "3.5.2",
|
||||
"yjs": "^13.6.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@affine/templates": "*",
|
||||
@@ -71,7 +72,8 @@
|
||||
"@blocksuite/lit": "*",
|
||||
"async-call-rpc": "*",
|
||||
"electron": "*",
|
||||
"react": "*"
|
||||
"react": "*",
|
||||
"yjs": "^13"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@affine/templates": {
|
||||
@@ -91,7 +93,10 @@
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"yjs": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"version": "0.8.0-beta.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { PageMeta, Workspace } from '@blocksuite/store';
|
||||
import { createIndexeddbStorage } from '@blocksuite/store';
|
||||
import type { createStore, WritableAtom } from 'jotai/vanilla';
|
||||
import { Array as YArray, Doc as YDoc, Map as YMap } from 'yjs';
|
||||
|
||||
export async function buildShowcaseWorkspace(
|
||||
workspace: Workspace,
|
||||
@@ -26,7 +28,9 @@ export async function buildShowcaseWorkspace(
|
||||
'affine:bookmark': 1,
|
||||
'affine:database': 2,
|
||||
};
|
||||
workspace.doc.getMap('meta').set('blockVersions', showcaseWorkspaceVersions);
|
||||
workspace.doc
|
||||
.getMap('meta')
|
||||
.set('blockVersions', new YMap(Object.entries(showcaseWorkspaceVersions)));
|
||||
const prototypes = {
|
||||
tags: {
|
||||
options: [
|
||||
@@ -188,3 +192,380 @@ export async function buildShowcaseWorkspace(
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
const migrationOrigin = 'affine-migration';
|
||||
|
||||
import type { Schema } from '@blocksuite/store';
|
||||
|
||||
type XYWH = [number, number, number, number];
|
||||
|
||||
function deserializeXYWH(xywh: string): XYWH {
|
||||
return JSON.parse(xywh) as XYWH;
|
||||
}
|
||||
|
||||
function migrateDatabase(data: YMap<unknown>) {
|
||||
data.delete('prop:mode');
|
||||
data.set('prop:views', new YArray());
|
||||
const columns = (data.get('prop:columns') as YArray<unknown>).toJSON() as {
|
||||
id: string;
|
||||
name: string;
|
||||
hide: boolean;
|
||||
type: string;
|
||||
width: number;
|
||||
selection?: unknown[];
|
||||
}[];
|
||||
const views = [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Table',
|
||||
columns: columns.map(col => ({
|
||||
id: col.id,
|
||||
width: col.width,
|
||||
hide: col.hide,
|
||||
})),
|
||||
filter: { type: 'group', op: 'and', conditions: [] },
|
||||
mode: 'table',
|
||||
},
|
||||
];
|
||||
const cells = (data.get('prop:cells') as YMap<unknown>).toJSON() as Record<
|
||||
string,
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
value: unknown;
|
||||
}
|
||||
>
|
||||
>;
|
||||
const convertColumn = (
|
||||
id: string,
|
||||
update: (cell: { id: string; value: unknown }) => void
|
||||
) => {
|
||||
Object.values(cells).forEach(row => {
|
||||
if (row[id] != null) {
|
||||
update(row[id]);
|
||||
}
|
||||
});
|
||||
};
|
||||
const newColumns = columns.map(v => {
|
||||
let data: Record<string, unknown> = {};
|
||||
if (v.type === 'select' || v.type === 'multi-select') {
|
||||
data = { options: v.selection };
|
||||
if (v.type === 'select') {
|
||||
convertColumn(v.id, cell => {
|
||||
if (Array.isArray(cell.value)) {
|
||||
cell.value = cell.value[0]?.id;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
convertColumn(v.id, cell => {
|
||||
if (Array.isArray(cell.value)) {
|
||||
cell.value = cell.value.map(v => v.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (v.type === 'number') {
|
||||
convertColumn(v.id, cell => {
|
||||
if (typeof cell.value === 'string') {
|
||||
cell.value = Number.parseFloat(cell.value.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
id: v.id,
|
||||
type: v.type,
|
||||
name: v.name,
|
||||
data,
|
||||
};
|
||||
});
|
||||
data.set('prop:columns', newColumns);
|
||||
data.set('prop:views', views);
|
||||
data.set('prop:cells', cells);
|
||||
}
|
||||
|
||||
function runBlockMigration(
|
||||
flavour: string,
|
||||
data: YMap<unknown>,
|
||||
version: number
|
||||
) {
|
||||
if (flavour === 'affine:frame') {
|
||||
data.set('sys:flavour', 'affine:note');
|
||||
return;
|
||||
}
|
||||
if (flavour === 'affine:surface' && version <= 3) {
|
||||
if (data.has('elements')) {
|
||||
const elements = data.get('elements') as YMap<unknown>;
|
||||
migrateSurface(elements);
|
||||
data.set('prop:elements', elements.clone());
|
||||
data.delete('elements');
|
||||
} else {
|
||||
data.set('prop:elements', new YMap());
|
||||
}
|
||||
}
|
||||
if (flavour === 'affine:embed') {
|
||||
data.set('sys:flavour', 'affine:image');
|
||||
data.delete('prop:type');
|
||||
}
|
||||
if (flavour === 'affine:database' && version < 2) {
|
||||
migrateDatabase(data);
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSurface(data: YMap<unknown>) {
|
||||
for (const [, value] of <IterableIterator<[string, YMap<unknown>]>>(
|
||||
data.entries()
|
||||
)) {
|
||||
if (value.get('type') === 'connector') {
|
||||
migrateSurfaceConnector(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSurfaceConnector(data: YMap<any>) {
|
||||
let id = data.get('startElement')?.id;
|
||||
const controllers = data.get('controllers');
|
||||
const length = controllers.length;
|
||||
const xywh = deserializeXYWH(data.get('xywh'));
|
||||
if (id) {
|
||||
data.set('source', { id });
|
||||
} else {
|
||||
data.set('source', {
|
||||
position: [controllers[0].x + xywh[0], controllers[0].y + xywh[1]],
|
||||
});
|
||||
}
|
||||
|
||||
id = data.get('endElement')?.id;
|
||||
if (id) {
|
||||
data.set('target', { id });
|
||||
} else {
|
||||
data.set('target', {
|
||||
position: [
|
||||
controllers[length - 1].x + xywh[0],
|
||||
controllers[length - 1].y + xywh[1],
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const width = data.get('lineWidth') ?? 4;
|
||||
data.set('strokeWidth', width);
|
||||
const color = data.get('color');
|
||||
data.set('stroke', color);
|
||||
|
||||
data.delete('startElement');
|
||||
data.delete('endElement');
|
||||
data.delete('controllers');
|
||||
data.delete('lineWidth');
|
||||
data.delete('color');
|
||||
data.delete('xywh');
|
||||
}
|
||||
|
||||
function updateBlockVersions(versions: YMap<number>) {
|
||||
const frameVersion = versions.get('affine:frame');
|
||||
if (frameVersion !== undefined) {
|
||||
versions.set('affine:note', frameVersion);
|
||||
versions.delete('affine:frame');
|
||||
}
|
||||
const embedVersion = versions.get('affine:embed');
|
||||
if (embedVersion !== undefined) {
|
||||
versions.set('affine:image', embedVersion);
|
||||
versions.delete('affine:embed');
|
||||
}
|
||||
const databaseVersion = versions.get('affine:database');
|
||||
if (databaseVersion !== undefined && databaseVersion < 2) {
|
||||
versions.set('affine:database', 2);
|
||||
}
|
||||
}
|
||||
|
||||
function migrateMeta(oldDoc: YDoc, newDoc: YDoc) {
|
||||
const originalMeta = oldDoc.getMap('space:meta');
|
||||
const originalVersions = originalMeta.get('versions') as YMap<number>;
|
||||
const originalPages = originalMeta.get('pages') as YArray<YMap<unknown>>;
|
||||
const meta = newDoc.getMap('meta');
|
||||
const pages = new YArray();
|
||||
const blockVersions = originalVersions.clone();
|
||||
|
||||
meta.set('workspaceVersion', 1);
|
||||
meta.set('blockVersions', blockVersions);
|
||||
meta.set('pages', pages);
|
||||
meta.set('name', originalMeta.get('name') as string);
|
||||
|
||||
updateBlockVersions(blockVersions);
|
||||
const mapList = originalPages.map(page => {
|
||||
const map = new YMap();
|
||||
Array.from(page.entries())
|
||||
.filter(([key]) => key !== 'subpageIds')
|
||||
.forEach(([key, value]) => {
|
||||
map.set(key, value);
|
||||
});
|
||||
return map;
|
||||
});
|
||||
pages.push(mapList);
|
||||
}
|
||||
|
||||
function migrateBlocks(oldDoc: YDoc, newDoc: YDoc) {
|
||||
const spaces = newDoc.getMap('spaces');
|
||||
const originalMeta = oldDoc.getMap('space:meta');
|
||||
const originalVersions = originalMeta.get('versions') as YMap<number>;
|
||||
const originalPages = originalMeta.get('pages') as YArray<YMap<unknown>>;
|
||||
originalPages.forEach(page => {
|
||||
const id = page.get('id') as string;
|
||||
const spaceId = id.startsWith('space:') ? id : `space:${id}`;
|
||||
const originalBlocks = oldDoc.getMap(spaceId) as YMap<unknown>;
|
||||
const subdoc = new YDoc();
|
||||
spaces.set(spaceId, subdoc);
|
||||
const blocks = subdoc.getMap('blocks');
|
||||
Array.from(originalBlocks.entries()).forEach(([key, value]) => {
|
||||
const blockData = value.clone();
|
||||
blocks.set(key, blockData);
|
||||
const flavour = blockData.get('sys:flavour') as string;
|
||||
const version = originalVersions.get(flavour);
|
||||
if (version !== undefined) {
|
||||
runBlockMigration(flavour, blockData, version);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function migrateToSubdoc(oldDoc: YDoc): YDoc {
|
||||
const needMigration =
|
||||
Array.from(oldDoc.getMap('space:meta').keys()).length > 0;
|
||||
if (!needMigration) {
|
||||
return oldDoc;
|
||||
}
|
||||
const newDoc = new YDoc();
|
||||
migrateMeta(oldDoc, newDoc);
|
||||
migrateBlocks(oldDoc, newDoc);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
export async function migrateDatabaseBlockTo3(rootDoc: YDoc, schema: Schema) {
|
||||
const spaces = rootDoc.getMap('spaces') as YMap<any>;
|
||||
spaces.forEach(space => {
|
||||
schema.upgradePage(
|
||||
{
|
||||
'affine:note': 1,
|
||||
'affine:bookmark': 1,
|
||||
'affine:database': 2,
|
||||
'affine:divider': 1,
|
||||
'affine:image': 1,
|
||||
'affine:list': 1,
|
||||
'affine:code': 1,
|
||||
'affine:page': 2,
|
||||
'affine:paragraph': 1,
|
||||
'affine:surface': 3,
|
||||
},
|
||||
space
|
||||
);
|
||||
});
|
||||
const meta = rootDoc.getMap('meta') as YMap<unknown>;
|
||||
const versions = meta.get('blockVersions') as YMap<number>;
|
||||
versions.set('affine:database', 3);
|
||||
}
|
||||
|
||||
export type UpgradeOptions = {
|
||||
getCurrentRootDoc: () => Promise<YDoc>;
|
||||
createWorkspace: () => Promise<Workspace>;
|
||||
getSchema: () => Schema;
|
||||
};
|
||||
|
||||
const upgradeV1ToV2 = async (options: UpgradeOptions) => {
|
||||
const oldDoc = await options.getCurrentRootDoc();
|
||||
const newDoc = migrateToSubdoc(oldDoc);
|
||||
const newWorkspace = await options.createWorkspace();
|
||||
applyUpdate(newWorkspace.doc, encodeStateAsUpdate(newDoc), migrationOrigin);
|
||||
newDoc.getSubdocs().forEach(subdoc => {
|
||||
newWorkspace.doc.getSubdocs().forEach(newDoc => {
|
||||
if (subdoc.guid === newDoc.guid) {
|
||||
applyUpdate(newDoc, encodeStateAsUpdate(subdoc), migrationOrigin);
|
||||
}
|
||||
});
|
||||
});
|
||||
return newWorkspace;
|
||||
};
|
||||
|
||||
const upgradeV2ToV3 = async (options: UpgradeOptions): Promise<boolean> => {
|
||||
const rootDoc = await options.getCurrentRootDoc();
|
||||
const spaces = rootDoc.getMap('spaces') as YMap<any>;
|
||||
const meta = rootDoc.getMap('meta') as YMap<unknown>;
|
||||
const versions = meta.get('blockVersions') as YMap<number>;
|
||||
if ('affine:database' in versions) {
|
||||
if (versions['affine:database'] === 3) {
|
||||
return false;
|
||||
}
|
||||
} else if (versions.get('affine:database') === 3) {
|
||||
return false;
|
||||
}
|
||||
const schema = options.getSchema();
|
||||
spaces.forEach(space => {
|
||||
schema.upgradePage(
|
||||
{
|
||||
'affine:note': 1,
|
||||
'affine:bookmark': 1,
|
||||
'affine:database': 2,
|
||||
'affine:divider': 1,
|
||||
'affine:image': 1,
|
||||
'affine:list': 1,
|
||||
'affine:code': 1,
|
||||
'affine:page': 2,
|
||||
'affine:paragraph': 1,
|
||||
'affine:surface': 3,
|
||||
},
|
||||
space
|
||||
);
|
||||
});
|
||||
if ('affine:database' in versions) {
|
||||
versions['affine:database'] = 3;
|
||||
meta.set('blockVersions', new YMap(Object.entries(versions)));
|
||||
} else {
|
||||
versions.set('affine:database', 3);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export enum WorkspaceVersion {
|
||||
// v1 is treated as undefined
|
||||
SubDoc = 2,
|
||||
DatabaseV3 = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* If returns false, it means no migration is needed.
|
||||
* If returns true, it means migration is done.
|
||||
* If returns Workspace, it means new workspace is created,
|
||||
* and the old workspace should be deleted.
|
||||
*/
|
||||
export async function migrateWorkspace(
|
||||
currentVersion: WorkspaceVersion | undefined,
|
||||
options: UpgradeOptions
|
||||
): Promise<Workspace | boolean> {
|
||||
if (currentVersion === undefined) {
|
||||
const workspace = await upgradeV1ToV2(options);
|
||||
await upgradeV2ToV3({
|
||||
...options,
|
||||
getCurrentRootDoc: () => Promise.resolve(workspace.doc),
|
||||
});
|
||||
return workspace;
|
||||
}
|
||||
if (currentVersion === WorkspaceVersion.SubDoc) {
|
||||
return upgradeV2ToV3(options);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateLocalBlobStorage(from: string, to: string) {
|
||||
const fromStorage = createIndexeddbStorage(from);
|
||||
const toStorage = createIndexeddbStorage(to);
|
||||
const keys = await fromStorage.crud.list();
|
||||
for (const key of keys) {
|
||||
const value = await fromStorage.crud.get(key);
|
||||
if (!value) {
|
||||
console.warn('cannot find blob:', key);
|
||||
continue;
|
||||
}
|
||||
await toStorage.crud.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export default defineConfig({
|
||||
'rxjs',
|
||||
'zod',
|
||||
'react',
|
||||
'yjs',
|
||||
/^jotai/,
|
||||
/^@blocksuite/,
|
||||
/^@affine\/templates/,
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
"jotai": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/block-std": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/block-std": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/editor": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/lit": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"lottie-web": "^5.12.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -23,5 +23,5 @@
|
||||
"@blocksuite/store": "*",
|
||||
"lottie-web": "*"
|
||||
},
|
||||
"version": "0.8.0-beta.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
15
packages/native/__tests__/db.spec.mts
Normal file
15
packages/native/__tests__/db.spec.mts
Normal file
@@ -0,0 +1,15 @@
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { SqliteConnection, ValidationResult } from '../index';
|
||||
|
||||
test('db', { concurrency: false }, async t => {
|
||||
await t.test('validate', async () => {
|
||||
const path = fileURLToPath(
|
||||
new URL('./fixtures/test01.affine', import.meta.url)
|
||||
);
|
||||
const result = await SqliteConnection.validate(path);
|
||||
assert.equal(result, ValidationResult.MissingVersionColumn);
|
||||
});
|
||||
});
|
||||
BIN
packages/native/__tests__/fixtures/test01.affine
Normal file
BIN
packages/native/__tests__/fixtures/test01.affine
Normal file
Binary file not shown.
7
packages/native/index.d.ts
vendored
7
packages/native/index.d.ts
vendored
@@ -41,8 +41,9 @@ export interface InsertRow {
|
||||
export enum ValidationResult {
|
||||
MissingTables = 0,
|
||||
MissingDocIdColumn = 1,
|
||||
GeneralError = 2,
|
||||
Valid = 3,
|
||||
MissingVersionColumn = 2,
|
||||
GeneralError = 3,
|
||||
Valid = 4,
|
||||
}
|
||||
export class Subscription {
|
||||
toString(): string;
|
||||
@@ -75,6 +76,8 @@ export class SqliteConnection {
|
||||
docId: string | undefined | null,
|
||||
updates: Array<InsertRow>
|
||||
): Promise<void>;
|
||||
initVersion(): Promise<void>;
|
||||
setVersion(version: number): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
get isClose(): boolean;
|
||||
static validate(path: string): Promise<ValidationResult>;
|
||||
|
||||
@@ -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.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -11,4 +11,9 @@ CREATE TABLE IF NOT EXISTS "blobs" (
|
||||
key TEXT PRIMARY KEY NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);"#;
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "version_info" (
|
||||
version NUMBER NOT NULL,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
)
|
||||
"#;
|
||||
|
||||
@@ -7,6 +7,9 @@ use sqlx::{
|
||||
Pool, Row,
|
||||
};
|
||||
|
||||
// latest version
|
||||
const LATEST_VERSION: i32 = 3;
|
||||
|
||||
#[napi(object)]
|
||||
pub struct BlobRow {
|
||||
pub key: String,
|
||||
@@ -38,6 +41,7 @@ pub struct SqliteConnection {
|
||||
pub enum ValidationResult {
|
||||
MissingTables,
|
||||
MissingDocIdColumn,
|
||||
MissingVersionColumn,
|
||||
GeneralError,
|
||||
Valid,
|
||||
}
|
||||
@@ -228,6 +232,39 @@ impl SqliteConnection {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn init_version(&self) -> napi::Result<()> {
|
||||
// create version_info table
|
||||
sqlx::query!(
|
||||
"CREATE TABLE IF NOT EXISTS version_info (
|
||||
version NUMBER NOT NULL,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
)"
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?;
|
||||
// `3` is the first version that has version_info table,
|
||||
// do not modify the version number.
|
||||
sqlx::query!("INSERT INTO version_info (version) VALUES (3)")
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn set_version(&self, version: i32) -> napi::Result<()> {
|
||||
if version > LATEST_VERSION {
|
||||
return Err(anyhow::Error::msg("Version is too new").into());
|
||||
}
|
||||
sqlx::query!("UPDATE version_info SET version = ?", version)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn close(&self) {
|
||||
self.pool.close().await;
|
||||
@@ -261,6 +298,18 @@ impl SqliteConnection {
|
||||
Err(_) => return ValidationResult::GeneralError,
|
||||
};
|
||||
|
||||
let tables_res = sqlx::query("SELECT name FROM sqlite_master WHERE type='table'")
|
||||
.fetch_all(&pool)
|
||||
.await;
|
||||
|
||||
let version_exist = match tables_res {
|
||||
Ok(res) => {
|
||||
let names: Vec<String> = res.iter().map(|row| row.get(0)).collect();
|
||||
names.contains(&"version_info".to_string())
|
||||
}
|
||||
Err(_) => return ValidationResult::GeneralError,
|
||||
};
|
||||
|
||||
let columns_res = sqlx::query("PRAGMA table_info(updates)")
|
||||
.fetch_all(&pool)
|
||||
.await;
|
||||
@@ -277,6 +326,8 @@ impl SqliteConnection {
|
||||
ValidationResult::MissingTables
|
||||
} else if !doc_id_exist {
|
||||
ValidationResult::MissingDocIdColumn
|
||||
} else if !version_exist {
|
||||
ValidationResult::MissingVersionColumn
|
||||
} else {
|
||||
ValidationResult::Valid
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/plugin-cli",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"bin": {
|
||||
"af": "./src/af.mjs"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/sdk",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
@@ -22,9 +22,9 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@blocksuite/blocks": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/global": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"jotai": "^2.3.1",
|
||||
"zod": "^3.22.1"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/storage",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"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.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@affine/workers",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "wrangler dev"
|
||||
|
||||
@@ -34,5 +34,5 @@
|
||||
"@types/ws": "^8.5.5",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"version": "0.8.0-beta.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { WorkspaceAdapter } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { BlockHub } from '@blocksuite/blocks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
||||
import { atom } from 'jotai';
|
||||
import { z } from 'zod';
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||
import type { LocalWorkspace } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||
import { nanoid, Workspace } from '@blocksuite/store';
|
||||
import { createIndexeddbStorage } from '@blocksuite/store';
|
||||
const Y = Workspace.Y;
|
||||
|
||||
export function upgradeV1ToV2(oldWorkspace: LocalWorkspace): LocalWorkspace {
|
||||
const oldDoc = oldWorkspace.blockSuiteWorkspace.doc;
|
||||
const newDoc = migrateToSubdoc(oldDoc);
|
||||
if (newDoc === oldDoc) {
|
||||
console.warn('do not need update');
|
||||
return oldWorkspace;
|
||||
} else {
|
||||
const id = nanoid();
|
||||
const newBlockSuiteWorkspace = getOrCreateWorkspace(
|
||||
id,
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
Y.applyUpdate(newBlockSuiteWorkspace.doc, Y.encodeStateAsUpdate(newDoc));
|
||||
newDoc.getSubdocs().forEach(subdoc => {
|
||||
newBlockSuiteWorkspace.doc.getSubdocs().forEach(newDoc => {
|
||||
if (subdoc.guid === newDoc.guid) {
|
||||
Y.applyUpdate(newDoc, Y.encodeStateAsUpdate(subdoc));
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log('migration result', newBlockSuiteWorkspace.doc.toJSON());
|
||||
|
||||
return {
|
||||
blockSuiteWorkspace: newBlockSuiteWorkspace,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateLocalBlobStorage(from: string, to: string) {
|
||||
const fromStorage = createIndexeddbStorage(from);
|
||||
const toStorage = createIndexeddbStorage(to);
|
||||
const keys = await fromStorage.crud.list();
|
||||
for (const key of keys) {
|
||||
const value = await fromStorage.crud.get(key);
|
||||
if (!value) {
|
||||
console.warn('cannot find blob:', key);
|
||||
continue;
|
||||
}
|
||||
await toStorage.crud.set(key, value);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@toeverything/y-indexeddb",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"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-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230821045124-712e6c3d-nightly",
|
||||
"@blocksuite/blocks": "0.0.0-20230830111255-92eab248-nightly",
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-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.1",
|
||||
"version": "0.8.3",
|
||||
"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-20230821045124-712e6c3d-nightly"
|
||||
"@blocksuite/store": "0.0.0-20230830111255-92eab248-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.1",
|
||||
"version": "0.8.3",
|
||||
"description": "Bookmark Plugin",
|
||||
"affinePlugin": {
|
||||
"release": true,
|
||||
|
||||
@@ -183,7 +183,7 @@ const BookMarkUI = ({ page }: BookMarkProps) => {
|
||||
if (!shouldShowBookmarkMenu(pastedBlocks)) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
setAnchor(getCurrentNativeRange());
|
||||
}, 100);
|
||||
});
|
||||
|
||||
@@ -35,5 +35,5 @@
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
},
|
||||
"version": "0.8.0-beta.1"
|
||||
"version": "0.8.3"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "Hello world plugin",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"scripts": {
|
||||
"dev": "af dev",
|
||||
"build": "af build"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@affine/image-preview-plugin",
|
||||
"type": "module",
|
||||
"version": "0.8.0-beta.1",
|
||||
"version": "0.8.3",
|
||||
"description": "Image preview plugin",
|
||||
"affinePlugin": {
|
||||
"release": true,
|
||||
|
||||
@@ -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.1",
|
||||
"version": "0.8.3",
|
||||
"scripts": {
|
||||
"dev": "af dev",
|
||||
"build": "af build"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user