mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-05 11:35:34 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 984caedcea |
@@ -121,18 +121,6 @@
|
||||
"default": {
|
||||
"concurrency": 1
|
||||
}
|
||||
},
|
||||
"queues.backendRuntime": {
|
||||
"type": "object",
|
||||
"description": "The config for backend runtime job queue\n@default {\"concurrency\":1}",
|
||||
"properties": {
|
||||
"concurrency": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"concurrency": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -505,7 +493,6 @@
|
||||
"jurisdiction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"eu"
|
||||
],
|
||||
"description": "Optional jurisdiction for the cloudflare r2 endpoint. Set to \"eu\" for EU buckets."
|
||||
@@ -531,36 +518,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"assetpack"
|
||||
]
|
||||
},
|
||||
"bucket": {
|
||||
"type": "string"
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"provider",
|
||||
"bucket",
|
||||
"config"
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
@@ -734,7 +691,6 @@
|
||||
"jurisdiction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"eu"
|
||||
],
|
||||
"description": "Optional jurisdiction for the cloudflare r2 endpoint. Set to \"eu\" for EU buckets."
|
||||
@@ -760,36 +716,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"assetpack"
|
||||
]
|
||||
},
|
||||
"bucket": {
|
||||
"type": "string"
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"provider",
|
||||
"bucket",
|
||||
"config"
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
@@ -1406,7 +1332,6 @@
|
||||
"jurisdiction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"eu"
|
||||
],
|
||||
"description": "Optional jurisdiction for the cloudflare r2 endpoint. Set to \"eu\" for EU buckets."
|
||||
@@ -1432,36 +1357,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"assetpack"
|
||||
]
|
||||
},
|
||||
"bucket": {
|
||||
"type": "string"
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"provider",
|
||||
"bucket",
|
||||
"config"
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
|
||||
@@ -3,4 +3,4 @@ name: affine
|
||||
description: AFFiNE cloud chart
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.27.0"
|
||||
appVersion: "0.26.3"
|
||||
|
||||
@@ -3,7 +3,7 @@ name: doc
|
||||
description: AFFiNE doc server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.27.0"
|
||||
appVersion: "0.26.3"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -3,7 +3,7 @@ name: front
|
||||
description: AFFiNE front server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.27.0"
|
||||
appVersion: "0.26.3"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -3,7 +3,7 @@ name: graphql
|
||||
description: AFFiNE GraphQL server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.27.0"
|
||||
appVersion: "0.26.3"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -89,13 +89,9 @@ jobs:
|
||||
id: check
|
||||
run: node ./scripts/check-windows-signer.mjs
|
||||
env:
|
||||
AFFINE_SIGN_CLIENT_HASH: ${{ secrets.AFFINE_SIGN_CLIENT_HASH }}
|
||||
AFFINE_SIGNER_ADDR: ${{ secrets.AFFINE_SIGNER_ADDR }}
|
||||
AFFINE_SIGNER_TOKEN: ${{ secrets.AFFINE_SIGNER_TOKEN }}
|
||||
AFFINE_SIGNER_TS_AUTH_KEY: ${{ secrets.AFFINE_SIGNER_TS_AUTH_KEY }}
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
REQUIRE_SIGNER: ${{ inputs.require-windows-signing }}
|
||||
WINDOWS_SIGNER_PUBLIC_CERT_BASE64: ${{ secrets.WINDOWS_SIGNER_PUBLIC_CERT_BASE64 }}
|
||||
|
||||
make-distribution-macos:
|
||||
if: ${{ inputs.desktop_macos }}
|
||||
@@ -147,192 +143,262 @@ jobs:
|
||||
target: ${{ matrix.spec.target }}
|
||||
install_linux_deps: true
|
||||
|
||||
build-and-sign-windows:
|
||||
package-distribution-windows-x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
enable_scripts: true
|
||||
|
||||
package-distribution-windows-arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: windows-latest
|
||||
platform: win32
|
||||
arch: arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
enable_scripts: true
|
||||
|
||||
sign-packaged-artifacts-windows_x64:
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- before-make
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-x64
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-x64.outputs.files_to_be_signed }}
|
||||
artifact-name: packaged-win32-x64
|
||||
|
||||
sign-packaged-artifacts-windows_arm64:
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-arm64
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-arm64.outputs.files_to_be_signed }}
|
||||
artifact-name: packaged-win32-arm64
|
||||
|
||||
make-windows-installer:
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
inputs.desktop_windows &&
|
||||
needs.windows-signer-gate.result == 'success' &&
|
||||
needs.package-distribution-windows-x64.result == 'success' &&
|
||||
needs.package-distribution-windows-arm64.result == 'success' &&
|
||||
(
|
||||
!inputs.require-windows-signing ||
|
||||
(
|
||||
needs.sign-packaged-artifacts-windows_x64.result == 'success' &&
|
||||
needs.sign-packaged-artifacts-windows_arm64.result == 'success'
|
||||
)
|
||||
)
|
||||
}}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-x64
|
||||
- package-distribution-windows-arm64
|
||||
- sign-packaged-artifacts-windows_x64
|
||||
- sign-packaged-artifacts-windows_arm64
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
spec:
|
||||
- platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
- platform: win32
|
||||
arch: arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
AFFINE_SIGNER_ADDR: ${{ secrets.AFFINE_SIGNER_ADDR }}
|
||||
AFFINE_SIGNER_TOKEN: ${{ secrets.AFFINE_SIGNER_TOKEN }}
|
||||
APP_NAME: affine
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
DEBUG: 'affine:*,napi:*'
|
||||
RELEASE_VERSION: ${{ inputs.app-version }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_RELEASE: ${{ inputs.app-version }}
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
TS_RS_EXPERIMENT: this_is_unstable_software
|
||||
outputs:
|
||||
FILES_TO_BE_SIGNED_x64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_x64 }}
|
||||
FILES_TO_BE_SIGNED_arm64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_arm64 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
|
||||
enableScripts: true
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo
|
||||
hard-link-nm: false
|
||||
nmHoistingLimits: workspaces
|
||||
env:
|
||||
npm_config_arch: ${{ matrix.spec.arch }}
|
||||
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
package: '@affine/native'
|
||||
|
||||
- name: Download web artifact
|
||||
- name: Download packaged artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn affine @affine/electron build
|
||||
|
||||
- name: Remove nbstore node_modules
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: Package windows app
|
||||
run: yarn affine @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
SKIP_WEB_BUILD: 1
|
||||
|
||||
- name: Connect Tailscale
|
||||
uses: tailscale/github-action@v4
|
||||
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: packaged-unsigned
|
||||
- name: unzip packaged artifacts
|
||||
run: Expand-Archive -Path packaged-unsigned/archive.zip -DestinationPath packages/frontend/apps/electron/out
|
||||
- name: Download signed packaged file diff
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success') }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
authkey: ${{ secrets.AFFINE_SIGNER_TS_AUTH_KEY }}
|
||||
hostname: affine-signer-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.spec.arch }}
|
||||
|
||||
- name: Check signer connectivity
|
||||
name: signed-packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: signed-packaged-diff
|
||||
- name: Apply signed packaged file diff
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success') }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Parts = "$env:AFFINE_SIGNER_ADDR".Split(':')
|
||||
if ($Parts.Count -ne 2) {
|
||||
throw "AFFINE_SIGNER_ADDR must be host:port, got $env:AFFINE_SIGNER_ADDR"
|
||||
$DiffRoot = 'signed-packaged-diff/files'
|
||||
$TargetRoot = 'packages/frontend/apps/electron/out'
|
||||
if (!(Test-Path -LiteralPath $DiffRoot)) {
|
||||
throw "Signed diff directory not found: $DiffRoot"
|
||||
}
|
||||
|
||||
$Result = Test-NetConnection -ComputerName $Parts[0] -Port ([int]$Parts[1])
|
||||
if (!$Result.TcpTestSucceeded) {
|
||||
throw "Unable to connect to signer at $env:AFFINE_SIGNER_ADDR"
|
||||
Copy-Item -Path (Join-Path $DiffRoot '*') -Destination $TargetRoot -Recurse -Force
|
||||
|
||||
$ManifestPath = 'signed-packaged-diff/manifest.json'
|
||||
if (Test-Path -LiteralPath $ManifestPath) {
|
||||
$ManifestEntries = @(Get-Content -LiteralPath $ManifestPath | ConvertFrom-Json)
|
||||
foreach ($Entry in $ManifestEntries) {
|
||||
$TargetPath = Join-Path $TargetRoot $Entry.path
|
||||
if (!(Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
|
||||
throw "Applied signed file not found: $($Entry.path)"
|
||||
}
|
||||
|
||||
$TargetHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
|
||||
if ($TargetHash -ne $Entry.sha256) {
|
||||
throw "Signed file hash mismatch: $($Entry.path)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- name: Download remote signer client
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (!$env:AFFINE_SIGN_CLIENT_HASH) {
|
||||
throw 'AFFINE_SIGN_CLIENT_HASH is required.'
|
||||
}
|
||||
Invoke-WebRequest -Uri "https://cdn.affine.pro/sign-client/$env:AFFINE_SIGN_CLIENT_HASH" -OutFile affine-sign-client.exe
|
||||
env:
|
||||
AFFINE_SIGN_CLIENT_HASH: ${{ secrets.AFFINE_SIGN_CLIENT_HASH }}
|
||||
|
||||
- name: Prepare public signing certificate
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (!$env:WINDOWS_SIGNER_PUBLIC_CERT_BASE64) {
|
||||
throw 'WINDOWS_SIGNER_PUBLIC_CERT_BASE64 is required.'
|
||||
}
|
||||
[IO.File]::WriteAllBytes('windows-signer-public.cer', [Convert]::FromBase64String($env:WINDOWS_SIGNER_PUBLIC_CERT_BASE64))
|
||||
env:
|
||||
WINDOWS_SIGNER_PUBLIC_CERT_BASE64: ${{ secrets.WINDOWS_SIGNER_PUBLIC_CERT_BASE64 }}
|
||||
|
||||
- name: Resolve signtool
|
||||
shell: pwsh
|
||||
run: |
|
||||
$Command = Get-Command signtool.exe -ErrorAction SilentlyContinue
|
||||
if ($Command) {
|
||||
"SIGNTOOL=$($Command.Source)" >> $env:GITHUB_ENV
|
||||
Write-Host "Using signtool: $($Command.Source)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$KitsRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\bin"
|
||||
$Candidates = @()
|
||||
if (Test-Path -LiteralPath $KitsRoot) {
|
||||
$Candidates = Get-ChildItem -Path $KitsRoot -Recurse -Filter signtool.exe |
|
||||
Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } |
|
||||
Sort-Object FullName -Descending
|
||||
}
|
||||
|
||||
if ($Candidates.Count -eq 0) {
|
||||
throw "Unable to find signtool.exe under PATH or $KitsRoot"
|
||||
}
|
||||
|
||||
"SIGNTOOL=$($Candidates[0].FullName)" >> $env:GITHUB_ENV
|
||||
Write-Host "Using signtool: $($Candidates[0].FullName)"
|
||||
|
||||
- name: Get packaged files to sign
|
||||
id: packaged_files_to_sign
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Sign packaged files
|
||||
shell: pwsh
|
||||
run: |
|
||||
./affine-sign-client.exe `
|
||||
--server "$env:AFFINE_SIGNER_ADDR" `
|
||||
--token "$env:AFFINE_SIGNER_TOKEN" `
|
||||
--workdir packages/frontend/apps/electron/out `
|
||||
--files '${{ steps.packaged_files_to_sign.outputs.FILES_TO_BE_SIGNED }}' `
|
||||
--cert windows-signer-public.cer `
|
||||
--plain-tcp
|
||||
|
||||
- name: Make squirrel.windows installer
|
||||
run: yarn affine @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
|
||||
- name: Make nsis.windows installer
|
||||
run: yarn affine @affine/electron make-nsis --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
|
||||
- name: Get installer files to sign
|
||||
id: installer_files_to_sign
|
||||
shell: pwsh
|
||||
- name: Zip artifacts for faster upload
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/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 packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\${{ env.BUILD_TYPE }}\make\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
"FILES_TO_BE_SIGNED_${{ matrix.spec.arch }}=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Sign installer files
|
||||
- name: Save installer for signing
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: archive.zip
|
||||
|
||||
sign-installer-artifacts-windows-x64:
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.make-windows-installer.outputs.FILES_TO_BE_SIGNED_x64 }}
|
||||
artifact-name: installer-win32-x64
|
||||
|
||||
sign-installer-artifacts-windows-arm64:
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.make-windows-installer.outputs.FILES_TO_BE_SIGNED_arm64 }}
|
||||
artifact-name: installer-win32-arm64
|
||||
|
||||
finalize-installer-windows:
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
inputs.desktop_windows &&
|
||||
needs.make-windows-installer.result == 'success'
|
||||
}}
|
||||
needs:
|
||||
[
|
||||
windows-signer-gate,
|
||||
make-windows-installer,
|
||||
sign-packaged-artifacts-windows_x64,
|
||||
sign-packaged-artifacts-windows_arm64,
|
||||
sign-installer-artifacts-windows-x64,
|
||||
sign-installer-artifacts-windows-arm64,
|
||||
before-make,
|
||||
]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
spec:
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: arm64
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
steps:
|
||||
- name: Download installer artifacts
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: installer-unsigned
|
||||
- name: unzip installer artifacts
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
run: Expand-Archive -Path installer-unsigned/archive.zip -DestinationPath packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make
|
||||
- name: Download signed installer file diff
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: signed-installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: signed-installer-diff
|
||||
- name: Apply signed installer file diff
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
./affine-sign-client.exe `
|
||||
--server "$env:AFFINE_SIGNER_ADDR" `
|
||||
--token "$env:AFFINE_SIGNER_TOKEN" `
|
||||
--workdir packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make `
|
||||
--files '${{ steps.installer_files_to_sign.outputs.FILES_TO_BE_SIGNED }}' `
|
||||
--cert windows-signer-public.cer `
|
||||
--plain-tcp
|
||||
$DiffRoot = 'signed-installer-diff/files'
|
||||
$TargetRoot = 'packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make'
|
||||
if (!(Test-Path -LiteralPath $DiffRoot)) {
|
||||
throw "Signed diff directory not found: $DiffRoot"
|
||||
}
|
||||
|
||||
Copy-Item -Path (Join-Path $DiffRoot '*') -Destination $TargetRoot -Recurse -Force
|
||||
|
||||
$ManifestPath = 'signed-installer-diff/manifest.json'
|
||||
if (Test-Path -LiteralPath $ManifestPath) {
|
||||
$ManifestEntries = @(Get-Content -LiteralPath $ManifestPath | ConvertFrom-Json)
|
||||
foreach ($Entry in $ManifestEntries) {
|
||||
$TargetPath = Join-Path $TargetRoot $Entry.path
|
||||
if (!(Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
|
||||
throw "Applied signed file not found: $($Entry.path)"
|
||||
}
|
||||
|
||||
$TargetHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
|
||||
if ($TargetHash -ne $Entry.sha256) {
|
||||
throw "Signed file hash mismatch: $($Entry.path)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- name: Save artifacts
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/win32/${{ matrix.spec.arch }}/AFFiNE*-win32-${{ matrix.spec.arch }}-*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
@@ -340,6 +406,7 @@ jobs:
|
||||
mv packages/frontend/apps/electron/out/*/make/nsis.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
|
||||
|
||||
- uses: actions/attest-build-provenance@v4
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
@@ -347,6 +414,7 @@ jobs:
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
|
||||
|
||||
- name: Upload Artifact
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
|
||||
@@ -356,14 +424,21 @@ jobs:
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
(inputs.desktop_macos || inputs.desktop_linux || inputs.desktop_windows) &&
|
||||
inputs.desktop_macos &&
|
||||
inputs.desktop_linux &&
|
||||
inputs.desktop_windows &&
|
||||
needs.before-make.result == 'success' &&
|
||||
(!inputs.desktop_macos || needs.make-distribution-macos.result == 'success') &&
|
||||
(!inputs.desktop_linux || needs.make-distribution-linux.result == 'success') &&
|
||||
needs.make-distribution-macos.result == 'success' &&
|
||||
needs.make-distribution-linux.result == 'success' &&
|
||||
(
|
||||
!inputs.desktop_windows ||
|
||||
!inputs.require-windows-signing ||
|
||||
needs.build-and-sign-windows.result == 'success'
|
||||
(
|
||||
needs.sign-packaged-artifacts-windows_x64.result == 'success' &&
|
||||
needs.sign-packaged-artifacts-windows_arm64.result == 'success' &&
|
||||
needs.sign-installer-artifacts-windows-x64.result == 'success' &&
|
||||
needs.sign-installer-artifacts-windows-arm64.result == 'success' &&
|
||||
needs.finalize-installer-windows.result == 'success'
|
||||
)
|
||||
)
|
||||
}}
|
||||
needs:
|
||||
@@ -371,38 +446,39 @@ jobs:
|
||||
before-make,
|
||||
make-distribution-macos,
|
||||
make-distribution-linux,
|
||||
build-and-sign-windows,
|
||||
sign-packaged-artifacts-windows_x64,
|
||||
sign-packaged-artifacts-windows_arm64,
|
||||
sign-installer-artifacts-windows-x64,
|
||||
sign-installer-artifacts-windows-arm64,
|
||||
finalize-installer-windows,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Download Artifacts (macos-x64)
|
||||
if: ${{ inputs.desktop_macos }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: affine-darwin-x64-builds
|
||||
path: ./release
|
||||
- name: Download Artifacts (macos-arm64)
|
||||
if: ${{ inputs.desktop_macos }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: affine-darwin-arm64-builds
|
||||
path: ./release
|
||||
- name: Download Artifacts (windows-x64)
|
||||
if: ${{ inputs.desktop_windows && needs.build-and-sign-windows.result == 'success' }}
|
||||
if: ${{ needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: affine-win32-x64-builds
|
||||
path: ./release
|
||||
- name: Download Artifacts (windows-arm64)
|
||||
if: ${{ inputs.desktop_windows && needs.build-and-sign-windows.result == 'success' }}
|
||||
if: ${{ needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: affine-win32-arm64-builds
|
||||
path: ./release
|
||||
- name: Download Artifacts (linux-x64)
|
||||
if: ${{ inputs.desktop_linux }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: affine-linux-x64-builds
|
||||
|
||||
@@ -109,9 +109,6 @@ jobs:
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 26.2
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.3'
|
||||
- name: Install Swiftformat
|
||||
run: brew install swiftformat
|
||||
- name: Cap sync
|
||||
@@ -134,10 +131,8 @@ jobs:
|
||||
printf '%s' "$BUILD_PROVISION_PROFILE" | base64 --decode -o "$PP_PATH"
|
||||
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
cp "$PP_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
bundle install
|
||||
bundle exec fastlane beta
|
||||
fastlane beta
|
||||
env:
|
||||
BUNDLE_PATH: vendor/bundle
|
||||
BUILD_TARGET: distribution
|
||||
BUILD_PROVISION_PROFILE: ${{ secrets.BUILD_PROVISION_PROFILE }}
|
||||
PP_PATH: ${{ runner.temp }}/build_pp.mobileprovision
|
||||
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
desktop_macos: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_macos }}
|
||||
desktop_windows: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_windows }}
|
||||
desktop_linux: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_linux }}
|
||||
require-windows-signing: ${{ needs.prepare.outputs.BUILD_TYPE == 'stable' || (github.event_name == 'workflow_dispatch' && inputs.desktop_windows) }}
|
||||
require-windows-signing: ${{ needs.prepare.outputs.BUILD_TYPE == 'beta' || needs.prepare.outputs.BUILD_TYPE == 'stable' || (github.event_name == 'workflow_dispatch' && inputs.desktop_windows) }}
|
||||
|
||||
mobile:
|
||||
name: Release Mobile
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
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@v4
|
||||
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.globalsign.com/tsa/r6advanced1 /td sha256 /fd sha256 /a ${{ inputs.files }}
|
||||
- name: collect signed file diff
|
||||
shell: powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File {0}
|
||||
run: |
|
||||
$OutDir = Join-Path '${{ env.ARCHIVE_DIR }}' 'out'
|
||||
$DiffDir = Join-Path '${{ env.ARCHIVE_DIR }}' 'signed-diff'
|
||||
$FilesDir = Join-Path $DiffDir 'files'
|
||||
New-Item -ItemType Directory -Path $FilesDir -Force | Out-Null
|
||||
|
||||
$SignedFiles = [regex]::Matches('${{ inputs.files }}', '"([^"]+)"') | ForEach-Object { $_.Groups[1].Value }
|
||||
if ($SignedFiles.Count -eq 0) {
|
||||
throw 'No files to sign were provided.'
|
||||
}
|
||||
|
||||
$Manifest = @()
|
||||
foreach ($RelativePath in $SignedFiles) {
|
||||
$SourcePath = Join-Path $OutDir $RelativePath
|
||||
if (!(Test-Path -LiteralPath $SourcePath -PathType Leaf)) {
|
||||
throw "Signed file not found: $RelativePath"
|
||||
}
|
||||
|
||||
$TargetPath = Join-Path $FilesDir $RelativePath
|
||||
$TargetDir = Split-Path -Parent $TargetPath
|
||||
if ($TargetDir) {
|
||||
New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force
|
||||
$Manifest += [PSCustomObject]@{
|
||||
path = $RelativePath
|
||||
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $TargetPath).Hash
|
||||
}
|
||||
}
|
||||
|
||||
$Manifest | ConvertTo-Json -Depth 4 | Out-File -FilePath (Join-Path $DiffDir 'manifest.json') -Encoding utf8
|
||||
Write-Host "Collected $($SignedFiles.Count) signed files."
|
||||
- name: upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed-${{ inputs.artifact-name }}
|
||||
path: ${{ env.ARCHIVE_DIR }}/signed-diff
|
||||
Generated
+589
-387
File diff suppressed because it is too large
Load Diff
+24
-2
@@ -17,12 +17,17 @@ resolver = "3"
|
||||
aes-gcm = "0.10"
|
||||
affine_common = { path = "./packages/common/native" }
|
||||
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
|
||||
ahash = "0.8"
|
||||
anyhow = "1"
|
||||
arbitrary = { version = "1.3", features = ["derive"] }
|
||||
assert-json-diff = "2.0"
|
||||
base64 = "0.22.1"
|
||||
base64-simd = "0.8"
|
||||
bitvec = "1.0"
|
||||
block2 = "0.6"
|
||||
byteorder = "1.5"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
core-foundation = "0.10"
|
||||
coreaudio-rs = "0.12"
|
||||
cpal = "0.15"
|
||||
@@ -43,10 +48,14 @@ resolver = "3"
|
||||
"webp",
|
||||
] }
|
||||
infer = { version = "0.19.0" }
|
||||
lasso = { version = "0.7", features = ["multi-threaded"] }
|
||||
lib0 = { version = "0.16", features = ["lib0-serde"] }
|
||||
libc = "0.2"
|
||||
libwebp-sys = "0.14.2"
|
||||
little_exif = "0.6.23"
|
||||
llm_adapter = { version = "0.2", default-features = false }
|
||||
llm_runtime = { version = "0.2", default-features = false }
|
||||
log = "0.4"
|
||||
lru = "0.16"
|
||||
matroska = "0.30"
|
||||
memory-indexer = "0.3.1"
|
||||
@@ -63,22 +72,33 @@ resolver = "3"
|
||||
] }
|
||||
napi-build = { version = "2" }
|
||||
napi-derive = { version = "3.4" }
|
||||
nom = "8"
|
||||
notify = { version = "8", features = ["serde"] }
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = "0.3"
|
||||
ogg = "0.9"
|
||||
once_cell = "1"
|
||||
ordered-float = "5"
|
||||
p256 = { version = "0.13", features = ["ecdsa", "pem"] }
|
||||
parking_lot = "0.12"
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
proptest = "1.3"
|
||||
proptest-derive = "0.5"
|
||||
pulldown-cmark = "0.13"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
rand_distr = "0.5"
|
||||
rayon = "1.10"
|
||||
regex = "1.10"
|
||||
rubato = "0.16"
|
||||
safefetch = "0.1.0"
|
||||
schemars = "0.8"
|
||||
screencapturekit = "0.3"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
sha2 = "0.11"
|
||||
sha3 = "0.11"
|
||||
sha2 = "0.10"
|
||||
sha3 = "0.10"
|
||||
smol_str = "0.3"
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
"chrono",
|
||||
"macros",
|
||||
@@ -116,6 +136,8 @@ resolver = "3"
|
||||
] }
|
||||
windows-core = { version = "0.61" }
|
||||
y-octo = "0.0.3"
|
||||
y-sync = { version = "0.4" }
|
||||
yrs = "0.23.0"
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
|
||||
@@ -295,7 +295,7 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0",
|
||||
"version": "0.26.3",
|
||||
"devDependencies": {
|
||||
"@vanilla-extract/vite-plugin": "^5.0.0",
|
||||
"msw": "^2.13.2",
|
||||
|
||||
@@ -30,7 +30,6 @@ import type {
|
||||
} from '@blocksuite/store';
|
||||
import { AssetsManager, MemoryBlobCRUD, Schema } from '@blocksuite/store';
|
||||
import { TestWorkspace } from '@blocksuite/store/test';
|
||||
import * as fflate from 'fflate';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { AffineSchemas } from '../../schemas.js';
|
||||
@@ -65,25 +64,6 @@ function markdownFixture(relativePath: string): File {
|
||||
);
|
||||
}
|
||||
|
||||
function zipBytes(entries: Record<string, string | Uint8Array>) {
|
||||
return fflate.zipSync(
|
||||
Object.fromEntries(
|
||||
Object.entries(entries).map(([path, content]) => [
|
||||
path,
|
||||
typeof content === 'string' ? fflate.strToU8(content) : content,
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function zipFixture(entries: Record<string, string | Uint8Array>) {
|
||||
const zipped = zipBytes(entries);
|
||||
const buffer = new ArrayBuffer(zipped.byteLength);
|
||||
new Uint8Array(buffer).set(zipped);
|
||||
|
||||
return new Blob([buffer], { type: 'application/zip' });
|
||||
}
|
||||
|
||||
function exportSnapshot(doc: Store): DocSnapshot {
|
||||
const job = doc.getTransformer([
|
||||
docLinkBaseURLMiddleware(doc.workspace.id),
|
||||
@@ -94,17 +74,6 @@ function exportSnapshot(doc: Store): DocSnapshot {
|
||||
return snapshot!;
|
||||
}
|
||||
|
||||
function noteSnapshotByTitle(collection: TestWorkspace, title: string) {
|
||||
const meta = collection.meta.docMetas.find(meta => meta.title === title);
|
||||
expect(meta).toBeTruthy();
|
||||
const doc = collection.getDoc(meta!.id)?.getStore({ id: meta!.id });
|
||||
expect(doc).toBeTruthy();
|
||||
const snapshot = exportSnapshot(doc!);
|
||||
return snapshot.blocks.children.find(
|
||||
block => block.flavour === 'affine:note'
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeDeltaForSnapshot(
|
||||
delta: DeltaInsert<AffineTextAttributes>[],
|
||||
titleById: ReadonlyMap<string, string>
|
||||
@@ -203,21 +172,6 @@ function snapshotDocByTitle(
|
||||
return simplifyBlockForSnapshot(exportSnapshot(doc!).blocks, titleById);
|
||||
}
|
||||
|
||||
function collectSimplifiedDeltas(
|
||||
block: Record<string, unknown>
|
||||
): Record<string, unknown>[] {
|
||||
const deltas = Array.isArray(block.delta)
|
||||
? (block.delta as Record<string, unknown>[])
|
||||
: [];
|
||||
const childDeltas = Array.isArray(block.children)
|
||||
? (block.children as Record<string, unknown>[]).flatMap(child =>
|
||||
collectSimplifiedDeltas(child)
|
||||
)
|
||||
: [];
|
||||
|
||||
return [...deltas, ...childDeltas];
|
||||
}
|
||||
|
||||
describe('snapshot to markdown', () => {
|
||||
test('code', async () => {
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
@@ -364,275 +318,6 @@ Hello world
|
||||
expect(exported.file).toContain('> \\- Oranges');
|
||||
});
|
||||
|
||||
test('imports notion markdown zip titles and folder names', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const imported = zipFixture({
|
||||
'Notion Export/Workspace 11111111111111111111111111111111.md':
|
||||
'# Workspace\nRoot body',
|
||||
'Notion Export/Workspace 11111111111111111111111111111111/Nested Page 22222222222222222222222222222222.md':
|
||||
'# Nested Page\nNested body',
|
||||
});
|
||||
|
||||
const { docIds, folderHierarchy } =
|
||||
await MarkdownTransformer.importNotionMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
|
||||
expect(docIds).toHaveLength(2);
|
||||
expect(
|
||||
collection.meta.docMetas
|
||||
.map(meta => meta.title)
|
||||
.sort((a, b) => (a ?? '').localeCompare(b ?? ''))
|
||||
).toEqual(['Nested Page', 'Workspace']);
|
||||
|
||||
const nestedNote = noteSnapshotByTitle(collection, 'Nested Page');
|
||||
expect(JSON.stringify(nestedNote)).toContain('Nested body');
|
||||
expect(JSON.stringify(nestedNote)).not.toContain('Nested Page');
|
||||
|
||||
const [folder] = [...(folderHierarchy?.children.values() ?? [])];
|
||||
expect(folder?.name).toBe('Notion Export');
|
||||
const workspaceMeta = collection.meta.docMetas.find(
|
||||
meta => meta.title === 'Workspace'
|
||||
);
|
||||
expect([...folder!.children.values()]).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ pageId: workspaceMeta?.id }),
|
||||
])
|
||||
);
|
||||
const workspaceFolder = [...folder!.children.values()].find(
|
||||
child => child.name === 'Workspace'
|
||||
);
|
||||
const nestedMeta = collection.meta.docMetas.find(
|
||||
meta => meta.title === 'Nested Page'
|
||||
);
|
||||
expect([...workspaceFolder!.children.values()]).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ pageId: nestedMeta?.id }),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
test('imports notion markdown zip folders with CJK names', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const imported = zipFixture({
|
||||
'Export/工作 11111111111111111111111111111111.md': '# 工作\nRoot body',
|
||||
'Export/工作 11111111111111111111111111111111/SDK架构 22222222222222222222222222222222.md':
|
||||
'# SDK架构\nNested body',
|
||||
});
|
||||
|
||||
const { folderHierarchy } =
|
||||
await MarkdownTransformer.importNotionMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
|
||||
const [rootFolder] = [...(folderHierarchy?.children.values() ?? [])];
|
||||
expect(rootFolder?.name).toBe('Export');
|
||||
const workFolder = [...(rootFolder?.children.values() ?? [])].find(
|
||||
child => child.name === '工作'
|
||||
);
|
||||
expect(workFolder?.name).toBe('工作');
|
||||
expect([...workFolder!.children.values()]).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ pageId: expect.any(String) }),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
test('imports notion markdown zip title from frontmatter when heading is absent', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const imported = zipFixture({
|
||||
'Export/Fallback 11111111111111111111111111111111.md':
|
||||
'---\ntitle: Frontmatter Title\n---\nBody',
|
||||
});
|
||||
|
||||
const { docIds } = await MarkdownTransformer.importNotionMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
|
||||
expect(docIds).toHaveLength(1);
|
||||
expect(collection.meta.getDocMeta(docIds[0])?.title).toBe(
|
||||
'Frontmatter Title'
|
||||
);
|
||||
});
|
||||
|
||||
test('imports markdown zip relative doc links as linked pages', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const imported = zipFixture({
|
||||
'entry.md': [
|
||||
'[引用](./test/2.md)',
|
||||
'[missing](./missing.md)',
|
||||
'[external](https://example.com/test.md)',
|
||||
].join('\n\n'),
|
||||
'test/2.md': 'target page',
|
||||
});
|
||||
|
||||
const { docIds } = await MarkdownTransformer.importMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
expect(docIds).toHaveLength(2);
|
||||
|
||||
const titleById = new Map(
|
||||
collection.meta.docMetas.map(meta => [
|
||||
meta.id,
|
||||
meta.title ?? '<untitled>',
|
||||
])
|
||||
);
|
||||
const entryDeltas = collectSimplifiedDeltas(
|
||||
snapshotDocByTitle(collection, 'entry', titleById)
|
||||
);
|
||||
|
||||
expect(entryDeltas).toContainEqual({
|
||||
insert: ' ',
|
||||
reference: {
|
||||
type: 'LinkedPage',
|
||||
page: '2',
|
||||
title: '引用',
|
||||
},
|
||||
});
|
||||
expect(entryDeltas).toContainEqual({
|
||||
insert: 'missing',
|
||||
link: './missing.md',
|
||||
});
|
||||
expect(entryDeltas).toContainEqual({
|
||||
insert: 'external',
|
||||
link: 'https://example.com/test.md',
|
||||
});
|
||||
});
|
||||
|
||||
test('imports notion markdown zip relative doc links as linked pages', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const imported = zipFixture({
|
||||
'Workspace 11111111111111111111111111111111/Entry 22222222222222222222222222222222.md':
|
||||
'# Entry\n[引用](./test/Target%2033333333333333333333333333333333.md)',
|
||||
'Workspace 11111111111111111111111111111111/test/Target 33333333333333333333333333333333.md':
|
||||
'# Target\ntarget page',
|
||||
});
|
||||
|
||||
const { docIds } = await MarkdownTransformer.importNotionMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
expect(docIds).toHaveLength(2);
|
||||
|
||||
const titleById = new Map(
|
||||
collection.meta.docMetas.map(meta => [
|
||||
meta.id,
|
||||
meta.title ?? '<untitled>',
|
||||
])
|
||||
);
|
||||
const entryDeltas = collectSimplifiedDeltas(
|
||||
snapshotDocByTitle(collection, 'Entry', titleById)
|
||||
);
|
||||
|
||||
expect(entryDeltas).toContainEqual({
|
||||
insert: ' ',
|
||||
reference: {
|
||||
type: 'LinkedPage',
|
||||
page: 'Target',
|
||||
title: '引用',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('imports nested notion markdown zips with isolated relative links', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const imported = zipFixture({
|
||||
'Export/Part A.zip': zipBytes({
|
||||
'Entry 11111111111111111111111111111111.md':
|
||||
'# Entry A\n[go](./Target%2022222222222222222222222222222222.md)',
|
||||
'Target 22222222222222222222222222222222.md': '# Target A\nA body',
|
||||
}),
|
||||
'Export/Part B.zip': zipBytes({
|
||||
'Entry 11111111111111111111111111111111.md':
|
||||
'# Entry B\n[go](./Target%2022222222222222222222222222222222.md)',
|
||||
'Target 22222222222222222222222222222222.md': '# Target B\nB body',
|
||||
}),
|
||||
});
|
||||
|
||||
const { docIds, folderHierarchy } =
|
||||
await MarkdownTransformer.importNotionMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
expect(docIds).toHaveLength(4);
|
||||
|
||||
const titleById = new Map(
|
||||
collection.meta.docMetas.map(meta => [
|
||||
meta.id,
|
||||
meta.title ?? '<untitled>',
|
||||
])
|
||||
);
|
||||
const entryADeltas = collectSimplifiedDeltas(
|
||||
snapshotDocByTitle(collection, 'Entry A', titleById)
|
||||
);
|
||||
const entryBDeltas = collectSimplifiedDeltas(
|
||||
snapshotDocByTitle(collection, 'Entry B', titleById)
|
||||
);
|
||||
|
||||
expect(entryADeltas).toContainEqual({
|
||||
insert: ' ',
|
||||
reference: {
|
||||
type: 'LinkedPage',
|
||||
page: 'Target A',
|
||||
title: 'go',
|
||||
},
|
||||
});
|
||||
expect(entryBDeltas).toContainEqual({
|
||||
insert: ' ',
|
||||
reference: {
|
||||
type: 'LinkedPage',
|
||||
page: 'Target B',
|
||||
title: 'go',
|
||||
},
|
||||
});
|
||||
|
||||
const [rootFolder] = [...(folderHierarchy?.children.values() ?? [])];
|
||||
expect(rootFolder?.name).toBe('Export');
|
||||
expect(
|
||||
[...(rootFolder?.children.values() ?? [])].map(node => node.name)
|
||||
).toEqual(expect.arrayContaining(['Part A', 'Part B']));
|
||||
});
|
||||
|
||||
test('imports obsidian vault fixtures', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
@@ -45,5 +45,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -47,5 +47,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -61,5 +61,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"lit-html": "^3.2.1",
|
||||
"lodash-es": "^4.17.23",
|
||||
@@ -74,5 +74,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.23",
|
||||
"yjs": "^13.6.27",
|
||||
@@ -46,5 +46,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -40,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -45,5 +45,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -40,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -40,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -76,5 +76,5 @@
|
||||
"@types/pdfmake": "^0.2.12",
|
||||
"vitest": "^4.1.8"
|
||||
},
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -40,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -27,5 +27,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -48,5 +48,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -3,12 +3,7 @@ import {
|
||||
docLinkBaseURLMiddleware,
|
||||
fileNameMiddleware,
|
||||
filePathMiddleware,
|
||||
FULL_FILE_PATH_KEY,
|
||||
getImageFullPath,
|
||||
MarkdownAdapter,
|
||||
type MarkdownAST,
|
||||
MarkdownASTToDeltaExtension,
|
||||
normalizeFilePathReference,
|
||||
titleMiddleware,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { Container } from '@blocksuite/global/di';
|
||||
@@ -66,117 +61,6 @@ const FRONTMATTER_KEYS = {
|
||||
trash: ['trash', 'trashed', 'deleted', 'archived'],
|
||||
};
|
||||
|
||||
const MARKDOWN_ZIP_PAGE_ID_CONFIG_PREFIX = 'markdown-zip:page-id:';
|
||||
|
||||
function normalizeMarkdownZipLookupPath(path: string) {
|
||||
return normalizeFilePathReference(path).toLowerCase();
|
||||
}
|
||||
|
||||
function stripMarkdownExtension(path: string) {
|
||||
return path.replace(/\.md$/i, '');
|
||||
}
|
||||
|
||||
function splitMarkdownLinkTarget(url: string) {
|
||||
const queryIndex = url.indexOf('?');
|
||||
const hashIndex = url.indexOf('#');
|
||||
const splitIndex = [queryIndex, hashIndex]
|
||||
.filter(index => index >= 0)
|
||||
.sort((a, b) => a - b)[0];
|
||||
|
||||
return splitIndex === undefined ? url : url.slice(0, splitIndex);
|
||||
}
|
||||
|
||||
function isLocalMarkdownDocLink(url: string) {
|
||||
const path = splitMarkdownLinkTarget(url).trim();
|
||||
if (!path || path.startsWith('//') || path.startsWith('#')) {
|
||||
return false;
|
||||
}
|
||||
if (/^[a-z][a-z0-9+.-]*:/i.test(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fileName = path.split('/').at(-1) ?? '';
|
||||
return path.toLowerCase().endsWith('.md') || !fileName.includes('.');
|
||||
}
|
||||
|
||||
function markdownAstText(ast: MarkdownAST): string {
|
||||
if ('value' in ast && typeof ast.value === 'string') {
|
||||
return ast.value;
|
||||
}
|
||||
if ('children' in ast && Array.isArray(ast.children)) {
|
||||
return ast.children.map(child => markdownAstText(child)).join('');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function getMarkdownZipPageIdConfigKey(path: string) {
|
||||
return `${MARKDOWN_ZIP_PAGE_ID_CONFIG_PREFIX}${normalizeMarkdownZipLookupPath(
|
||||
path
|
||||
)}`;
|
||||
}
|
||||
|
||||
function getMarkdownZipTargetPageId(
|
||||
configs: Map<string, string>,
|
||||
currentFilePath: string,
|
||||
url: string
|
||||
) {
|
||||
const targetPath = splitMarkdownLinkTarget(url);
|
||||
const fullPath = getImageFullPath(currentFilePath, targetPath);
|
||||
const candidates = [fullPath, stripMarkdownExtension(fullPath)];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const pageId = configs.get(getMarkdownZipPageIdConfigKey(candidate));
|
||||
if (pageId) {
|
||||
return pageId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const markdownZipDocLinkToDeltaMatcher = MarkdownASTToDeltaExtension({
|
||||
name: 'markdown-zip-doc-link',
|
||||
match: ast =>
|
||||
ast.type === 'link' &&
|
||||
'url' in ast &&
|
||||
typeof ast.url === 'string' &&
|
||||
isLocalMarkdownDocLink(ast.url),
|
||||
toDelta: (ast, context) => {
|
||||
if (!('children' in ast) || !('url' in ast)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentFilePath = context.configs.get(FULL_FILE_PATH_KEY);
|
||||
const targetPageId =
|
||||
typeof currentFilePath === 'string'
|
||||
? getMarkdownZipTargetPageId(context.configs, currentFilePath, ast.url)
|
||||
: null;
|
||||
|
||||
if (targetPageId) {
|
||||
const title = markdownAstText(ast).trim();
|
||||
return [
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
reference: {
|
||||
type: 'LinkedPage',
|
||||
pageId: targetPageId,
|
||||
...(title ? { title } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return ast.children.flatMap(child =>
|
||||
context.toDelta(child).map(delta => {
|
||||
delta.attributes = { ...delta.attributes, link: ast.url };
|
||||
return delta;
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const truthyStrings = new Set(['true', 'yes', 'y', '1', 'on']);
|
||||
const falsyStrings = new Set(['false', 'no', 'n', '0', 'off']);
|
||||
|
||||
@@ -350,94 +234,6 @@ type ImportMarkdownZipOptions = {
|
||||
extensions: ExtensionType[];
|
||||
};
|
||||
|
||||
type PrepareMarkdownFileOptions = {
|
||||
filename: string;
|
||||
markdown: string;
|
||||
};
|
||||
|
||||
type PreparedMarkdownFile = {
|
||||
content: string;
|
||||
meta: ParsedFrontmatterMeta;
|
||||
preferredTitle: string;
|
||||
};
|
||||
|
||||
type ImportMarkdownZipInternalOptions = ImportMarkdownZipOptions & {
|
||||
createRootFolderForTopLevelDocs?: boolean;
|
||||
normalizeFolderName?: (folderName: string) => string;
|
||||
prepareMarkdownFile?: (
|
||||
options: PrepareMarkdownFileOptions
|
||||
) => PreparedMarkdownFile;
|
||||
preserveCommonRoot?: boolean;
|
||||
recursiveZip?: boolean;
|
||||
};
|
||||
|
||||
function getFileNameWithoutExtension(filename: string) {
|
||||
return filename.replace(/\.[^/.]+$/, '');
|
||||
}
|
||||
|
||||
function stripNotionHash(name: string) {
|
||||
return name
|
||||
.replace(
|
||||
/\s+[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
||||
''
|
||||
)
|
||||
.replace(/\s+[0-9a-f]{32}$/i, '');
|
||||
}
|
||||
|
||||
function parseNotionMarkdownTitle(markdown: string):
|
||||
| {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
| undefined {
|
||||
const match = markdown.match(/^\uFEFF?#(?!#)\s+(.+?)\s*(?:\r?\n|$)/);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = match?.[1]?.trim();
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
content: markdown.slice(match[0].length),
|
||||
};
|
||||
}
|
||||
|
||||
function prepareDefaultMarkdownFile({
|
||||
filename,
|
||||
markdown,
|
||||
}: PrepareMarkdownFileOptions): PreparedMarkdownFile {
|
||||
const fileNameWithoutExt = getFileNameWithoutExtension(filename);
|
||||
const { content, meta } = parseFrontmatter(markdown);
|
||||
return {
|
||||
content,
|
||||
meta,
|
||||
preferredTitle: meta.title ?? fileNameWithoutExt,
|
||||
};
|
||||
}
|
||||
|
||||
function prepareNotionMarkdownFile({
|
||||
filename,
|
||||
markdown,
|
||||
}: PrepareMarkdownFileOptions): PreparedMarkdownFile {
|
||||
const notionTitle = parseNotionMarkdownTitle(markdown);
|
||||
const { content, meta } = parseFrontmatter(notionTitle?.content ?? markdown);
|
||||
const fallbackTitle = stripNotionHash(getFileNameWithoutExtension(filename));
|
||||
const preferredTitle = notionTitle?.title ?? meta.title ?? fallbackTitle;
|
||||
|
||||
return {
|
||||
content,
|
||||
meta: {
|
||||
...meta,
|
||||
title: preferredTitle,
|
||||
},
|
||||
preferredTitle,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters hidden/system entries that should never participate in imports.
|
||||
*/
|
||||
@@ -535,25 +331,6 @@ export function bindImportedAssetsToJob(
|
||||
return pathBlobIdMap;
|
||||
}
|
||||
|
||||
function bindImportedMarkdownPagesToJob(
|
||||
job: Transformer,
|
||||
pagePathIdMap: ReadonlyMap<string, string>
|
||||
) {
|
||||
for (const [path, pageId] of pagePathIdMap.entries()) {
|
||||
job.adapterConfigs.set(getMarkdownZipPageIdConfigKey(path), pageId);
|
||||
}
|
||||
}
|
||||
|
||||
function registerMarkdownZipPagePath(
|
||||
pagePathIdMap: Map<string, string>,
|
||||
path: string,
|
||||
pageId: string
|
||||
) {
|
||||
const normalizedPath = normalizeFilePathReference(path);
|
||||
pagePathIdMap.set(normalizedPath, pageId);
|
||||
pagePathIdMap.set(stripMarkdownExtension(normalizedPath), pageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a doc to a Markdown file or a zip archive containing Markdown and assets.
|
||||
* @param doc The doc to export
|
||||
@@ -693,110 +470,59 @@ type FolderHierarchy = {
|
||||
parentPath?: string;
|
||||
};
|
||||
|
||||
export type ImportMarkdownZipResult = {
|
||||
docIds: string[];
|
||||
folderHierarchy?: FolderHierarchy;
|
||||
};
|
||||
|
||||
async function importMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions,
|
||||
}: ImportMarkdownZipOptions): Promise<ImportMarkdownZipResult> {
|
||||
return importMarkdownZipInternal({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions,
|
||||
});
|
||||
}
|
||||
}: ImportMarkdownZipOptions): Promise<{
|
||||
docIds: string[];
|
||||
folderHierarchy?: FolderHierarchy;
|
||||
}> {
|
||||
const provider = getProvider(extensions);
|
||||
const unzip = new Unzip();
|
||||
await unzip.load(imported);
|
||||
|
||||
async function importNotionMarkdownZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions,
|
||||
}: ImportMarkdownZipOptions): Promise<ImportMarkdownZipResult> {
|
||||
return importMarkdownZipInternal({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions,
|
||||
normalizeFolderName: stripNotionHash,
|
||||
prepareMarkdownFile: prepareNotionMarkdownFile,
|
||||
preserveCommonRoot: true,
|
||||
createRootFolderForTopLevelDocs: true,
|
||||
recursiveZip: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function importMarkdownZipInternal({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
extensions,
|
||||
createRootFolderForTopLevelDocs = false,
|
||||
normalizeFolderName,
|
||||
prepareMarkdownFile = prepareDefaultMarkdownFile,
|
||||
preserveCommonRoot = false,
|
||||
recursiveZip = false,
|
||||
}: ImportMarkdownZipInternalOptions): Promise<ImportMarkdownZipResult> {
|
||||
const provider = getProvider([
|
||||
markdownZipDocLinkToDeltaMatcher,
|
||||
...extensions,
|
||||
]);
|
||||
const docIds: string[] = [];
|
||||
const pendingAssets: AssetMap = new Map();
|
||||
const pendingPathBlobIdMap: PathBlobIdMap = new Map();
|
||||
const markdownBlobs: ImportedFileEntry[] = [];
|
||||
const docPathMap: Array<{ fullPath: string; docId: string }> = [];
|
||||
const pendingPagePathIdMap = new Map<string, string>();
|
||||
const markdownBlobs: Array<ImportedFileEntry & { pageId: string }> = [];
|
||||
|
||||
async function collectZipEntries(zipBlob: Blob, basePath = '') {
|
||||
const unzip = new Unzip();
|
||||
await unzip.load(zipBlob);
|
||||
// Iterate over all files in the zip
|
||||
for (const { path, content: blob } of unzip) {
|
||||
// Skip the files that are not markdown files
|
||||
if (isSystemImportPath(path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const { path, content: blob } of unzip) {
|
||||
if (isSystemImportPath(path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fileName = path.split('/').pop() ?? '';
|
||||
const fullPath = basePath ? `${basePath}/${path}` : path;
|
||||
if (fileName.endsWith('.md')) {
|
||||
const pageId = collection.idGenerator();
|
||||
registerMarkdownZipPagePath(pendingPagePathIdMap, fullPath, pageId);
|
||||
markdownBlobs.push({
|
||||
filename: fileName,
|
||||
contentBlob: blob,
|
||||
fullPath,
|
||||
pageId,
|
||||
});
|
||||
} else if (recursiveZip && fileName.endsWith('.zip')) {
|
||||
await collectZipEntries(blob, getFileNameWithoutExtension(fullPath));
|
||||
} else {
|
||||
await stageImportedAsset({
|
||||
pendingAssets,
|
||||
pendingPathBlobIdMap,
|
||||
path: fullPath,
|
||||
content: blob,
|
||||
fileName,
|
||||
});
|
||||
}
|
||||
// Get the file name
|
||||
const fileName = path.split('/').pop() ?? '';
|
||||
// If the file is a markdown file, store it to markdownBlobs
|
||||
if (fileName.endsWith('.md')) {
|
||||
markdownBlobs.push({
|
||||
filename: fileName,
|
||||
contentBlob: blob,
|
||||
fullPath: path,
|
||||
});
|
||||
} else {
|
||||
await stageImportedAsset({
|
||||
pendingAssets,
|
||||
pendingPathBlobIdMap,
|
||||
path,
|
||||
content: blob,
|
||||
fileName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await collectZipEntries(imported);
|
||||
|
||||
await Promise.all(
|
||||
markdownBlobs.map(async markdownFile => {
|
||||
const { filename, contentBlob, fullPath, pageId } = markdownFile;
|
||||
const { filename, contentBlob, fullPath } = markdownFile;
|
||||
const fileNameWithoutExt = filename.replace(/\.[^/.]+$/, '');
|
||||
const markdown = await contentBlob.text();
|
||||
const { content, meta, preferredTitle } = prepareMarkdownFile({
|
||||
filename,
|
||||
markdown,
|
||||
});
|
||||
const { content, meta } = parseFrontmatter(markdown);
|
||||
const preferredTitle = meta.title ?? fileNameWithoutExt;
|
||||
const job = createMarkdownImportJob({
|
||||
collection,
|
||||
schema,
|
||||
@@ -804,15 +530,12 @@ async function importMarkdownZipInternal({
|
||||
fullPath,
|
||||
});
|
||||
bindImportedAssetsToJob(job, pendingAssets, pendingPathBlobIdMap);
|
||||
bindImportedMarkdownPagesToJob(job, pendingPagePathIdMap);
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(job, provider);
|
||||
const snapshot = await mdAdapter.toDocSnapshot({
|
||||
const doc = await mdAdapter.toDoc({
|
||||
file: content,
|
||||
assets: job.assetsManager,
|
||||
});
|
||||
snapshot.meta.id = pageId;
|
||||
const doc = await job.snapshotToDoc(snapshot);
|
||||
if (doc) {
|
||||
applyMetaPatch(collection, doc.id, meta);
|
||||
docIds.push(doc.id);
|
||||
@@ -822,12 +545,7 @@ async function importMarkdownZipInternal({
|
||||
);
|
||||
|
||||
// Build folder hierarchy from zip paths
|
||||
const folderHierarchy = buildMarkdownZipFolderHierarchy(
|
||||
docPathMap,
|
||||
normalizeFolderName,
|
||||
preserveCommonRoot,
|
||||
createRootFolderForTopLevelDocs
|
||||
);
|
||||
const folderHierarchy = buildMarkdownZipFolderHierarchy(docPathMap);
|
||||
|
||||
return { docIds, folderHierarchy };
|
||||
}
|
||||
@@ -840,28 +558,15 @@ async function importMarkdownZipInternal({
|
||||
* hierarchy starts one level deeper.
|
||||
*/
|
||||
function buildMarkdownZipFolderHierarchy(
|
||||
entries: Array<{ fullPath: string; docId: string }>,
|
||||
normalizeFolderName?: (folderName: string) => string,
|
||||
preserveCommonRoot = false,
|
||||
createRootFolderForTopLevelDocs = false
|
||||
entries: Array<{ fullPath: string; docId: string }>
|
||||
): FolderHierarchy | undefined {
|
||||
if (entries.length === 0) return undefined;
|
||||
|
||||
// Check once whether all entries share a common root directory
|
||||
const candidateRoot = entries[0]?.fullPath.split('/').find(Boolean);
|
||||
const skipRoot =
|
||||
!preserveCommonRoot &&
|
||||
!!candidateRoot &&
|
||||
entries.every(e => e.fullPath.startsWith(candidateRoot + '/'));
|
||||
|
||||
// Check if any entries have folder structure after the common root is stripped.
|
||||
// Check if any entries have folder structure
|
||||
const hasSubfolders = entries.some(e => {
|
||||
const parts = e.fullPath.split('/').filter(Boolean);
|
||||
const fileName = parts.pop();
|
||||
const folderParts = skipRoot ? parts.slice(1) : parts;
|
||||
return (
|
||||
folderParts.length > 0 || (createRootFolderForTopLevelDocs && !!fileName)
|
||||
);
|
||||
// More than just "root/file.md" -- need at least one real subfolder
|
||||
return parts.length > 2;
|
||||
});
|
||||
if (!hasSubfolders) {
|
||||
// All files are at the same level, no folder hierarchy needed
|
||||
@@ -874,15 +579,18 @@ function buildMarkdownZipFolderHierarchy(
|
||||
children: new Map(),
|
||||
};
|
||||
|
||||
// Check once whether all entries share a common root directory
|
||||
const candidateRoot = entries[0]?.fullPath.split('/').find(Boolean);
|
||||
const skipRoot =
|
||||
!!candidateRoot &&
|
||||
entries.every(e => e.fullPath.startsWith(candidateRoot + '/'));
|
||||
|
||||
for (const { fullPath, docId } of entries) {
|
||||
const parts = fullPath.split('/').filter(Boolean);
|
||||
const fileName = parts.pop(); // Remove filename
|
||||
if (!fileName) continue;
|
||||
|
||||
const folderParts = skipRoot ? parts.slice(1) : parts;
|
||||
if (folderParts.length === 0 && createRootFolderForTopLevelDocs) {
|
||||
folderParts.push(getFileNameWithoutExtension(fileName));
|
||||
}
|
||||
let folderParts = skipRoot ? parts.slice(1) : parts;
|
||||
|
||||
if (folderParts.length === 0) {
|
||||
// Root-level file, no folder needed
|
||||
@@ -898,7 +606,7 @@ function buildMarkdownZipFolderHierarchy(
|
||||
|
||||
if (!current.children.has(folderName)) {
|
||||
current.children.set(folderName, {
|
||||
name: normalizeFolderName?.(folderName) ?? folderName,
|
||||
name: folderName,
|
||||
path: currentPath,
|
||||
parentPath: parentPath || undefined,
|
||||
children: new Map(),
|
||||
@@ -926,5 +634,4 @@ export const MarkdownTransformer = {
|
||||
importMarkdownToBlock,
|
||||
importMarkdownToDoc,
|
||||
importMarkdownZip,
|
||||
importNotionMarkdownZip,
|
||||
};
|
||||
|
||||
@@ -68,15 +68,8 @@ export class Unzip {
|
||||
|
||||
private fixFileNameEncoding(fileName: string): string {
|
||||
try {
|
||||
// `fflate` already returns valid Unicode filenames for UTF-8 zip entries.
|
||||
// Only retry decoding for legacy byte-like mojibake strings, otherwise
|
||||
// normal CJK characters can be corrupted by truncating their code points.
|
||||
if (
|
||||
fileName.split('').some(char => {
|
||||
const code = char.charCodeAt(0);
|
||||
return code >= 0x80 && code <= 0xff;
|
||||
})
|
||||
) {
|
||||
// check if contains non-ASCII characters
|
||||
if (fileName.split('').some(char => char.charCodeAt(0) > 127)) {
|
||||
// try different encodings
|
||||
const fixedName = this.tryDifferentEncodings(fileName);
|
||||
if (fixedName && fixedName !== fileName) {
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -17,17 +17,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@blocksuite/affine": "workspace:*",
|
||||
"date-fns": "^4.4.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"vitepress-plugin-sandpack": "^1.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it-container": "^4.0.0",
|
||||
"typedoc": "^0.28.19",
|
||||
"typedoc-plugin-markdown": "^4.12.0",
|
||||
"vite-plugin-wasm": "^3.6.0",
|
||||
"vitepress": "^1.6.4",
|
||||
"typedoc": "^0.28.0",
|
||||
"typedoc-plugin-markdown": "^4.5.0",
|
||||
"vite-plugin-wasm": "^3.3.0",
|
||||
"vitepress": "^1.6.3",
|
||||
"vue": "^3.4.38"
|
||||
},
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ For (un)locking the element, use `(un)lock` instead.
|
||||
|
||||
###### lockedBySelf
|
||||
|
||||
`boolean` \| `undefined`
|
||||
`boolean` | `undefined`
|
||||
|
||||
##### Returns
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ Toggle the selection state of single element
|
||||
|
||||
##### element
|
||||
|
||||
`string` \| `GfxModel`
|
||||
`string` | `GfxModel`
|
||||
|
||||
#### Returns
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ SortOrder.AFTER means a should be rendered after b and so on.
|
||||
|
||||
### a
|
||||
|
||||
`GfxModel` \| `GfxLocalElementModel`
|
||||
`GfxModel` | `GfxLocalElementModel`
|
||||
|
||||
### b
|
||||
|
||||
`GfxModel` \| `GfxLocalElementModel`
|
||||
`GfxModel` | `GfxLocalElementModel`
|
||||
|
||||
## Returns
|
||||
|
||||
|
||||
@@ -32,4 +32,18 @@ Note:
|
||||
|
||||
## Returns
|
||||
|
||||
(`_`, `context`) => `ClassAccessorDecoratorResult`\<`T`, `V`\>
|
||||
> (`_`, `context`): `ClassAccessorDecoratorResult`\<`T`, `V`\>
|
||||
|
||||
### Parameters
|
||||
|
||||
#### \_
|
||||
|
||||
`unknown`
|
||||
|
||||
#### context
|
||||
|
||||
`ClassAccessorDecoratorContext`
|
||||
|
||||
### Returns
|
||||
|
||||
`ClassAccessorDecoratorResult`\<`T`, `V`\>
|
||||
|
||||
@@ -37,4 +37,18 @@ Note:
|
||||
|
||||
## Returns
|
||||
|
||||
(`_`, `context`) => `ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
> (`_`, `context`): `ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
|
||||
### Parameters
|
||||
|
||||
#### \_
|
||||
|
||||
`unknown`
|
||||
|
||||
#### context
|
||||
|
||||
`ClassAccessorDecoratorContext`
|
||||
|
||||
### Returns
|
||||
|
||||
`ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
### a
|
||||
|
||||
`string` \| `null` \| `undefined`
|
||||
`string` | `null` | `undefined`
|
||||
|
||||
### b
|
||||
|
||||
`string` \| `null` \| `undefined`
|
||||
`string` | `null` | `undefined`
|
||||
|
||||
### digits?
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ make sure a and b are generated by this function.
|
||||
|
||||
### a
|
||||
|
||||
`string` \| `null`
|
||||
`string` | `null`
|
||||
|
||||
### b
|
||||
|
||||
`string` \| `null`
|
||||
`string` | `null`
|
||||
|
||||
## Returns
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ a and b.
|
||||
|
||||
### a
|
||||
|
||||
`string` \| `null` \| `undefined`
|
||||
`string` | `null` | `undefined`
|
||||
|
||||
### b
|
||||
|
||||
`string` \| `null` \| `undefined`
|
||||
`string` | `null` | `undefined`
|
||||
|
||||
### n
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Function: getEffectiveDpr()
|
||||
|
||||
> **getEffectiveDpr**(`zoom`, `rawDpr?`): `number`
|
||||
> **getEffectiveDpr**(`zoom`, `rawDpr`): `number`
|
||||
|
||||
Resolves the effective device-pixel-ratio for canvas backing stores at the
|
||||
given zoom, honoring [viewportRuntimeConfig.CANVAS\_DPR\_CAP\_BY\_ZOOM](../variables/viewportRuntimeConfig.md#canvas_dpr_cap_by_zoom).
|
||||
@@ -19,7 +19,7 @@ Returns the raw `window.devicePixelRatio` when no cap applies.
|
||||
|
||||
`number`
|
||||
|
||||
### rawDpr?
|
||||
### rawDpr
|
||||
|
||||
`number` = `window.devicePixelRatio`
|
||||
|
||||
|
||||
@@ -25,4 +25,18 @@ Updating local property will also trigger the `elementUpdated` slot of the surfa
|
||||
|
||||
## Returns
|
||||
|
||||
(`_target`, `context`) => `ClassAccessorDecoratorResult`\<`T`, `V`\>
|
||||
> (`_target`, `context`): `ClassAccessorDecoratorResult`\<`T`, `V`\>
|
||||
|
||||
### Parameters
|
||||
|
||||
#### \_target
|
||||
|
||||
`ClassAccessorDecoratorTarget`\<`T`, `V`\>
|
||||
|
||||
#### context
|
||||
|
||||
`ClassAccessorDecoratorContext`
|
||||
|
||||
### Returns
|
||||
|
||||
`ClassAccessorDecoratorResult`\<`T`, `V`\>
|
||||
|
||||
@@ -36,4 +36,18 @@ re-observe the property automatically when the value is altered.
|
||||
|
||||
## Returns
|
||||
|
||||
(`_`, `context`) => `ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
> (`_`, `context`): `ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
|
||||
### Parameters
|
||||
|
||||
#### \_
|
||||
|
||||
`unknown`
|
||||
|
||||
#### context
|
||||
|
||||
`ClassAccessorDecoratorContext`
|
||||
|
||||
### Returns
|
||||
|
||||
`ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
|
||||
@@ -29,4 +29,18 @@ You can thinks of it as a decorator version of `elementUpdated` slot of the surf
|
||||
|
||||
## Returns
|
||||
|
||||
(`_`, `context`) => `ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
> (`_`, `context`): `ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
|
||||
### Parameters
|
||||
|
||||
#### \_
|
||||
|
||||
`unknown`
|
||||
|
||||
#### context
|
||||
|
||||
`ClassAccessorDecoratorContext`
|
||||
|
||||
### Returns
|
||||
|
||||
`ClassAccessorDecoratorResult`\<`GfxPrimitiveElementModel`\<`BaseElementProps`\>, `V`\>
|
||||
|
||||
@@ -28,7 +28,7 @@ The bound of the element without considering the response extension.
|
||||
|
||||
### forceFullRender?
|
||||
|
||||
> `optional` **forceFullRender?**: `boolean`
|
||||
> `optional` **forceFullRender**: `boolean`
|
||||
|
||||
Whether to disable fallback rendering for this element, e.g., during zooming.
|
||||
Defaults to false (fallback to placeholder rendering is enabled).
|
||||
@@ -37,7 +37,7 @@ Defaults to false (fallback to placeholder rendering is enabled).
|
||||
|
||||
### lockedBySelf?
|
||||
|
||||
> `optional` **lockedBySelf?**: `boolean`
|
||||
> `optional` **lockedBySelf**: `boolean`
|
||||
|
||||
Indicates whether the current block is explicitly locked by self.
|
||||
For checking the lock status of the element, use `isLocked` instead.
|
||||
|
||||
@@ -47,7 +47,7 @@ The bound of the element without considering the response extension.
|
||||
|
||||
### forceFullRender?
|
||||
|
||||
> `optional` **forceFullRender?**: `boolean`
|
||||
> `optional` **forceFullRender**: `boolean`
|
||||
|
||||
Whether to disable fallback rendering for this element, e.g., during zooming.
|
||||
Defaults to false (fallback to placeholder rendering is enabled).
|
||||
@@ -60,7 +60,7 @@ Defaults to false (fallback to placeholder rendering is enabled).
|
||||
|
||||
### lockedBySelf?
|
||||
|
||||
> `optional` **lockedBySelf?**: `boolean`
|
||||
> `optional` **lockedBySelf**: `boolean`
|
||||
|
||||
Indicates whether the current block is explicitly locked by self.
|
||||
For checking the lock status of the element, use `isLocked` instead.
|
||||
|
||||
@@ -12,7 +12,7 @@ The options for the hit testing of a point.
|
||||
|
||||
### hitThreshold?
|
||||
|
||||
> `optional` **hitThreshold?**: `number`
|
||||
> `optional` **hitThreshold**: `number`
|
||||
|
||||
The threshold of the hit test. The unit is pixel.
|
||||
|
||||
@@ -20,7 +20,7 @@ The threshold of the hit test. The unit is pixel.
|
||||
|
||||
### ignoreTransparent?
|
||||
|
||||
> `optional` **ignoreTransparent?**: `boolean`
|
||||
> `optional` **ignoreTransparent**: `boolean`
|
||||
|
||||
If true, the transparent area of the element will be ignored during the point inclusion test.
|
||||
Otherwise, the transparent area will be considered as filled area.
|
||||
@@ -31,7 +31,7 @@ Default is true.
|
||||
|
||||
### responsePadding?
|
||||
|
||||
> `optional` **responsePadding?**: \[`number`, `number`\]
|
||||
> `optional` **responsePadding**: \[`number`, `number`\]
|
||||
|
||||
The padding of the response area for each element when do the hit testing. The unit is pixel.
|
||||
The first value is the padding for the x-axis, and the second value is the padding for the y-axis.
|
||||
@@ -40,7 +40,7 @@ The first value is the padding for the x-axis, and the second value is the paddi
|
||||
|
||||
### useElementBound?
|
||||
|
||||
> `optional` **useElementBound?**: `boolean`
|
||||
> `optional` **useElementBound**: `boolean`
|
||||
|
||||
If true, the element bound will be used for the hit testing.
|
||||
By default, the response bound will be used.
|
||||
@@ -49,6 +49,6 @@ By default, the response bound will be used.
|
||||
|
||||
### zoom?
|
||||
|
||||
> `optional` **zoom?**: `number`
|
||||
> `optional` **zoom**: `number`
|
||||
|
||||
The zoom level of current view when do the hit testing.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
[BlockSuite API Documentation](../../../../README.md) / [@blocksuite/std](../../README.md) / [index](../README.md) / BlockService
|
||||
|
||||
# ~~Abstract Class: BlockService~~
|
||||
# Class: ~~`abstract` BlockService~~
|
||||
|
||||
## Deprecated
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
[BlockSuite API Documentation](../../../../README.md) / [@blocksuite/std](../../README.md) / [index](../README.md) / LifeCycleWatcher
|
||||
|
||||
# Abstract Class: LifeCycleWatcher
|
||||
# Class: `abstract` LifeCycleWatcher
|
||||
|
||||
A life cycle watcher is an extension that watches the life cycle of the editor.
|
||||
It is used to perform actions when the editor is created, mounted, rendered, or unmounted.
|
||||
|
||||
@@ -25,8 +25,6 @@ boxedObject.setValue({ foo: 'bar' });
|
||||
|
||||
## Type Param
|
||||
|
||||
**T**
|
||||
|
||||
The type of the value stored in the Boxed.
|
||||
|
||||
## Type Parameters
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user