Compare commits

...

38 Commits

Author SHA1 Message Date
Alex Yang
c78e7983f3 v0.8.3 2023-08-31 07:47:10 -05:00
Alex Yang
10aaaeb1d7 v0.8.3-beta.0 2023-08-31 07:45:27 -05:00
Alex Yang
13323bcbc0 chore: bump version 2023-08-30 13:06:58 -05:00
Alex Yang
06455bee5e v0.8.2 2023-08-30 00:12:35 -05:00
Alex Yang
aa40d4be8f v0.8.2-beta.0 2023-08-30 00:11:41 -05:00
Alex Yang
448496c245 fix(core): incorrect blocksuite data format (#4039) 2023-08-30 00:10:12 -05:00
Alex Yang
92714a588b v0.8.1 2023-08-28 21:01:04 -05:00
Alex Yang
d5d73db4d6 fix(electron): upgrade db file (#3984) 2023-08-28 20:55:24 -05:00
Alex Yang
3d887abefc refactor: migration logic (#3973) 2023-08-28 20:55:15 -05:00
Peng Xiao
14d0c8ba30 fix: reduce the number of files being packed (#3974) 2023-08-28 20:53:14 -05:00
Alex Yang
391ca0b934 docs: update README.md 2023-08-28 20:49:00 -05:00
Alex Yang
3a9265b280 v0.8.0-beta.4 2023-08-27 20:47:27 -05:00
Peng Xiao
4f93e7dbc8 feat: custom updater provider (#3959) 2023-08-27 20:44:53 -05:00
Alex Yang
e4543ef3b7 v0.8.0 2023-08-25 00:05:07 -05:00
Peng Xiao
b7f024eeb2 fix: update changelog 2023-08-25 00:05:07 -05:00
Peng Xiao
ebc98002ba fix: add missing matrix value (#3937) 2023-08-24 23:57:49 -05:00
Peng Xiao
9a89c08fd1 fix: incorrect workflow file (#3935) 2023-08-24 23:57:49 -05:00
Peng Xiao
d749e8a284 fix: disable windows signing for nightly (#3933) 2023-08-24 23:57:49 -05:00
Peng Xiao
0b54d82ddb fix: remove use of glob (#3932) 2023-08-24 23:57:49 -05:00
Alex Yang
a76d99381d fix: add missing package (#3927) 2023-08-24 23:57:49 -05:00
Peng Xiao
d89be5804f fix: support windows auto update (#3911) 2023-08-24 23:57:49 -05:00
Alex Yang
3ddc76a703 v0.8.0-beta.3 2023-08-23 00:41:21 -05:00
Alex Yang
9d6d5594b5 fix(core): search feature not working (#3902) 2023-08-22 19:43:09 -05:00
Alex Yang
96f3bbd484 fix(y-provider): syncing status (#3903) 2023-08-22 19:43:09 -05:00
fourdim
561970f7ff fix: make media print overflow visible (#3893) 2023-08-22 19:43:09 -05:00
Alex Yang
357b403073 chore: bump version (#3901) 2023-08-22 19:43:09 -05:00
Alex Yang
77d1dd674b v0.8.0-beta.2 2023-08-22 13:01:48 -05:00
Peng Xiao
7035584203 build: sign windows app (#3809)
(cherry picked from commit 7d6e91f56e)
2023-08-22 13:01:20 -05:00
Alex Yang
c55df09db0 test: loose cmdk result check (#3888)
(cherry picked from commit 507b5dcfb3)
2023-08-22 13:01:20 -05:00
Alex Yang
ddbc37dd45 chore: bump version (#3885)
(cherry picked from commit 8ec005f7de)
2023-08-22 13:01:20 -05:00
Alex Yang
e59ec2de62 test: fix flaky title insert (#3884)
(cherry picked from commit b5afbe385f)
2023-08-22 13:01:19 -05:00
Noothan am
73aea1e2d1 fix(core): add toast message (#3847)
(cherry picked from commit 2a5ef04397)
2023-08-22 13:01:19 -05:00
Alex Yang
38a50b4fe8 fix(cli): read environment variable (#3883)
(cherry picked from commit 58184679ca)
2023-08-22 13:01:19 -05:00
Alex Yang
c2d901c245 ci: do not build core in e2e test (#3882)
(cherry picked from commit bf00299bc7)
2023-08-22 13:01:19 -05:00
Camol
9eee00ddf3 fix: timers type in browser env (#3875)
(cherry picked from commit fc9981335b)
2023-08-22 13:01:19 -05:00
danielchim
96dcd84ee1 feat: e2e for recent search list (#3872)
(cherry picked from commit eda5ff4d3f)
2023-08-22 13:01:19 -05:00
KaranPant
4d047db7ec fix: recent pages list doesn't update (#3848)
Co-authored-by: Alex Yang <himself65@outlook.com>
(cherry picked from commit 54d74f6f0b)
2023-08-22 13:01:14 -05:00
dependabot[bot]
aa254fc8fd chore: bump @storybook/jest from 0.1.0 to 0.2.1 (#3859)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
(cherry picked from commit c689c08b9a)
2023-08-22 13:01:11 -05:00
110 changed files with 2087 additions and 1142 deletions

View File

@@ -21,6 +21,7 @@
"native",
"templates",
"y-indexeddb",
"y-provider",
"debug",
"storage",
"infra",

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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].

View File

@@ -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,

View File

@@ -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",

View File

@@ -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}`);
});

View File

@@ -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}
/>

View File

@@ -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');
})()
})
);
}
});

View File

@@ -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;

View File

@@ -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"

View File

@@ -82,7 +82,7 @@ export const WorkspaceDeleteModal = ({
<Input
ref={ref => {
if (ref) {
setTimeout(() => ref.focus(), 0);
window.setTimeout(() => ref.focus(), 0);
}
}}
onChange={setDeleteStr}

View File

@@ -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(
() =>

View File

@@ -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]

View File

@@ -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();
});

View File

@@ -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,
});

View File

@@ -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 ? (
<>

View File

@@ -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>
);
}

View File

@@ -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);
}}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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);

View File

@@ -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
);

View File

@@ -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,
]
);

View File

@@ -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",

View File

@@ -1,4 +1,4 @@
owner: toeverything
repo: AFFiNE
provider: github
provider: custom
private: false

View File

@@ -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',
},
},

View File

@@ -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": {

View File

@@ -1,4 +1,4 @@
owner: toeverything
repo: AFFiNE
provider: github
provider: custom
private: false

View File

@@ -44,6 +44,7 @@ export const config = () => {
'electron-updater',
'@toeverything/plugin-infra',
'yjs',
'semver',
],
define: define,
format: 'cjs',

View File

@@ -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');

View File

@@ -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(

View 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,
};

View 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();

View File

@@ -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());

View File

@@ -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
) {

View 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`;
}

View File

@@ -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);

View File

@@ -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"]
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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": {

View File

@@ -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"
}

View File

@@ -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: '/',
},
}),
};

View File

@@ -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

View File

@@ -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"]'

View File

@@ -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",

View File

@@ -7,5 +7,5 @@
"@affine/env": "workspace:*",
"@toeverything/infra": "workspace:*"
},
"version": "0.8.0-beta.1"
"version": "0.8.3"
}

View File

@@ -20,5 +20,5 @@
"peerDependencies": {
"ts-node": "*"
},
"version": "0.8.0-beta.1"
"version": "0.8.3"
}

View File

@@ -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: () =>

View File

@@ -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"
}

View File

@@ -35,7 +35,7 @@ export type AppSidebarProps = PropsWithChildren<
function useEnableAnimation() {
const [enable, setEnable] = useState(false);
useEffect(() => {
setTimeout(() => {
window.setTimeout(() => {
setEnable(true);
}, 500);
}, []);

View File

@@ -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);
}

View File

@@ -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: {

View File

@@ -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);
}}

View File

@@ -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',

View File

@@ -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);
};

View File

@@ -8,5 +8,5 @@
"devDependencies": {
"@types/debug": "^4.1.8"
},
"version": "0.8.0-beta.1"
"version": "0.8.3"
}

View File

@@ -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"
}

View File

@@ -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')
);
});
});

Binary file not shown.

View File

@@ -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';

View File

@@ -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);
}

View File

@@ -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',

View File

@@ -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",

View File

@@ -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"
}

View File

@@ -37,5 +37,5 @@
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
},
"version": "0.8.0-beta.1"
"version": "0.8.3"
}

View File

@@ -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.",

View File

@@ -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"
}

View File

@@ -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);
}
}

View File

@@ -34,6 +34,7 @@ export default defineConfig({
'rxjs',
'zod',
'react',
'yjs',
/^jotai/,
/^@blocksuite/,
/^@affine\/templates/,

View File

@@ -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"
}

View 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);
});
});

Binary file not shown.

View File

@@ -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>;

View File

@@ -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"
}

View File

@@ -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
)
"#;

View File

@@ -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
}

View File

@@ -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"
},

View File

@@ -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"
},

View File

@@ -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"
},

View File

@@ -7,5 +7,5 @@
"./v1/*.json": "./v1/*.json",
"./preloading.json": "./preloading.json"
},
"version": "0.8.0-beta.1"
"version": "0.8.3"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@affine/workers",
"version": "0.8.0-beta.1",
"version": "0.8.3",
"private": true,
"scripts": {
"dev": "wrangler dev"

View File

@@ -34,5 +34,5 @@
"@types/ws": "^8.5.5",
"ws": "^8.13.0"
},
"version": "0.8.0-beta.1"
"version": "0.8.3"
}

View File

@@ -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';

View File

@@ -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);
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -183,7 +183,7 @@ const BookMarkUI = ({ page }: BookMarkProps) => {
if (!shouldShowBookmarkMenu(pastedBlocks)) {
return;
}
setTimeout(() => {
window.setTimeout(() => {
setAnchor(getCurrentNativeRange());
}, 100);
});

View File

@@ -35,5 +35,5 @@
"react": "*",
"react-dom": "*"
},
"version": "0.8.0-beta.1"
"version": "0.8.3"
}

View File

@@ -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"

View File

@@ -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,

View File

@@ -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);

View File

@@ -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