mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-02 02:00:49 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6557e5d01d |
@@ -19,8 +19,3 @@ rustflags = [
|
||||
# pthread_key_create() destructors and segfault after a DSO unloading
|
||||
[target.'cfg(all(target_env = "gnu", not(target_os = "windows")))']
|
||||
rustflags = ["-C", "link-args=-Wl,-z,nodelete"]
|
||||
|
||||
# Temporary local llm_adapter override.
|
||||
# Uncomment when verifying AFFiNE against the sibling llm_adapter workspace.
|
||||
# [patch.crates-io]
|
||||
# llm_adapter = { path = "../llm_adapter" }
|
||||
|
||||
+80
-111
@@ -62,18 +62,6 @@
|
||||
"concurrency": 10
|
||||
}
|
||||
},
|
||||
"queues.calendar": {
|
||||
"type": "object",
|
||||
"description": "The config for calendar job queue\n@default {\"concurrency\":4}",
|
||||
"properties": {
|
||||
"concurrency": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"concurrency": 4
|
||||
}
|
||||
},
|
||||
"queues.doc": {
|
||||
"type": "object",
|
||||
"description": "The config for doc job queue\n@default {\"concurrency\":1}",
|
||||
@@ -135,17 +123,17 @@
|
||||
},
|
||||
"throttlers.default": {
|
||||
"type": "object",
|
||||
"description": "The config for the default throttler.\n@default {\"ttl\":60000,\"limit\":120}",
|
||||
"description": "The config for the default throttler.\n@default {\"ttl\":60,\"limit\":120}",
|
||||
"default": {
|
||||
"ttl": 60000,
|
||||
"ttl": 60,
|
||||
"limit": 120
|
||||
}
|
||||
},
|
||||
"throttlers.strict": {
|
||||
"type": "object",
|
||||
"description": "The config for the strict throttler.\n@default {\"ttl\":60000,\"limit\":20}",
|
||||
"description": "The config for the strict throttler.\n@default {\"ttl\":60,\"limit\":20}",
|
||||
"default": {
|
||||
"ttl": 60000,
|
||||
"ttl": 60,
|
||||
"limit": 20
|
||||
}
|
||||
}
|
||||
@@ -175,11 +163,6 @@
|
||||
"description": "Whether require email verification before accessing restricted resources(not implemented).\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"newAccountShareActionDelay": {
|
||||
"type": "number",
|
||||
"description": "Minimum account age in seconds before new accounts can invite members or create share links.\n@default 86400",
|
||||
"default": 86400
|
||||
},
|
||||
"passwordRequirements": {
|
||||
"type": "object",
|
||||
"description": "The password strength requirements when set new password.\n@default {\"min\":8,\"max\":32}",
|
||||
@@ -214,8 +197,8 @@
|
||||
"properties": {
|
||||
"SMTP.name": {
|
||||
"type": "string",
|
||||
"description": "Hostname used for SMTP HELO/EHLO (e.g. mail.example.com). Leave empty to use the system hostname.\n@default \"\"\n@environment `MAILER_SERVERNAME`",
|
||||
"default": ""
|
||||
"description": "Name of the email server (e.g. your domain name)\n@default \"AFFiNE Server\"\n@environment `MAILER_SERVERNAME`",
|
||||
"default": "AFFiNE Server"
|
||||
},
|
||||
"SMTP.host": {
|
||||
"type": "string",
|
||||
@@ -254,8 +237,8 @@
|
||||
},
|
||||
"fallbackSMTP.name": {
|
||||
"type": "string",
|
||||
"description": "Hostname used for fallback SMTP HELO/EHLO (e.g. mail.example.com). Leave empty to use the system hostname.\n@default \"\"",
|
||||
"default": ""
|
||||
"description": "Name of the fallback email server (e.g. your domain name)\n@default \"AFFiNE Server\"",
|
||||
"default": "AFFiNE Server"
|
||||
},
|
||||
"fallbackSMTP.host": {
|
||||
"type": "string",
|
||||
@@ -305,22 +288,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"type": "object",
|
||||
"description": "Configuration for permission module",
|
||||
"properties": {
|
||||
"readModel": {
|
||||
"type": "string",
|
||||
"description": "Permission data source for Rust evaluation\n@default \"projection\"\n@environment `AFFINE_PERMISSION_READ_MODEL`",
|
||||
"default": "projection"
|
||||
},
|
||||
"fallbackLegacyLoader": {
|
||||
"type": "boolean",
|
||||
"description": "Fallback from projection loader to legacy loader when projection input loading fails\n@default false\n@environment `AFFINE_PERMISSION_FALLBACK_LEGACY_LOADER`",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"storages": {
|
||||
"type": "object",
|
||||
"description": "Configuration for storages module",
|
||||
@@ -374,7 +341,7 @@
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint (used by aws-s3 provider). Optional; if omitted, endpoint is derived from region."
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
@@ -441,6 +408,10 @@
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
@@ -490,13 +461,6 @@
|
||||
"type": "string",
|
||||
"description": "The account id for the cloudflare r2 storage provider."
|
||||
},
|
||||
"jurisdiction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"eu"
|
||||
],
|
||||
"description": "Optional jurisdiction for the cloudflare r2 endpoint. Set to \"eu\" for EU buckets."
|
||||
},
|
||||
"usePresignedURL": {
|
||||
"type": "object",
|
||||
"description": "The presigned url config for the cloudflare r2 storage provider.",
|
||||
@@ -572,7 +536,7 @@
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint (used by aws-s3 provider). Optional; if omitted, endpoint is derived from region."
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
@@ -639,6 +603,10 @@
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
@@ -688,13 +656,6 @@
|
||||
"type": "string",
|
||||
"description": "The account id for the cloudflare r2 storage provider."
|
||||
},
|
||||
"jurisdiction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"eu"
|
||||
],
|
||||
"description": "Optional jurisdiction for the cloudflare r2 endpoint. Set to \"eu\" for EU buckets."
|
||||
},
|
||||
"usePresignedURL": {
|
||||
"type": "object",
|
||||
"description": "The presigned url config for the cloudflare r2 storage provider.",
|
||||
@@ -882,14 +843,11 @@
|
||||
"properties": {
|
||||
"google": {
|
||||
"type": "object",
|
||||
"description": "Google Calendar integration config\n@default {\"enabled\":false,\"allowNewAccounts\":true,\"clientId\":\"\",\"clientSecret\":\"\",\"externalWebhookUrl\":\"\",\"webhookVerificationToken\":\"\",\"requestTimeoutMs\":10000}\n@link https://developers.google.com/calendar/api/guides/push",
|
||||
"description": "Google Calendar integration config\n@default {\"enabled\":false,\"clientId\":\"\",\"clientSecret\":\"\",\"externalWebhookUrl\":\"\",\"webhookVerificationToken\":\"\"}\n@link https://developers.google.com/calendar/api/guides/push",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowNewAccounts": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -901,19 +859,14 @@
|
||||
},
|
||||
"webhookVerificationToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"enabled": false,
|
||||
"allowNewAccounts": true,
|
||||
"clientId": "",
|
||||
"clientSecret": "",
|
||||
"externalWebhookUrl": "",
|
||||
"webhookVerificationToken": "",
|
||||
"requestTimeoutMs": 10000
|
||||
"webhookVerificationToken": ""
|
||||
}
|
||||
},
|
||||
"caldav": {
|
||||
@@ -1016,35 +969,24 @@
|
||||
"description": "Whether to enable the copilot plugin. <br> Document: <a href=\"https://docs.affine.pro/self-host-affine/administer/ai\" target=\"_blank\">https://docs.affine.pro/self-host-affine/administer/ai</a>\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"byok.enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to enable workspace BYOK.\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"byok.allowedProviders": {
|
||||
"type": "array",
|
||||
"description": "The allowlist for workspace BYOK providers.\n@default [\"openai\",\"anthropic\",\"gemini\",\"fal\"]",
|
||||
"default": [
|
||||
"openai",
|
||||
"anthropic",
|
||||
"gemini",
|
||||
"fal"
|
||||
]
|
||||
},
|
||||
"byok.allowCustomEndpoint": {
|
||||
"type": "boolean",
|
||||
"description": "Whether workspace BYOK custom endpoints are accepted.\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"providers.profiles": {
|
||||
"type": "array",
|
||||
"description": "The profile list for copilot providers.\n@default []",
|
||||
"default": []
|
||||
},
|
||||
"providers.defaults": {
|
||||
"scenarios": {
|
||||
"type": "object",
|
||||
"description": "The default provider ids for model output types and global fallback.\n@default {}",
|
||||
"default": {}
|
||||
"description": "Use custom models in scenarios and override default settings.\n@default {\"override_enabled\":false,\"scenarios\":{\"audio_transcribing\":\"gemini-2.5-flash\",\"chat\":\"gemini-2.5-flash\",\"embedding\":\"gemini-embedding-001\",\"image\":\"gpt-image-1\",\"rerank\":\"gpt-4.1\",\"coding\":\"claude-sonnet-4-5@20250929\",\"complex_text_generation\":\"gpt-4o-2024-08-06\",\"quick_decision_making\":\"gpt-5-mini\",\"quick_text_generation\":\"gemini-2.5-flash\",\"polish_and_summarize\":\"gemini-2.5-flash\"}}",
|
||||
"default": {
|
||||
"override_enabled": false,
|
||||
"scenarios": {
|
||||
"audio_transcribing": "gemini-2.5-flash",
|
||||
"chat": "gemini-2.5-flash",
|
||||
"embedding": "gemini-embedding-001",
|
||||
"image": "gpt-image-1",
|
||||
"rerank": "gpt-4.1",
|
||||
"coding": "claude-sonnet-4-5@20250929",
|
||||
"complex_text_generation": "gpt-4o-2024-08-06",
|
||||
"quick_decision_making": "gpt-5-mini",
|
||||
"quick_text_generation": "gemini-2.5-flash",
|
||||
"polish_and_summarize": "gemini-2.5-flash"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.openai": {
|
||||
"type": "object",
|
||||
@@ -1054,14 +996,6 @@
|
||||
"baseURL": "https://api.openai.com/v1"
|
||||
}
|
||||
},
|
||||
"providers.cloudflareWorkersAi": {
|
||||
"type": "object",
|
||||
"description": "The config for the Cloudflare Workers AI provider.\n@default {\"apiToken\":\"\",\"accountId\":\"\"}",
|
||||
"default": {
|
||||
"apiToken": "",
|
||||
"accountId": ""
|
||||
}
|
||||
},
|
||||
"providers.fal": {
|
||||
"type": "object",
|
||||
"description": "The config for the fal provider.\n@default {\"apiKey\":\"\"}",
|
||||
@@ -1112,6 +1046,13 @@
|
||||
},
|
||||
"default": {}
|
||||
},
|
||||
"providers.perplexity": {
|
||||
"type": "object",
|
||||
"description": "The config for the perplexity provider.\n@default {\"apiKey\":\"\"}",
|
||||
"default": {
|
||||
"apiKey": ""
|
||||
}
|
||||
},
|
||||
"providers.anthropic": {
|
||||
"type": "object",
|
||||
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.anthropic.com/v1\"}",
|
||||
@@ -1155,6 +1096,11 @@
|
||||
},
|
||||
"default": {}
|
||||
},
|
||||
"providers.morph": {
|
||||
"type": "object",
|
||||
"description": "The config for the morph provider.\n@default {}",
|
||||
"default": {}
|
||||
},
|
||||
"unsplash": {
|
||||
"type": "object",
|
||||
"description": "The config for the unsplash key.\n@default {\"key\":\"\"}",
|
||||
@@ -1213,7 +1159,7 @@
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint (used by aws-s3 provider). Optional; if omitted, endpoint is derived from region."
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
@@ -1280,6 +1226,10 @@
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
@@ -1329,13 +1279,6 @@
|
||||
"type": "string",
|
||||
"description": "The account id for the cloudflare r2 storage provider."
|
||||
},
|
||||
"jurisdiction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"eu"
|
||||
],
|
||||
"description": "Optional jurisdiction for the cloudflare r2 endpoint. Set to \"eu\" for EU buckets."
|
||||
},
|
||||
"usePresignedURL": {
|
||||
"type": "object",
|
||||
"description": "The presigned url config for the cloudflare r2 storage provider.",
|
||||
@@ -1410,6 +1353,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"customerIo": {
|
||||
"type": "object",
|
||||
"description": "Configuration for customerIo module",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable customer.io integration\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "Customer.io token\n@default \"\"",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth": {
|
||||
"type": "object",
|
||||
"description": "Configuration for oauth module",
|
||||
@@ -1508,6 +1467,16 @@
|
||||
"description": "Whether enable lifetime price and allow user to pay for it.\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"apiKey": {
|
||||
"type": "string",
|
||||
"description": "[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.\n@default \"\"\n@environment `STRIPE_API_KEY`",
|
||||
"default": ""
|
||||
},
|
||||
"webhookKey": {
|
||||
"type": "string",
|
||||
"description": "[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`",
|
||||
"default": ""
|
||||
},
|
||||
"stripe": {
|
||||
"type": "object",
|
||||
"description": "Stripe sdk options and credentials\n@default {\"apiKey\":\"\",\"webhookKey\":\"\"}\n@link https://docs.stripe.com/api",
|
||||
|
||||
@@ -50,21 +50,8 @@ runs:
|
||||
# https://github.com/tree-sitter/tree-sitter/issues/4186
|
||||
# pass -D_BSD_SOURCE to clang to fix the tree-sitter build issue
|
||||
run: |
|
||||
if [[ "${{ inputs.target }}" == "aarch64-unknown-linux-gnu" ]]; then
|
||||
# napi cross-toolchain 1.0.3 headers miss AT_HWCAP2 in elf.h
|
||||
echo "CC=clang -D_BSD_SOURCE -DAT_HWCAP2=26" >> "$GITHUB_ENV"
|
||||
echo "TARGET_CC=clang -D_BSD_SOURCE -DAT_HWCAP2=26" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
echo "TARGET_CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Prepare cache key
|
||||
id: cache-key
|
||||
shell: bash
|
||||
run: |
|
||||
shared_key="$(printf '%s' "${{ inputs.target }}-${{ inputs.package }}" | tr -c 'A-Za-z0-9_.-' '-')"
|
||||
echo "shared-key=$shared_key" >> "$GITHUB_OUTPUT"
|
||||
echo "CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
echo "TARGET_CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -72,7 +59,7 @@ runs:
|
||||
with:
|
||||
workspaces: ${{ env.DEV_DRIVE_WORKSPACE }}
|
||||
save-if: ${{ github.ref_name == 'canary' }}
|
||||
shared-key: ${{ steps.cache-key.outputs.shared-key }}
|
||||
shared-key: ${{ inputs.target }}-${{ inputs.package }}
|
||||
env:
|
||||
CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo
|
||||
RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup
|
||||
@@ -82,7 +69,7 @@ runs:
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
with:
|
||||
save-if: ${{ github.ref_name == 'canary' }}
|
||||
shared-key: ${{ steps.cache-key.outputs.shared-key }}
|
||||
shared-key: ${{ inputs.target }}-${{ inputs.package }}
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
|
||||
@@ -53,7 +53,7 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
@@ -93,7 +93,7 @@ runs:
|
||||
run: node -e "const p = $(yarn config cacheFolder --json).effective; console.log('yarn_global_cache=' + p)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache non-full yarn cache on Linux
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
if: ${{ inputs.full-cache != 'true' && runner.os == 'Linux' }}
|
||||
with:
|
||||
path: |
|
||||
@@ -105,7 +105,7 @@ runs:
|
||||
# and the decompression performance on Windows is very terrible
|
||||
# so we reduce the number of cached files on non-Linux systems by remove node_modules from cache path.
|
||||
- name: Cache non-full yarn cache on non-Linux
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
if: ${{ inputs.full-cache != 'true' && runner.os != 'Linux' }}
|
||||
with:
|
||||
path: |
|
||||
@@ -113,7 +113,7 @@ runs:
|
||||
key: node_modules-cache-${{ github.job }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.system-info.outputs.name }}-${{ steps.system-info.outputs.release }}-${{ steps.system-info.outputs.version }}
|
||||
|
||||
- name: Cache full yarn cache on Linux
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
if: ${{ inputs.full-cache == 'true' && runner.os == 'Linux' }}
|
||||
with:
|
||||
path: |
|
||||
@@ -122,7 +122,7 @@ runs:
|
||||
key: node_modules-cache-full-${{ runner.os }}-${{ runner.arch }}-${{ steps.system-info.outputs.name }}-${{ steps.system-info.outputs.release }}-${{ steps.system-info.outputs.version }}
|
||||
|
||||
- name: Cache full yarn cache on non-Linux
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
if: ${{ inputs.full-cache == 'true' && runner.os != 'Linux' }}
|
||||
with:
|
||||
path: |
|
||||
@@ -154,7 +154,7 @@ runs:
|
||||
# Note: Playwright's cache directory is hard coded because that's what it
|
||||
# says to do in the docs. There doesn't appear to be a command that prints
|
||||
# it out for us.
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
if: ${{ inputs.playwright-install == 'true' }}
|
||||
with:
|
||||
@@ -189,7 +189,7 @@ runs:
|
||||
run: |
|
||||
echo "version=$(yarn why --json electron | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://' | head -n 1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v4
|
||||
id: electron-cache
|
||||
if: ${{ inputs.electron-install == 'true' }}
|
||||
with:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,22 +25,6 @@ spec:
|
||||
serviceAccountName: {{ include "front.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
{{- with .Values.global.database.gcloud }}
|
||||
{{- if .enabled }}
|
||||
initContainers:
|
||||
- name: wait-for-cloud-sql-proxy
|
||||
image: busybox:1.36.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
until wget -q -T 2 -O /dev/null "http://{{ $.Values.global.database.host }}:9801/startup"; do
|
||||
echo "waiting for cloud sql proxy to become ready"
|
||||
sleep 2
|
||||
done
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
|
||||
@@ -31,10 +31,10 @@ podSecurityContext:
|
||||
resources:
|
||||
limits:
|
||||
cpu: '1'
|
||||
memory: 6Gi
|
||||
memory: 4Gi
|
||||
requests:
|
||||
cpu: '1'
|
||||
memory: 4Gi
|
||||
memory: 2Gi
|
||||
|
||||
probe:
|
||||
initialDelaySeconds: 20
|
||||
|
||||
@@ -12,10 +12,6 @@ spec:
|
||||
targetPort: cloud-sql-proxy
|
||||
protocol: TCP
|
||||
name: cloud-sql-proxy
|
||||
- port: 9801
|
||||
targetPort: 9801
|
||||
protocol: TCP
|
||||
name: health
|
||||
selector:
|
||||
{{- include "gcloud-sql-proxy.selectorLabels" . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,22 +23,6 @@ spec:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "graphql.serviceAccountName" . }}
|
||||
{{- with .Values.global.database.gcloud }}
|
||||
{{- if .enabled }}
|
||||
initContainers:
|
||||
- name: wait-for-cloud-sql-proxy
|
||||
image: busybox:1.36.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
until wget -q -T 2 -O /dev/null "http://{{ $.Values.global.database.host }}:9801/startup"; do
|
||||
echo "waiting for cloud sql proxy to become ready"
|
||||
sleep 2
|
||||
done
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
@@ -111,19 +95,11 @@ spec:
|
||||
path: /info
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
||||
timeoutSeconds: {{ default .Values.probe.timeoutSeconds .Values.probe.liveness.timeoutSeconds }}
|
||||
periodSeconds: {{ default .Values.probe.periodSeconds .Values.probe.liveness.periodSeconds }}
|
||||
failureThreshold: {{ default .Values.probe.failureThreshold .Values.probe.liveness.failureThreshold }}
|
||||
successThreshold: {{ default .Values.probe.successThreshold .Values.probe.liveness.successThreshold }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /info
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
||||
timeoutSeconds: {{ default .Values.probe.timeoutSeconds .Values.probe.readiness.timeoutSeconds }}
|
||||
periodSeconds: {{ default .Values.probe.periodSeconds .Values.probe.readiness.periodSeconds }}
|
||||
failureThreshold: {{ default .Values.probe.failureThreshold .Values.probe.readiness.failureThreshold }}
|
||||
successThreshold: {{ default .Values.probe.successThreshold .Values.probe.readiness.successThreshold }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
|
||||
@@ -13,22 +13,6 @@ spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: {{ include "graphql.serviceAccountName" . }}
|
||||
{{- with .Values.global.database.gcloud }}
|
||||
{{- if .enabled }}
|
||||
initContainers:
|
||||
- name: wait-for-cloud-sql-proxy
|
||||
image: busybox:1.36.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
until wget -q -T 2 -O /dev/null "http://{{ $.Values.global.database.host }}:9801/startup"; do
|
||||
echo "waiting for cloud sql proxy to become ready"
|
||||
sleep 2
|
||||
done
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
|
||||
@@ -36,13 +36,6 @@ resources:
|
||||
|
||||
probe:
|
||||
initialDelaySeconds: 20
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 10
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
liveness:
|
||||
failureThreshold: 12
|
||||
readiness: {}
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"groupSlug": "all-minor-patch",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchManagers": ["npm"],
|
||||
"excludePackagePatterns": ["^@blocksuite/", "^oxlint$"]
|
||||
"matchPackageNames": ["*", "!/^@blocksuite//", "!/oxlint/"]
|
||||
},
|
||||
{
|
||||
"groupName": "all non-major dependencies",
|
||||
@@ -63,7 +63,7 @@
|
||||
"groupName": "opentelemetry",
|
||||
"matchPackageNames": [
|
||||
"/^@opentelemetry/",
|
||||
"/^@google-cloud/opentelemetry-/"
|
||||
"/^@google-cloud\/opentelemetry-/"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -79,7 +79,7 @@
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^rust-toolchain\\.toml?$/"],
|
||||
"fileMatch": ["^rust-toolchain\\.toml?$"],
|
||||
"matchStrings": [
|
||||
"channel\\s*=\\s*\"(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)\""
|
||||
],
|
||||
|
||||
@@ -13,5 +13,5 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/labeler@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/labeler@v5
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
|
||||
build-server-native:
|
||||
name: Build Server native - ${{ matrix.targets.name }}
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
file: server-native.armv7.node
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
needs:
|
||||
- build-server-native
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -202,7 +202,7 @@ jobs:
|
||||
- build-mobile
|
||||
- build-admin
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download server dist
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
# setup node without cache configuration
|
||||
# Prisma cache is not compatible with docker build cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
|
||||
+184
-204
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
@@ -67,9 +67,9 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Go (for actionlint)
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Install actionlint
|
||||
@@ -82,13 +82,17 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
"$(go env GOPATH)/bin/actionlint"
|
||||
- name: Run oxlint
|
||||
# oxlint is fast, so wrong code will fail quickly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
oxlint_version="$(node -e "console.log(require('./package.json').devDependencies.oxlint)")"
|
||||
yarn dlx "oxlint@${oxlint_version}" --deny-warnings
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
- name: Run oxlint
|
||||
run: yarn lint:ox
|
||||
- name: Run i18n codegen
|
||||
run: yarn affine @affine/i18n build
|
||||
- name: Run ESLint
|
||||
@@ -107,27 +111,20 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
- name: Run i18n codegen
|
||||
run: |
|
||||
yarn affine @affine/i18n build
|
||||
git checkout packages/frontend/i18n/src/i18n-completenesses.json
|
||||
if git status --porcelain | grep -q .; then
|
||||
echo "Run 'yarn affine @affine/i18n build' and make sure all generated i18n changes are submitted"
|
||||
exit 1
|
||||
else
|
||||
echo "All generated i18n changes are submitted"
|
||||
fi
|
||||
run: yarn affine @affine/i18n build
|
||||
- name: Run Type Check
|
||||
run: yarn typecheck
|
||||
- name: Run BS Docs Build
|
||||
run: |
|
||||
yarn affine bs-docs build
|
||||
git checkout packages/frontend/i18n/src/i18n-completenesses.json
|
||||
if git status --porcelain | grep -q .; then
|
||||
echo "Run 'yarn typecheck && yarn affine bs-docs build' and make sure all changes are submitted"
|
||||
exit 1
|
||||
@@ -135,166 +132,13 @@ jobs:
|
||||
echo "All changes are submitted"
|
||||
fi
|
||||
|
||||
mobile-native-build-filter:
|
||||
name: Mobile native build filter
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
run-android: ${{ steps.mobile-native-filter.outputs.android }}
|
||||
run-ios: ${{ steps.mobile-native-filter.outputs.ios }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: mobile-native-filter
|
||||
with:
|
||||
filters: |
|
||||
android:
|
||||
- '.github/workflows/build-test.yml'
|
||||
- 'packages/frontend/apps/android/**'
|
||||
- 'packages/frontend/mobile-native/**'
|
||||
- '.cargo/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- 'rust-toolchain*'
|
||||
ios:
|
||||
- '.github/workflows/build-test.yml'
|
||||
- 'packages/frontend/apps/ios/**'
|
||||
- 'packages/frontend/mobile-native/**'
|
||||
- '.cargo/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- 'rust-toolchain*'
|
||||
|
||||
build-android-app:
|
||||
name: Build Android app
|
||||
if: ${{ needs.mobile-native-build-filter.outputs.run-android == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- mobile-native-build-filter
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/monorepo @affine-tools/cli @affine/android
|
||||
electron-install: false
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: 'aarch64-linux-android'
|
||||
package: 'affine_mobile_native'
|
||||
no-build: 'true'
|
||||
|
||||
- name: Build Android web assets
|
||||
run: yarn affine @affine/android build
|
||||
env:
|
||||
PUBLIC_PATH: '/'
|
||||
|
||||
- name: Write CI Firebase config
|
||||
run: |
|
||||
cat > packages/frontend/apps/android/App/app/google-services.json <<'JSON'
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "1",
|
||||
"project_id": "affine-ci",
|
||||
"storage_bucket": "affine-ci.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1:android:0000000000000000",
|
||||
"android_client_info": {
|
||||
"package_name": "app.affine.pro"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "ci-placeholder"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
JSON
|
||||
|
||||
- name: Cap sync
|
||||
run: yarn workspace @affine/android cap sync
|
||||
|
||||
- name: Build Android debug app
|
||||
working-directory: packages/frontend/apps/android/App
|
||||
run: ./gradlew :app:assembleCanaryDebug --no-daemon --stacktrace
|
||||
|
||||
build-ios-app:
|
||||
name: Build iOS app
|
||||
if: ${{ needs.mobile-native-build-filter.outputs.run-ios == 'true' }}
|
||||
runs-on: macos-15
|
||||
needs:
|
||||
- mobile-native-build-filter
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/monorepo @affine-tools/cli @affine/ios
|
||||
electron-install: false
|
||||
hard-link-nm: false
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 26.2
|
||||
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: 'aarch64-apple-ios-sim'
|
||||
package: 'affine_mobile_native'
|
||||
no-build: 'true'
|
||||
|
||||
- name: Build iOS web assets
|
||||
run: yarn affine @affine/ios build
|
||||
env:
|
||||
PUBLIC_PATH: '/'
|
||||
|
||||
- name: Cap sync
|
||||
run: yarn workspace @affine/ios sync
|
||||
|
||||
- name: Build iOS simulator app
|
||||
run: |
|
||||
xcodebuild \
|
||||
-workspace packages/frontend/apps/ios/App/App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Debug \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'generic/platform=iOS Simulator' \
|
||||
ARCHS=arm64 \
|
||||
ONLY_ACTIVE_ARCH=YES \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
build
|
||||
|
||||
rust-test-filter:
|
||||
name: Rust test filter
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
run-rust: ${{ steps.rust-filter.outputs.rust }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: rust-filter
|
||||
@@ -315,7 +159,7 @@ jobs:
|
||||
needs:
|
||||
- rust-test-filter
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
@@ -338,7 +182,7 @@ jobs:
|
||||
needs:
|
||||
- build-server-native
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -368,7 +212,7 @@ jobs:
|
||||
name: Check yarn binary
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run check
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -382,9 +226,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
shard: [1, 2]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -412,7 +256,7 @@ jobs:
|
||||
name: E2E BlockSuite Cross Browser Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -425,13 +269,10 @@ jobs:
|
||||
- name: Run playground build
|
||||
run: yarn workspace @blocksuite/playground build
|
||||
|
||||
- name: Run integration browser tests
|
||||
timeout-minutes: 10
|
||||
run: yarn workspace @blocksuite/integration-test test:unit
|
||||
|
||||
- name: Run cross-platform playwright tests
|
||||
timeout-minutes: 10
|
||||
run: yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only
|
||||
- name: Run playwright tests
|
||||
run: |
|
||||
yarn workspace @blocksuite/integration-test test:unit
|
||||
yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
@@ -441,6 +282,52 @@ jobs:
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
bundler-matrix:
|
||||
name: Bundler Matrix (${{ matrix.bundler }})
|
||||
runs-on: ubuntu-24.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
bundler: [webpack, rspack]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: false
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
|
||||
- name: Run frontend build matrix
|
||||
env:
|
||||
AFFINE_BUNDLER: ${{ matrix.bundler }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
packages=(
|
||||
"@affine/web"
|
||||
"@affine/mobile"
|
||||
"@affine/ios"
|
||||
"@affine/android"
|
||||
"@affine/admin"
|
||||
"@affine/electron-renderer"
|
||||
)
|
||||
summary="test-results-bundler-${AFFINE_BUNDLER}.txt"
|
||||
: > "$summary"
|
||||
for pkg in "${packages[@]}"; do
|
||||
start=$(date +%s)
|
||||
yarn affine "$pkg" build
|
||||
end=$(date +%s)
|
||||
echo "${pkg},$((end-start))" >> "$summary"
|
||||
done
|
||||
|
||||
- name: Upload bundler timing
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-bundler-${{ matrix.bundler }}
|
||||
path: ./test-results-bundler-${{ matrix.bundler }}.txt
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-24.04-arm
|
||||
@@ -453,7 +340,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -485,7 +372,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -515,9 +402,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
shard: [1, 2, 3]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -550,7 +437,7 @@ jobs:
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -589,7 +476,7 @@ jobs:
|
||||
- { os: macos-latest, target: aarch64-apple-darwin }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -630,7 +517,7 @@ jobs:
|
||||
- { os: windows-latest, target: aarch64-pc-windows-msvc }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: samypr100/setup-dev-drive@v3
|
||||
with:
|
||||
workspace-copy: true
|
||||
@@ -670,7 +557,7 @@ jobs:
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -693,7 +580,7 @@ jobs:
|
||||
name: Build @affine/electron renderer
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -720,7 +607,7 @@ jobs:
|
||||
needs:
|
||||
- build-native-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -774,7 +661,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -855,7 +742,7 @@ jobs:
|
||||
stack-version: 9.0.1
|
||||
security-enabled: false
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -918,7 +805,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -948,6 +835,99 @@ jobs:
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
miri:
|
||||
name: miri code check
|
||||
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- rust-test-filter
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
CARGO_TERM_COLOR: always
|
||||
MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: miri
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: nextest@0.9.98
|
||||
|
||||
- name: Miri Code Check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cargo +nightly miri nextest run -p y-octo -j4
|
||||
|
||||
loom:
|
||||
name: loom thread test
|
||||
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- rust-test-filter
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom
|
||||
RUST_BACKTRACE: full
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: nextest@0.9.98
|
||||
|
||||
- name: Loom Thread Test
|
||||
run: |
|
||||
cargo nextest run -p y-octo --lib
|
||||
|
||||
fuzzing:
|
||||
name: fuzzing
|
||||
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- rust-test-filter
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: fuzzing
|
||||
working-directory: ./packages/common/y-octo/utils
|
||||
run: |
|
||||
cargo install cargo-fuzz
|
||||
cargo +nightly fuzz run apply_update -- -max_total_time=30
|
||||
cargo +nightly fuzz run codec_doc_any_struct -- -max_total_time=30
|
||||
cargo +nightly fuzz run codec_doc_any -- -max_total_time=30
|
||||
cargo +nightly fuzz run decode_bytes -- -max_total_time=30
|
||||
cargo +nightly fuzz run i32_decode -- -max_total_time=30
|
||||
cargo +nightly fuzz run i32_encode -- -max_total_time=30
|
||||
cargo +nightly fuzz run ins_del_text -- -max_total_time=30
|
||||
cargo +nightly fuzz run sync_message -- -max_total_time=30
|
||||
cargo +nightly fuzz run u64_decode -- -max_total_time=30
|
||||
cargo +nightly fuzz run u64_encode -- -max_total_time=30
|
||||
cargo +nightly fuzz run apply_update -- -max_total_time=30
|
||||
|
||||
- name: upload fuzz artifacts
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fuzz-artifact
|
||||
path: packages/common/y-octo/utils/fuzz/artifacts/**/*
|
||||
|
||||
rust-test:
|
||||
name: Run native tests
|
||||
if: ${{ needs.rust-test-filter.outputs.run-rust == 'true' }}
|
||||
@@ -957,7 +937,7 @@ jobs:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
@@ -980,7 +960,7 @@ jobs:
|
||||
run-api: ${{ steps.decision.outputs.run_api }}
|
||||
run-e2e: ${{ steps.decision.outputs.run_e2e }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: copilot-filter
|
||||
@@ -1049,7 +1029,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -1122,7 +1102,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -1205,7 +1185,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -1286,7 +1266,7 @@ jobs:
|
||||
test: true,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
timeout-minutes: 10
|
||||
@@ -1388,9 +1368,6 @@ jobs:
|
||||
- analyze
|
||||
- lint
|
||||
- typecheck
|
||||
- mobile-native-build-filter
|
||||
- build-android-app
|
||||
- build-ios-app
|
||||
- lint-rust
|
||||
- check-git-status
|
||||
- check-yarn-binary
|
||||
@@ -1405,6 +1382,9 @@ jobs:
|
||||
- build-server-native
|
||||
- build-electron-renderer
|
||||
- native-unit-test
|
||||
- miri
|
||||
- loom
|
||||
- fuzzing
|
||||
- server-test
|
||||
- server-e2e-test
|
||||
- rust-test
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Post test result message
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.action != 'edited' || github.event.changes.title != null }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- build-images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Deploy to ${{ inputs.build-type }}
|
||||
uses: ./.github/actions/deploy
|
||||
with:
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
|
||||
- name: Signing By Apple Developer ID
|
||||
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
|
||||
uses: apple-actions/import-codesign-certs@v7
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||
@@ -178,14 +178,14 @@ jobs:
|
||||
mv packages/frontend/apps/electron/out/*/make/deb/${{ inputs.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
|
||||
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
|
||||
|
||||
- uses: actions/attest-build-provenance@v4
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ inputs.platform == 'darwin' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
|
||||
|
||||
- uses: actions/attest-build-provenance@v4
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ inputs.platform == 'linux' }}
|
||||
with:
|
||||
subject-path: |
|
||||
|
||||
@@ -27,11 +27,6 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
require-windows-signing:
|
||||
description: 'Require all Windows signing steps to succeed before release'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
@@ -53,7 +48,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -78,21 +73,6 @@ jobs:
|
||||
name: desktop-web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
windows-signer-gate:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
signer_available: ${{ steps.check.outputs.signer_available }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Check windows signer availability
|
||||
id: check
|
||||
run: node ./scripts/check-windows-signer.mjs
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
REQUIRE_SIGNER: ${{ inputs.require-windows-signing }}
|
||||
|
||||
make-distribution-macos:
|
||||
if: ${{ inputs.desktop_macos }}
|
||||
strategy:
|
||||
@@ -174,45 +154,24 @@ jobs:
|
||||
enable_scripts: true
|
||||
|
||||
sign-packaged-artifacts-windows_x64:
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-x64
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: 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
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: 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'
|
||||
)
|
||||
)
|
||||
}}
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-x64
|
||||
- package-distribution-windows-arm64
|
||||
- sign-packaged-artifacts-windows_x64
|
||||
- sign-packaged-artifacts-windows_arm64
|
||||
strategy:
|
||||
@@ -228,7 +187,7 @@ jobs:
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -250,13 +209,11 @@ jobs:
|
||||
- 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:
|
||||
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: |
|
||||
$DiffRoot = 'signed-packaged-diff/files'
|
||||
@@ -306,38 +263,25 @@ jobs:
|
||||
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
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: 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
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: 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'
|
||||
}}
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
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,
|
||||
@@ -355,22 +299,18 @@ jobs:
|
||||
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: |
|
||||
$DiffRoot = 'signed-installer-diff/files'
|
||||
@@ -398,15 +338,13 @@ jobs:
|
||||
}
|
||||
|
||||
- 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
|
||||
mv packages/frontend/apps/electron/out/*/make/squirrel.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
|
||||
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') }}
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
@@ -414,77 +352,50 @@ 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
|
||||
path: builds
|
||||
|
||||
release:
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
(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') &&
|
||||
(
|
||||
!inputs.require-windows-signing ||
|
||||
(
|
||||
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'
|
||||
)
|
||||
)
|
||||
}}
|
||||
if: ${{ inputs.desktop_macos && inputs.desktop_linux && inputs.desktop_windows }}
|
||||
needs:
|
||||
[
|
||||
before-make,
|
||||
make-distribution-macos,
|
||||
make-distribution-linux,
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
- 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: ${{ 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: ${{ 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
|
||||
path: ./release
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Copy Selfhost Release Files
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
build-android-web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
needs:
|
||||
- build-ios-web
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -109,15 +109,12 @@ 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
|
||||
run: yarn workspace @affine/ios sync
|
||||
- name: Signing By Apple Developer ID
|
||||
uses: apple-actions/import-codesign-certs@v7
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
id: import-codesign-certs
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12_MOBILE }}
|
||||
@@ -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
|
||||
@@ -152,7 +147,7 @@ jobs:
|
||||
needs:
|
||||
- build-android-web
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -185,7 +180,7 @@ jobs:
|
||||
no-build: 'true'
|
||||
- name: Cap sync
|
||||
run: yarn workspace @affine/android cap sync
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
- name: Auth gcloud
|
||||
@@ -197,7 +192,7 @@ jobs:
|
||||
token_format: 'access_token'
|
||||
project_id: '${{ secrets.GCP_PROJECT_ID }}'
|
||||
access_token_scopes: 'https://www.googleapis.com/auth/androidpublisher'
|
||||
- uses: actions/setup-java@v5
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
GIT_SHORT_HASH: ${{ steps.prepare.outputs.GIT_SHORT_HASH }}
|
||||
BUILD_TYPE: ${{ steps.prepare.outputs.BUILD_TYPE }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare Release
|
||||
id: prepare
|
||||
uses: ./.github/actions/prepare-release
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
steps:
|
||||
- name: Decide whether to release
|
||||
id: decide
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}'
|
||||
@@ -195,7 +195,6 @@ 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) }}
|
||||
|
||||
mobile:
|
||||
name: Release Mobile
|
||||
|
||||
-10
@@ -6,7 +6,6 @@
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
.yarn/versions
|
||||
.corepack-bin
|
||||
|
||||
# compiled output
|
||||
*dist
|
||||
@@ -49,9 +48,6 @@ testem.log
|
||||
/typings
|
||||
tsconfig.tsbuildinfo
|
||||
.context
|
||||
/*.md
|
||||
.codex
|
||||
.cursor
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
@@ -96,9 +92,3 @@ af.cmd
|
||||
|
||||
# playwright
|
||||
storageState.json
|
||||
/.understand-anything
|
||||
|
||||
# local test/browser artifacts
|
||||
/.playwright-browsers/
|
||||
**/.vitest-attachments/
|
||||
/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/
|
||||
|
||||
+7
-88
@@ -1,18 +1,10 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"jsPlugins": [
|
||||
"eslint-plugin-simple-import-sort",
|
||||
"eslint-plugin-sonarjs",
|
||||
"eslint-plugin-lit"
|
||||
],
|
||||
"plugins": ["import", "react", "typescript", "unicorn", "promise"],
|
||||
"categories": {
|
||||
"correctness": "error",
|
||||
"perf": "error"
|
||||
},
|
||||
"options": {
|
||||
"typeAware": true
|
||||
},
|
||||
"env": {
|
||||
"builtin": true,
|
||||
"es2026": true
|
||||
@@ -23,8 +15,6 @@
|
||||
".github/helm",
|
||||
".git",
|
||||
".vscode",
|
||||
".context",
|
||||
".codex",
|
||||
".yarnrc.yml",
|
||||
".docker",
|
||||
"**/.storybook",
|
||||
@@ -53,8 +43,6 @@
|
||||
"packages/frontend/apps/ios/App/**",
|
||||
"tests/blocksuite/snapshots",
|
||||
"blocksuite/docs/api/**",
|
||||
"blocksuite/docs-site/.vitepress/.temp/**",
|
||||
"blocksuite/docs-site/api/**",
|
||||
"packages/frontend/admin/src/config.json",
|
||||
"**/test-docs.json",
|
||||
"**/test-blocks.json"
|
||||
@@ -68,31 +56,9 @@
|
||||
"react/display-name": "error",
|
||||
"react/rules-of-hooks": "error",
|
||||
"react/exhaustive-deps": "warn",
|
||||
"typescript/prefer-for-of": "error",
|
||||
"typescript/no-unsafe-function-type": "error",
|
||||
"typescript/no-wrapper-object-types": "error",
|
||||
"typescript/unified-signatures": "error",
|
||||
"typescript/await-thenable": "allow",
|
||||
"typescript/no-floating-promises": "allow",
|
||||
"typescript/no-misused-promises": "allow",
|
||||
"typescript/prefer-readonly": "allow",
|
||||
"typescript/require-array-sort-compare": "allow",
|
||||
"typescript/return-await": ["error", "error-handling-correctness-only"],
|
||||
"typescript/no-array-delete": "allow",
|
||||
"typescript/no-base-to-string": "allow",
|
||||
"typescript/no-duplicate-type-constituents": "allow",
|
||||
"typescript/no-for-in-array": "allow",
|
||||
"typescript/no-implied-eval": "allow",
|
||||
"typescript/no-meaningless-void-operator": "allow",
|
||||
"typescript/no-misused-spread": "allow",
|
||||
"typescript/no-redundant-type-constituents": "allow",
|
||||
"typescript/no-unnecessary-parameter-property-assignment": "allow",
|
||||
"typescript/no-unsafe-unary-minus": "allow",
|
||||
"typescript/no-useless-empty-export": "allow",
|
||||
"typescript/prefer-namespace-keyword": "allow",
|
||||
"typescript/prefer-string-starts-ends-with": "allow",
|
||||
"typescript/restrict-template-expressions": "allow",
|
||||
"typescript/unbound-method": "allow",
|
||||
"@typescript-eslint/prefer-for-of": "error",
|
||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
@@ -201,29 +167,6 @@
|
||||
"react/jsx-no-target-blank": "error",
|
||||
"react/jsx-no-comment-textnodes": "error",
|
||||
"react/no-array-index-key": "off",
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
"sonarjs/no-all-duplicated-branches": "error",
|
||||
"sonarjs/no-element-overwrite": "error",
|
||||
"sonarjs/no-empty-collection": "error",
|
||||
"sonarjs/no-extra-arguments": "error",
|
||||
"sonarjs/no-identical-conditions": "error",
|
||||
"sonarjs/no-identical-expressions": "error",
|
||||
"sonarjs/no-ignored-return": "error",
|
||||
"sonarjs/no-use-of-empty-return-value": "error",
|
||||
"sonarjs/non-existent-operator": "error",
|
||||
"sonarjs/no-collapsible-if": "error",
|
||||
"sonarjs/no-same-line-conditional": "error",
|
||||
"sonarjs/no-duplicated-branches": "error",
|
||||
"sonarjs/no-collection-size-mischeck": "error",
|
||||
"sonarjs/no-identical-functions": "error",
|
||||
"sonarjs/no-gratuitous-expressions": "error",
|
||||
"lit/attribute-value-entities": "error",
|
||||
"lit/binding-positions": "error",
|
||||
"lit/no-duplicate-template-bindings": "error",
|
||||
"lit/no-invalid-html": "error",
|
||||
"lit/no-legacy-template-syntax": "error",
|
||||
"lit/no-property-change-update": "error",
|
||||
"typescript/consistent-type-imports": "error",
|
||||
"typescript/no-non-null-assertion": "error",
|
||||
"typescript/triple-slash-reference": "error",
|
||||
@@ -231,6 +174,7 @@
|
||||
"typescript/no-duplicate-enum-values": "error",
|
||||
"typescript/no-extra-non-null-assertion": "error",
|
||||
"typescript/no-misused-new": "error",
|
||||
"typescript/prefer-for-of": "error",
|
||||
"typescript/no-unsafe-declaration-merging": "error",
|
||||
"typescript/no-unnecessary-type-constraint": "error",
|
||||
"typescript/no-this-alias": [
|
||||
@@ -282,10 +226,11 @@
|
||||
"ignoreTypes": true
|
||||
}
|
||||
],
|
||||
"import/sort-imports": "error",
|
||||
"import/namespace": "off",
|
||||
"import/no-webpack-loader-syntax": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"no-import-assign": "error",
|
||||
"import/no-import-assign": "error",
|
||||
"import/no-self-import": "error"
|
||||
},
|
||||
"overrides": [
|
||||
@@ -324,7 +269,7 @@
|
||||
{
|
||||
"files": ["blocksuite/**/*.ts"],
|
||||
"rules": {
|
||||
"eqeqeq": "off",
|
||||
"eslint/eqeqeq": "off",
|
||||
"typescript/no-non-null-assertion": "off",
|
||||
"unicorn/prefer-array-some": "off"
|
||||
}
|
||||
@@ -345,17 +290,6 @@
|
||||
"blocksuite/**/*.{ts,tsx}"
|
||||
],
|
||||
"rules": {
|
||||
"typescript/await-thenable": "error",
|
||||
"typescript/no-floating-promises": [
|
||||
"error",
|
||||
{
|
||||
"ignoreVoid": false,
|
||||
"ignoreIIFE": false
|
||||
}
|
||||
],
|
||||
"typescript/no-misused-promises": "error",
|
||||
"typescript/prefer-readonly": "error",
|
||||
"typescript/require-array-sort-compare": "error",
|
||||
"react/exhaustive-deps": [
|
||||
"warn",
|
||||
{
|
||||
@@ -376,14 +310,6 @@
|
||||
"**/e2e/**/*"
|
||||
],
|
||||
"rules": {
|
||||
"typescript/no-floating-promises": [
|
||||
"error",
|
||||
{
|
||||
"ignoreVoid": true,
|
||||
"ignoreIIFE": false
|
||||
}
|
||||
],
|
||||
"typescript/no-misused-promises": "off",
|
||||
"no-restricted-imports": "off"
|
||||
}
|
||||
},
|
||||
@@ -392,13 +318,6 @@
|
||||
"rules": {
|
||||
"react/rules-of-hooks": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["blocksuite/framework/std/src/view/element/lit-host.ts"],
|
||||
"rules": {
|
||||
"lit/binding-positions": "off",
|
||||
"lit/no-invalid-html": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
.github/helm
|
||||
.git
|
||||
.vscode
|
||||
.context
|
||||
.codex
|
||||
.yarnrc.yml
|
||||
.docker
|
||||
**/.storybook
|
||||
@@ -40,8 +38,6 @@ packages/frontend/apps/android/App/**
|
||||
packages/frontend/apps/ios/App/**
|
||||
tests/blocksuite/snapshots
|
||||
blocksuite/docs/api/**
|
||||
blocksuite/docs-site/.vitepress/.temp/**
|
||||
blocksuite/docs-site/api/**
|
||||
packages/frontend/admin/src/config.json
|
||||
**/test-docs.json
|
||||
**/test-blocks.json
|
||||
|
||||
Vendored
-2
@@ -32,8 +32,6 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"vitest.include": ["packages/**/*.spec.ts", "packages/**/*.spec.tsx"],
|
||||
"eslint.runtime": "node",
|
||||
"eslint.execArgv": ["--max_old_space_size=16384"],
|
||||
"rust-analyzer.check.extraEnv": {
|
||||
"DATABASE_URL": "sqlite:affine.db"
|
||||
},
|
||||
|
||||
+942
File diff suppressed because one or more lines are too long
Vendored
-940
File diff suppressed because one or more lines are too long
+1
-1
@@ -12,4 +12,4 @@ npmPublishAccess: public
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.13.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||
|
||||
Generated
+859
-4714
File diff suppressed because it is too large
Load Diff
+25
-36
@@ -2,6 +2,8 @@
|
||||
members = [
|
||||
"./packages/backend/native",
|
||||
"./packages/common/native",
|
||||
"./packages/common/y-octo/core",
|
||||
"./packages/common/y-octo/utils",
|
||||
"./packages/frontend/mobile-native",
|
||||
"./packages/frontend/native",
|
||||
"./packages/frontend/native/nbstore",
|
||||
@@ -14,14 +16,13 @@ resolver = "3"
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
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"
|
||||
async-lock = { version = "3.4.0", features = ["loom"] }
|
||||
base64-simd = "0.8"
|
||||
bitvec = "1.0"
|
||||
block2 = "0.6"
|
||||
@@ -35,31 +36,18 @@ resolver = "3"
|
||||
criterion2 = { version = "3", default-features = false }
|
||||
crossbeam-channel = "0.5"
|
||||
dispatch2 = "0.3"
|
||||
doc_extractor = "0.1.0"
|
||||
docx-parser = { git = "https://github.com/toeverything/docx-parser" }
|
||||
dotenvy = "0.15"
|
||||
file-format = { version = "0.28", features = ["reader"] }
|
||||
hex = "0.4"
|
||||
homedir = "0.3"
|
||||
image = { version = "0.25.9", default-features = false, features = [
|
||||
"bmp",
|
||||
"gif",
|
||||
"jpeg",
|
||||
"png",
|
||||
"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"
|
||||
loom = { version = "0.7", features = ["checkpoint"] }
|
||||
lru = "0.16"
|
||||
matroska = "0.30"
|
||||
memory-indexer = "0.3.1"
|
||||
mermaid-rs-renderer = { git = "https://github.com/toeverything/mermaid-rs-renderer", rev = "fba9097", default-features = false }
|
||||
memory-indexer = "0.3.0"
|
||||
mimalloc = "0.1"
|
||||
mp4parse = "0.17"
|
||||
nanoid = "0.4"
|
||||
@@ -76,11 +64,11 @@ resolver = "3"
|
||||
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"
|
||||
path-ext = "0.1.2"
|
||||
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
proptest = "1.3"
|
||||
proptest-derive = "0.5"
|
||||
@@ -89,14 +77,12 @@ resolver = "3"
|
||||
rand_chacha = "0.9"
|
||||
rand_distr = "0.5"
|
||||
rayon = "1.10"
|
||||
readability = { version = "0.3.0", default-features = false }
|
||||
regex = "1.10"
|
||||
rubato = "0.16"
|
||||
safefetch = "0.1.0"
|
||||
schemars = "0.8"
|
||||
screencapturekit = "0.3"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
sha3 = "0.10"
|
||||
smol_str = "0.3"
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
@@ -105,19 +91,26 @@ resolver = "3"
|
||||
"migrate",
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
"tls-rustls",
|
||||
] }
|
||||
strum_macros = "0.27.0"
|
||||
symphonia = { version = "0.5", features = ["all", "opt-simd"] }
|
||||
text-splitter = "0.27"
|
||||
thiserror = "2"
|
||||
tiktoken-rs = "0.7"
|
||||
tokio = "1.45"
|
||||
typst = "0.14.2"
|
||||
typst-as-lib = { version = "0.15.4", default-features = false, features = [
|
||||
"packages",
|
||||
"typst-kit-embed-fonts",
|
||||
"typst-kit-fonts",
|
||||
"ureq",
|
||||
] }
|
||||
typst-svg = "0.14.2"
|
||||
tree-sitter = { version = "0.25" }
|
||||
tree-sitter-c = { version = "0.24" }
|
||||
tree-sitter-c-sharp = { version = "0.23" }
|
||||
tree-sitter-cpp = { version = "0.23" }
|
||||
tree-sitter-go = { version = "0.23" }
|
||||
tree-sitter-java = { version = "0.23" }
|
||||
tree-sitter-javascript = { version = "0.23" }
|
||||
tree-sitter-kotlin-ng = { version = "1.1" }
|
||||
tree-sitter-python = { version = "0.23" }
|
||||
tree-sitter-rust = { version = "0.24" }
|
||||
tree-sitter-scala = { version = "0.24" }
|
||||
tree-sitter-typescript = { version = "0.23" }
|
||||
uniffi = "0.29"
|
||||
url = { version = "2.5" }
|
||||
uuid = "1.8"
|
||||
@@ -135,7 +128,7 @@ resolver = "3"
|
||||
"Win32_UI_Shell_PropertiesSystem",
|
||||
] }
|
||||
windows-core = { version = "0.61" }
|
||||
y-octo = "0.0.3"
|
||||
y-octo = { path = "./packages/common/y-octo/core" }
|
||||
y-sync = { version = "0.4" }
|
||||
yrs = "0.23.0"
|
||||
|
||||
@@ -151,7 +144,3 @@ strip = "symbols"
|
||||
# android uniffi bindgen requires symbols
|
||||
[profile.release.package.affine_mobile_native]
|
||||
strip = "none"
|
||||
|
||||
# [patch.crates-io]
|
||||
# llm_adapter = { path = "../llm_adapter/crates/llm_adapter" }
|
||||
# llm_runtime = { path = "../llm_adapter/crates/llm_runtime" }
|
||||
|
||||
@@ -23,6 +23,4 @@ We welcome you to provide us with bug reports via and email at [security@toevery
|
||||
|
||||
Since we are an open source project, we also welcome you to provide corresponding fix PRs, we will determine specific rewards based on the evaluation results.
|
||||
|
||||
Due to limited resources, we do not accept and will not review any AI-generated security reports.
|
||||
|
||||
If the vulnerability is caused by a library we depend on, we encourage you to submit a security report to the corresponding dependent library at the same time to benefit more users.
|
||||
|
||||
@@ -78,7 +78,8 @@
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@blocksuite/sync": "workspace:*"
|
||||
"@blocksuite/sync": "workspace:*",
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -295,10 +296,10 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0",
|
||||
"version": "0.26.3",
|
||||
"devDependencies": {
|
||||
"@vanilla-extract/vite-plugin": "^5.0.0",
|
||||
"msw": "^2.13.2",
|
||||
"vitest": "^4.1.8"
|
||||
"msw": "^2.12.4",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`snapshot to markdown > imports obsidian vault fixtures 1`] = `
|
||||
{
|
||||
"entry": {
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Panel
|
||||
Body line",
|
||||
},
|
||||
],
|
||||
"flavour": "affine:paragraph",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"emoji": "💡",
|
||||
"flavour": "affine:callout",
|
||||
},
|
||||
{
|
||||
"flavour": "affine:attachment",
|
||||
"name": "archive.zip",
|
||||
"style": "horizontalThin",
|
||||
},
|
||||
{
|
||||
"delta": [
|
||||
{
|
||||
"footnote": {
|
||||
"label": "1",
|
||||
"reference": {
|
||||
"title": "reference body",
|
||||
"type": "url",
|
||||
},
|
||||
},
|
||||
"insert": " ",
|
||||
},
|
||||
],
|
||||
"flavour": "affine:paragraph",
|
||||
"type": "text",
|
||||
},
|
||||
{
|
||||
"flavour": "affine:divider",
|
||||
},
|
||||
{
|
||||
"delta": [
|
||||
{
|
||||
"insert": "after note",
|
||||
},
|
||||
],
|
||||
"flavour": "affine:paragraph",
|
||||
"type": "text",
|
||||
},
|
||||
{
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"reference": {
|
||||
"page": "linked",
|
||||
"type": "LinkedPage",
|
||||
},
|
||||
},
|
||||
],
|
||||
"flavour": "affine:paragraph",
|
||||
"type": "text",
|
||||
},
|
||||
{
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Sources",
|
||||
},
|
||||
],
|
||||
"flavour": "affine:paragraph",
|
||||
"type": "h6",
|
||||
},
|
||||
{
|
||||
"flavour": "affine:bookmark",
|
||||
},
|
||||
],
|
||||
"flavour": "affine:note",
|
||||
},
|
||||
],
|
||||
"flavour": "affine:page",
|
||||
},
|
||||
"titles": [
|
||||
"entry",
|
||||
"linked",
|
||||
],
|
||||
}
|
||||
`;
|
||||
@@ -1,14 +0,0 @@
|
||||
> [!custom] Panel
|
||||
> Body line
|
||||
|
||||
![[archive.zip]]
|
||||
|
||||
[^1]
|
||||
|
||||
---
|
||||
|
||||
after note
|
||||
|
||||
[[linked]]
|
||||
|
||||
[^1]: reference body
|
||||
@@ -1 +0,0 @@
|
||||
plain linked page
|
||||
@@ -1,10 +1,4 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { basename, resolve } from 'node:path';
|
||||
|
||||
import {
|
||||
MarkdownTransformer,
|
||||
ObsidianTransformer,
|
||||
} from '@blocksuite/affine/widgets/linked-doc';
|
||||
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||
import {
|
||||
DefaultTheme,
|
||||
NoteDisplayMode,
|
||||
@@ -14,18 +8,13 @@ import {
|
||||
CalloutAdmonitionType,
|
||||
CalloutExportStyle,
|
||||
calloutMarkdownExportMiddleware,
|
||||
docLinkBaseURLMiddleware,
|
||||
embedSyncedDocMiddleware,
|
||||
MarkdownAdapter,
|
||||
titleMiddleware,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type {
|
||||
BlockSnapshot,
|
||||
DeltaInsert,
|
||||
DocSnapshot,
|
||||
SliceSnapshot,
|
||||
Store,
|
||||
TransformerMiddleware,
|
||||
} from '@blocksuite/store';
|
||||
import { AssetsManager, MemoryBlobCRUD, Schema } from '@blocksuite/store';
|
||||
@@ -40,138 +29,6 @@ import { testStoreExtensions } from '../utils/store.js';
|
||||
|
||||
const provider = getProvider();
|
||||
|
||||
function withRelativePath(file: File, relativePath: string): File {
|
||||
Object.defineProperty(file, 'webkitRelativePath', {
|
||||
value: relativePath,
|
||||
writable: false,
|
||||
});
|
||||
return file;
|
||||
}
|
||||
|
||||
function markdownFixture(relativePath: string): File {
|
||||
return withRelativePath(
|
||||
new File(
|
||||
[
|
||||
readFileSync(
|
||||
resolve(import.meta.dirname, 'fixtures/obsidian', relativePath),
|
||||
'utf8'
|
||||
),
|
||||
],
|
||||
basename(relativePath),
|
||||
{ type: 'text/markdown' }
|
||||
),
|
||||
`vault/${relativePath}`
|
||||
);
|
||||
}
|
||||
|
||||
function exportSnapshot(doc: Store): DocSnapshot {
|
||||
const job = doc.getTransformer([
|
||||
docLinkBaseURLMiddleware(doc.workspace.id),
|
||||
titleMiddleware(doc.workspace.meta.docMetas),
|
||||
]);
|
||||
const snapshot = job.docToSnapshot(doc);
|
||||
expect(snapshot).toBeTruthy();
|
||||
return snapshot!;
|
||||
}
|
||||
|
||||
function normalizeDeltaForSnapshot(
|
||||
delta: DeltaInsert<AffineTextAttributes>[],
|
||||
titleById: ReadonlyMap<string, string>
|
||||
) {
|
||||
return delta.map(item => {
|
||||
const normalized: Record<string, unknown> = {
|
||||
insert: item.insert,
|
||||
};
|
||||
|
||||
if (item.attributes?.link) {
|
||||
normalized.link = item.attributes.link;
|
||||
}
|
||||
|
||||
if (item.attributes?.reference?.type === 'LinkedPage') {
|
||||
normalized.reference = {
|
||||
type: 'LinkedPage',
|
||||
page: titleById.get(item.attributes.reference.pageId) ?? '<missing>',
|
||||
...(item.attributes.reference.title
|
||||
? { title: item.attributes.reference.title }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (item.attributes?.footnote) {
|
||||
const reference = item.attributes.footnote.reference;
|
||||
normalized.footnote = {
|
||||
label: item.attributes.footnote.label,
|
||||
reference:
|
||||
reference.type === 'doc'
|
||||
? {
|
||||
type: 'doc',
|
||||
page: reference.docId
|
||||
? (titleById.get(reference.docId) ?? '<missing>')
|
||||
: '<missing>',
|
||||
}
|
||||
: {
|
||||
type: reference.type,
|
||||
...(reference.title ? { title: reference.title } : {}),
|
||||
...(reference.fileName ? { fileName: reference.fileName } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return normalized;
|
||||
});
|
||||
}
|
||||
|
||||
function simplifyBlockForSnapshot(
|
||||
block: BlockSnapshot,
|
||||
titleById: ReadonlyMap<string, string>
|
||||
): Record<string, unknown> {
|
||||
const simplified: Record<string, unknown> = {
|
||||
flavour: block.flavour,
|
||||
};
|
||||
|
||||
if (block.flavour === 'affine:paragraph' || block.flavour === 'affine:list') {
|
||||
simplified.type = block.props.type;
|
||||
const text = block.props.text as
|
||||
| { delta?: DeltaInsert<AffineTextAttributes>[] }
|
||||
| undefined;
|
||||
simplified.delta = normalizeDeltaForSnapshot(text?.delta ?? [], titleById);
|
||||
}
|
||||
|
||||
if (block.flavour === 'affine:callout') {
|
||||
simplified.emoji = block.props.emoji;
|
||||
}
|
||||
|
||||
if (block.flavour === 'affine:attachment') {
|
||||
simplified.name = block.props.name;
|
||||
simplified.style = block.props.style;
|
||||
}
|
||||
|
||||
if (block.flavour === 'affine:image') {
|
||||
simplified.sourceId = '<asset>';
|
||||
}
|
||||
|
||||
const children = (block.children ?? [])
|
||||
.filter(child => child.flavour !== 'affine:surface')
|
||||
.map(child => simplifyBlockForSnapshot(child, titleById));
|
||||
if (children.length) {
|
||||
simplified.children = children;
|
||||
}
|
||||
|
||||
return simplified;
|
||||
}
|
||||
|
||||
function snapshotDocByTitle(
|
||||
collection: TestWorkspace,
|
||||
title: string,
|
||||
titleById: ReadonlyMap<string, 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();
|
||||
return simplifyBlockForSnapshot(exportSnapshot(doc!).blocks, titleById);
|
||||
}
|
||||
|
||||
describe('snapshot to markdown', () => {
|
||||
test('code', async () => {
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
@@ -270,94 +127,6 @@ Hello world
|
||||
expect(meta?.tags).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
test('preserves list text inside blockquotes without list blocks', async () => {
|
||||
const markdown = `> **Shopping List:**
|
||||
> - Apples
|
||||
> - Bananas
|
||||
> - Oranges
|
||||
`;
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const snapshot = await mdAdapter.toDocSnapshot({
|
||||
file: markdown,
|
||||
assets: new AssetsManager({ blob: new MemoryBlobCRUD() }),
|
||||
});
|
||||
|
||||
expect(simplifyBlockForSnapshot(snapshot.blocks, new Map())).toMatchObject({
|
||||
children: [
|
||||
{
|
||||
flavour: 'affine:note',
|
||||
children: [
|
||||
{
|
||||
flavour: 'affine:paragraph',
|
||||
type: 'quote',
|
||||
delta: [
|
||||
{ insert: 'Shopping List:' },
|
||||
{ insert: '\n' },
|
||||
{ insert: '- ' },
|
||||
{ insert: 'Apples' },
|
||||
{ insert: '\n' },
|
||||
{ insert: '- ' },
|
||||
{ insert: 'Bananas' },
|
||||
{ insert: '\n' },
|
||||
{ insert: '- ' },
|
||||
{ insert: 'Oranges' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const exported = await mdAdapter.fromDocSnapshot({
|
||||
snapshot,
|
||||
assets: new AssetsManager({ blob: new MemoryBlobCRUD() }),
|
||||
});
|
||||
expect(exported.file).toContain('> **Shopping List:**');
|
||||
expect(exported.file).toContain('> \\- Apples');
|
||||
expect(exported.file).toContain('> \\- Bananas');
|
||||
expect(exported.file).toContain('> \\- Oranges');
|
||||
});
|
||||
|
||||
test('imports obsidian vault fixtures', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const attachment = withRelativePath(
|
||||
new File([new Uint8Array([80, 75, 3, 4])], 'archive.zip', {
|
||||
type: 'application/zip',
|
||||
}),
|
||||
'vault/archive.zip'
|
||||
);
|
||||
|
||||
const { docIds } = await ObsidianTransformer.importObsidianVault({
|
||||
collection,
|
||||
schema,
|
||||
importedFiles: [
|
||||
markdownFixture('entry.md'),
|
||||
markdownFixture('linked.md'),
|
||||
attachment,
|
||||
],
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
expect(docIds).toHaveLength(2);
|
||||
|
||||
const titleById = new Map(
|
||||
collection.meta.docMetas.map(meta => [
|
||||
meta.id,
|
||||
meta.title ?? '<untitled>',
|
||||
])
|
||||
);
|
||||
|
||||
expect({
|
||||
titles: collection.meta.docMetas
|
||||
.map(meta => meta.title)
|
||||
.sort((a, b) => (a ?? '').localeCompare(b ?? '')),
|
||||
entry: snapshotDocByTitle(collection, 'entry', titleById),
|
||||
}).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('paragraph', async () => {
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { bilibiliConfig } from '../../../blocks/embed/src/embed-iframe-block/configs/providers/bilibili.js';
|
||||
import { excalidrawConfig } from '../../../blocks/embed/src/embed-iframe-block/configs/providers/excalidraw.js';
|
||||
import { genericConfig } from '../../../blocks/embed/src/embed-iframe-block/configs/providers/generic.js';
|
||||
import { googleDocsConfig } from '../../../blocks/embed/src/embed-iframe-block/configs/providers/google-docs.js';
|
||||
import { googleDriveConfig } from '../../../blocks/embed/src/embed-iframe-block/configs/providers/google-drive.js';
|
||||
import { miroConfig } from '../../../blocks/embed/src/embed-iframe-block/configs/providers/miro.js';
|
||||
import { spotifyConfig } from '../../../blocks/embed/src/embed-iframe-block/configs/providers/spotify.js';
|
||||
|
||||
describe('embed iframe provider config', () => {
|
||||
test('validates final iframe URLs from oEmbed providers', () => {
|
||||
expect(
|
||||
spotifyConfig.validateIframeUrl?.(
|
||||
'https://open.spotify.com/embed/track/0TK2YIli7K1leLovkQiNik'
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
spotifyConfig.validateIframeUrl?.(
|
||||
'https://example.com/embed/track/0TK2YIli7K1leLovkQiNik'
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('validates provider-specific iframe URL shapes', () => {
|
||||
expect(
|
||||
googleDriveConfig.validateIframeUrl?.(
|
||||
'https://drive.google.com/file/d/file-id/preview?usp=embed_googleplus'
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
googleDriveConfig.validateIframeUrl?.(
|
||||
'https://drive.google.com/drive/folders/folder-id?usp=sharing'
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
bilibiliConfig.validateIframeUrl?.(
|
||||
'https://player.bilibili.com/player.html?bvid=BV1xx411c7mD&autoplay=0'
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
bilibiliConfig.match(
|
||||
'https://player.bilibili.com/player.html?aid=123&autoplay=0'
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
bilibiliConfig.buildOEmbedUrl(
|
||||
'https://player.bilibili.com/video/BV1xx411c7mD'
|
||||
)
|
||||
).toBe(
|
||||
'https://player.bilibili.com/player.html?bvid=BV1xx411c7mD&autoplay=0'
|
||||
);
|
||||
expect(
|
||||
bilibiliConfig.validateIframeUrl?.(
|
||||
'https://www.bilibili.com/video/BV1xx411c7mD'
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
googleDocsConfig.validateIframeUrl?.(
|
||||
'https://docs.google.com/document/d/doc-id/edit?usp=sharing'
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
miroConfig.validateIframeUrl?.(
|
||||
'https://miro.com/app/live-embed/board-id/'
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
excalidrawConfig.validateIframeUrl?.('https://excalidraw.com/#room-id')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('generic iframe validation excludes affine and non-https URLs', () => {
|
||||
expect(genericConfig.validateIframeUrl?.('https://example.com/embed')).toBe(
|
||||
true
|
||||
);
|
||||
expect(genericConfig.validateIframeUrl?.('http://example.com/embed')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
genericConfig.validateIframeUrl?.('https://app.affine.pro/embed')
|
||||
).toBe(false);
|
||||
expect(genericConfig.validateIframeUrl?.('https://127.0.0.1/embed')).toBe(
|
||||
false
|
||||
);
|
||||
expect(genericConfig.validateIframeUrl?.('https://localhost/embed')).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,770 +0,0 @@
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import { Viewport, viewportRuntimeConfig } from '@blocksuite/std/gfx';
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import * as viewportModule from '../../../../../framework/std/src/gfx/viewport.js';
|
||||
import * as viewportElementModule from '../../../../../framework/std/src/gfx/viewport-element.js';
|
||||
import * as canvasRendererModule from '../../../../blocks/surface/src/renderer/canvas-renderer.js';
|
||||
import {
|
||||
paintPlaceholder,
|
||||
syncCanvasSize,
|
||||
} from '../../../../gfx/turbo-renderer/src/renderer-utils.js';
|
||||
import type { ViewportLayoutTree } from '../../../../gfx/turbo-renderer/src/types.js';
|
||||
|
||||
const originalCaps = [...viewportRuntimeConfig.CANVAS_DPR_CAP_BY_ZOOM];
|
||||
const originalDevicePixelRatio = Object.getOwnPropertyDescriptor(
|
||||
window,
|
||||
'devicePixelRatio'
|
||||
);
|
||||
|
||||
function setDevicePixelRatio(value: number) {
|
||||
Object.defineProperty(window, 'devicePixelRatio', {
|
||||
configurable: true,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
function createRect(width: number, height: number): DOMRect {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: width,
|
||||
bottom: height,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => ({}),
|
||||
} as DOMRect;
|
||||
}
|
||||
|
||||
function createFakeBlockModel(
|
||||
id: string,
|
||||
x: number,
|
||||
y: number,
|
||||
w = 10,
|
||||
h = 10
|
||||
) {
|
||||
return {
|
||||
id,
|
||||
elementBound: new Bound(x, y, w, h),
|
||||
};
|
||||
}
|
||||
|
||||
type PaintPlaceholderForTest = (
|
||||
canvas: HTMLCanvasElement,
|
||||
layout: ViewportLayoutTree,
|
||||
viewport: {
|
||||
zoom: number;
|
||||
toViewCoord: (x: number, y: number) => [number, number];
|
||||
}
|
||||
) => void;
|
||||
|
||||
afterEach(() => {
|
||||
viewportRuntimeConfig.CANVAS_DPR_CAP_BY_ZOOM = [...originalCaps];
|
||||
|
||||
if (originalDevicePixelRatio) {
|
||||
Object.defineProperty(window, 'devicePixelRatio', originalDevicePixelRatio);
|
||||
}
|
||||
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('edgeless canvas budget', () => {
|
||||
test('requests canvas budget sync when zoom crosses an effective dpr bucket', () => {
|
||||
viewportRuntimeConfig.CANVAS_DPR_CAP_BY_ZOOM = [
|
||||
[0.5, 1],
|
||||
[0.8, 2],
|
||||
];
|
||||
|
||||
expect(
|
||||
'shouldSyncCanvasBudgetOnViewportUpdate' in canvasRendererModule
|
||||
).toBe(true);
|
||||
|
||||
const shouldSyncCanvasBudgetOnViewportUpdate = (
|
||||
canvasRendererModule as {
|
||||
shouldSyncCanvasBudgetOnViewportUpdate: (
|
||||
previousZoom: number,
|
||||
nextZoom: number,
|
||||
rawDpr?: number
|
||||
) => boolean;
|
||||
}
|
||||
).shouldSyncCanvasBudgetOnViewportUpdate;
|
||||
|
||||
expect(shouldSyncCanvasBudgetOnViewportUpdate(0.95, 0.4, 2)).toBe(true);
|
||||
expect(shouldSyncCanvasBudgetOnViewportUpdate(0.95, 0.75, 2)).toBe(false);
|
||||
expect(shouldSyncCanvasBudgetOnViewportUpdate(0.45, 0.4, 2)).toBe(false);
|
||||
expect(shouldSyncCanvasBudgetOnViewportUpdate(0.95, 0.4, 1)).toBe(false);
|
||||
});
|
||||
|
||||
test('enables low-zoom survival mode only for active iOS gestures', () => {
|
||||
expect('shouldUseLowZoomSurvivalMode' in canvasRendererModule).toBe(true);
|
||||
|
||||
const shouldUseLowZoomSurvivalMode = (
|
||||
canvasRendererModule as {
|
||||
shouldUseLowZoomSurvivalMode: (
|
||||
isIOS: boolean,
|
||||
zoom: number,
|
||||
gestureActive: boolean
|
||||
) => boolean;
|
||||
}
|
||||
).shouldUseLowZoomSurvivalMode;
|
||||
|
||||
expect(shouldUseLowZoomSurvivalMode(true, 0.4, true)).toBe(true);
|
||||
expect(shouldUseLowZoomSurvivalMode(true, 0.6, true)).toBe(false);
|
||||
expect(shouldUseLowZoomSurvivalMode(true, 0.4, false)).toBe(false);
|
||||
expect(shouldUseLowZoomSurvivalMode(false, 0.4, true)).toBe(false);
|
||||
});
|
||||
|
||||
test('does not enable canvas placeholders for low-zoom panning without zooming', () => {
|
||||
expect('shouldRenderCanvasPlaceholders' in canvasRendererModule).toBe(true);
|
||||
|
||||
const shouldRenderCanvasPlaceholders = (
|
||||
canvasRendererModule as {
|
||||
shouldRenderCanvasPlaceholders: (params: {
|
||||
isIOS: boolean;
|
||||
zoom: number;
|
||||
isPanning: boolean;
|
||||
isZooming: boolean;
|
||||
skipRefreshDuringGesture: boolean;
|
||||
turboEnabled: boolean;
|
||||
}) => boolean;
|
||||
}
|
||||
).shouldRenderCanvasPlaceholders;
|
||||
|
||||
expect(
|
||||
shouldRenderCanvasPlaceholders({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
isPanning: true,
|
||||
isZooming: false,
|
||||
skipRefreshDuringGesture: true,
|
||||
turboEnabled: true,
|
||||
})
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
shouldRenderCanvasPlaceholders({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
isPanning: false,
|
||||
isZooming: true,
|
||||
skipRefreshDuringGesture: true,
|
||||
turboEnabled: true,
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('shares one bypass decision for placeholder and render paths only during the low-zoom iOS landscape gesture or recovery window', () => {
|
||||
expect('getStackingCanvasBypassState' in canvasRendererModule).toBe(true);
|
||||
expect(
|
||||
'shouldBypassStackingCanvasesDuringLowZoomGesture' in canvasRendererModule
|
||||
).toBe(true);
|
||||
|
||||
const getStackingCanvasBypassState = (
|
||||
canvasRendererModule as {
|
||||
getStackingCanvasBypassState: (params: {
|
||||
isIOS: boolean;
|
||||
zoom: number;
|
||||
gestureActive: boolean;
|
||||
recoveryActive: boolean;
|
||||
viewportWidth: number;
|
||||
viewportHeight: number;
|
||||
}) => boolean;
|
||||
}
|
||||
).getStackingCanvasBypassState;
|
||||
const shouldBypassStackingCanvasesDuringLowZoomGesture = (
|
||||
canvasRendererModule as {
|
||||
shouldBypassStackingCanvasesDuringLowZoomGesture: (params: {
|
||||
isIOS: boolean;
|
||||
zoom: number;
|
||||
gestureActive: boolean;
|
||||
recoveryActive: boolean;
|
||||
viewportWidth: number;
|
||||
viewportHeight: number;
|
||||
}) => boolean;
|
||||
}
|
||||
).shouldBypassStackingCanvasesDuringLowZoomGesture;
|
||||
|
||||
expect(
|
||||
getStackingCanvasBypassState({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
gestureActive: true,
|
||||
recoveryActive: false,
|
||||
viewportWidth: 932,
|
||||
viewportHeight: 430,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
getStackingCanvasBypassState({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
gestureActive: false,
|
||||
recoveryActive: true,
|
||||
viewportWidth: 932,
|
||||
viewportHeight: 430,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
getStackingCanvasBypassState({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
gestureActive: false,
|
||||
recoveryActive: false,
|
||||
viewportWidth: 932,
|
||||
viewportHeight: 430,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
shouldBypassStackingCanvasesDuringLowZoomGesture({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
gestureActive: false,
|
||||
recoveryActive: false,
|
||||
viewportWidth: 932,
|
||||
viewportHeight: 430,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
getStackingCanvasBypassState({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
gestureActive: true,
|
||||
recoveryActive: false,
|
||||
viewportWidth: 430,
|
||||
viewportHeight: 932,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
getStackingCanvasBypassState({
|
||||
isIOS: true,
|
||||
zoom: 0.6,
|
||||
gestureActive: true,
|
||||
recoveryActive: false,
|
||||
viewportWidth: 932,
|
||||
viewportHeight: 430,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
getStackingCanvasBypassState({
|
||||
isIOS: false,
|
||||
zoom: 0.4,
|
||||
gestureActive: true,
|
||||
recoveryActive: false,
|
||||
viewportWidth: 932,
|
||||
viewportHeight: 430,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('gesture low-zoom landscape bypass detaches stacking canvases through the existing attachment path', () => {
|
||||
expect(
|
||||
'shouldBypassStackingCanvasesDuringLowZoomGesture' in canvasRendererModule
|
||||
).toBe(true);
|
||||
expect('getStackingCanvasAttachmentDiff' in canvasRendererModule).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
const shouldBypassStackingCanvasesDuringLowZoomGesture = (
|
||||
canvasRendererModule as {
|
||||
shouldBypassStackingCanvasesDuringLowZoomGesture: (params: {
|
||||
isIOS: boolean;
|
||||
zoom: number;
|
||||
gestureActive: boolean;
|
||||
recoveryActive: boolean;
|
||||
viewportWidth: number;
|
||||
viewportHeight: number;
|
||||
}) => boolean;
|
||||
}
|
||||
).shouldBypassStackingCanvasesDuringLowZoomGesture;
|
||||
const getStackingCanvasAttachmentDiff = (
|
||||
canvasRendererModule as {
|
||||
getStackingCanvasAttachmentDiff: (params: {
|
||||
canvases: HTMLCanvasElement[];
|
||||
wasAttached: boolean;
|
||||
shouldAttach: boolean;
|
||||
}) => {
|
||||
added: HTMLCanvasElement[];
|
||||
removed: HTMLCanvasElement[];
|
||||
};
|
||||
}
|
||||
).getStackingCanvasAttachmentDiff;
|
||||
|
||||
const canvases = [document.createElement('canvas')];
|
||||
const shouldBypass = shouldBypassStackingCanvasesDuringLowZoomGesture({
|
||||
isIOS: true,
|
||||
zoom: 0.4,
|
||||
gestureActive: true,
|
||||
recoveryActive: false,
|
||||
viewportWidth: 932,
|
||||
viewportHeight: 430,
|
||||
});
|
||||
|
||||
expect(shouldBypass).toBe(true);
|
||||
expect(
|
||||
getStackingCanvasAttachmentDiff({
|
||||
canvases,
|
||||
wasAttached: true,
|
||||
shouldAttach: !shouldBypass,
|
||||
})
|
||||
).toEqual({
|
||||
added: [],
|
||||
removed: canvases,
|
||||
});
|
||||
});
|
||||
|
||||
test('uses overscan for main-canvas fallback culling and render origin', () => {
|
||||
expect('getMainCanvasFallbackBounds' in canvasRendererModule).toBe(true);
|
||||
|
||||
const getMainCanvasFallbackBounds = (
|
||||
canvasRendererModule as {
|
||||
getMainCanvasFallbackBounds: (params: {
|
||||
viewportBounds: Bound;
|
||||
overscanViewportBounds: Bound;
|
||||
}) => {
|
||||
cullBound: Bound;
|
||||
renderBound: Bound;
|
||||
};
|
||||
}
|
||||
).getMainCanvasFallbackBounds;
|
||||
|
||||
const viewportBounds = new Bound(100, 200, 300, 150);
|
||||
const overscanViewportBounds = new Bound(40, 170, 420, 210);
|
||||
|
||||
expect(
|
||||
getMainCanvasFallbackBounds({
|
||||
viewportBounds,
|
||||
overscanViewportBounds,
|
||||
})
|
||||
).toEqual({
|
||||
cullBound: overscanViewportBounds,
|
||||
renderBound: overscanViewportBounds,
|
||||
});
|
||||
});
|
||||
|
||||
test('lays out overscan canvases relative to the exact viewport', () => {
|
||||
expect('getCanvasViewportLayout' in canvasRendererModule).toBe(true);
|
||||
|
||||
const getCanvasViewportLayout = (
|
||||
canvasRendererModule as {
|
||||
getCanvasViewportLayout: (params: {
|
||||
bound: Bound;
|
||||
viewportBounds: Bound;
|
||||
zoom: number;
|
||||
viewScale: number;
|
||||
dpr: number;
|
||||
}) => {
|
||||
actualHeight: number;
|
||||
actualWidth: number;
|
||||
height: number;
|
||||
transform: string;
|
||||
width: number;
|
||||
};
|
||||
}
|
||||
).getCanvasViewportLayout;
|
||||
|
||||
expect(
|
||||
getCanvasViewportLayout({
|
||||
bound: new Bound(40, 170, 420, 210),
|
||||
viewportBounds: new Bound(100, 200, 300, 150),
|
||||
zoom: 1,
|
||||
viewScale: 1,
|
||||
dpr: 2,
|
||||
})
|
||||
).toEqual({
|
||||
actualHeight: 420,
|
||||
actualWidth: 840,
|
||||
height: 210,
|
||||
transform: 'translate(-60px, -30px) scale(1)',
|
||||
width: 420,
|
||||
});
|
||||
});
|
||||
|
||||
test('computes stacking canvas DOM attachment diffs when bypass toggles', () => {
|
||||
expect('getStackingCanvasAttachmentDiff' in canvasRendererModule).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
const getStackingCanvasAttachmentDiff = (
|
||||
canvasRendererModule as {
|
||||
getStackingCanvasAttachmentDiff: (params: {
|
||||
canvases: HTMLCanvasElement[];
|
||||
wasAttached: boolean;
|
||||
shouldAttach: boolean;
|
||||
}) => {
|
||||
added: HTMLCanvasElement[];
|
||||
removed: HTMLCanvasElement[];
|
||||
};
|
||||
}
|
||||
).getStackingCanvasAttachmentDiff;
|
||||
|
||||
const canvasA = document.createElement('canvas');
|
||||
const canvasB = document.createElement('canvas');
|
||||
const canvases = [canvasA, canvasB];
|
||||
|
||||
expect(
|
||||
getStackingCanvasAttachmentDiff({
|
||||
canvases,
|
||||
wasAttached: true,
|
||||
shouldAttach: false,
|
||||
})
|
||||
).toEqual({
|
||||
added: [],
|
||||
removed: canvases,
|
||||
});
|
||||
|
||||
expect(
|
||||
getStackingCanvasAttachmentDiff({
|
||||
canvases,
|
||||
wasAttached: false,
|
||||
shouldAttach: true,
|
||||
})
|
||||
).toEqual({
|
||||
added: canvases,
|
||||
removed: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
getStackingCanvasAttachmentDiff({
|
||||
canvases,
|
||||
wasAttached: true,
|
||||
shouldAttach: true,
|
||||
})
|
||||
).toEqual({
|
||||
added: [],
|
||||
removed: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('emits a lightweight zoom signal during gesture-skipped zoom updates so canvas budgets can shrink', () => {
|
||||
viewportRuntimeConfig.CANVAS_DPR_CAP_BY_ZOOM = [
|
||||
[0.5, 1],
|
||||
[0.8, 2],
|
||||
];
|
||||
|
||||
const viewport = new Viewport();
|
||||
viewport.SKIP_REFRESH_DURING_GESTURE = true;
|
||||
|
||||
const viewportUpdated = vi.fn();
|
||||
const zoomUpdates: Array<{ previousZoom: number; zoom: number }> = [];
|
||||
let lastCanvasBudgetZoom = viewport.zoom;
|
||||
let budgetSyncCount = 0;
|
||||
|
||||
viewport.viewportUpdated.subscribe(viewportUpdated);
|
||||
|
||||
expect('zoomUpdated' in viewport).toBe(true);
|
||||
const zoomUpdated = (
|
||||
viewport as unknown as {
|
||||
zoomUpdated: {
|
||||
subscribe: (
|
||||
callback: (update: { previousZoom: number; zoom: number }) => void
|
||||
) => void;
|
||||
};
|
||||
}
|
||||
).zoomUpdated;
|
||||
|
||||
zoomUpdated.subscribe(update => {
|
||||
zoomUpdates.push(update);
|
||||
if (
|
||||
(
|
||||
canvasRendererModule as {
|
||||
shouldSyncCanvasBudgetOnViewportUpdate: (
|
||||
previousZoom: number,
|
||||
nextZoom: number,
|
||||
rawDpr?: number
|
||||
) => boolean;
|
||||
}
|
||||
).shouldSyncCanvasBudgetOnViewportUpdate(
|
||||
lastCanvasBudgetZoom,
|
||||
update.zoom,
|
||||
2
|
||||
)
|
||||
) {
|
||||
budgetSyncCount += 1;
|
||||
}
|
||||
lastCanvasBudgetZoom = update.zoom;
|
||||
});
|
||||
|
||||
viewport.panning$.next(true);
|
||||
viewport.setZoom(0.4, { x: 0, y: 0 }, false, false, true);
|
||||
|
||||
expect(viewportUpdated).not.toHaveBeenCalled();
|
||||
expect(zoomUpdates).toEqual([{ previousZoom: 1, zoom: 0.4 }]);
|
||||
expect(budgetSyncCount).toBe(1);
|
||||
|
||||
viewport.dispose();
|
||||
});
|
||||
|
||||
test('keeps programmatic setZoom on the normal viewport update path in skip mode', () => {
|
||||
const viewport = new Viewport();
|
||||
viewport.SKIP_REFRESH_DURING_GESTURE = true;
|
||||
|
||||
const viewportUpdated = vi.fn();
|
||||
const zoomUpdated = vi.fn();
|
||||
|
||||
viewport.viewportUpdated.subscribe(viewportUpdated);
|
||||
viewport.zoomUpdated.subscribe(zoomUpdated);
|
||||
|
||||
viewport.setZoom(0.4, { x: 0, y: 0 });
|
||||
|
||||
expect(viewportUpdated).toHaveBeenCalledTimes(1);
|
||||
expect(zoomUpdated).toHaveBeenCalledWith({ previousZoom: 1, zoom: 0.4 });
|
||||
expect(viewport.panning$.value).toBe(false);
|
||||
expect(viewport.zooming$.value).toBe(false);
|
||||
|
||||
viewport.dispose();
|
||||
});
|
||||
|
||||
test('enables low-zoom block survival only while the gesture is still active', () => {
|
||||
expect('shouldUseLowZoomBlockSurvivalMode' in viewportElementModule).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
const shouldUseLowZoomBlockSurvivalMode = (
|
||||
viewportElementModule as {
|
||||
shouldUseLowZoomBlockSurvivalMode: (params: {
|
||||
zoom: number;
|
||||
skipRefreshDuringGesture: boolean;
|
||||
gestureActive: boolean;
|
||||
}) => boolean;
|
||||
}
|
||||
).shouldUseLowZoomBlockSurvivalMode;
|
||||
|
||||
expect(
|
||||
shouldUseLowZoomBlockSurvivalMode({
|
||||
zoom: 0.4,
|
||||
skipRefreshDuringGesture: true,
|
||||
gestureActive: true,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldUseLowZoomBlockSurvivalMode({
|
||||
zoom: 0.4,
|
||||
skipRefreshDuringGesture: true,
|
||||
gestureActive: false,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('keeps selected and one nearby viewport block active during low-zoom gesture survival', () => {
|
||||
expect('getLowZoomGestureActiveModels' in viewportElementModule).toBe(true);
|
||||
|
||||
const getLowZoomGestureActiveModels = (
|
||||
viewportElementModule as {
|
||||
getLowZoomGestureActiveModels: (params: {
|
||||
selectedModels: Set<{ id: string; elementBound: Bound }>;
|
||||
viewportModels: Set<{ id: string; elementBound: Bound }>;
|
||||
viewportBounds: Bound;
|
||||
nearbyActiveBlockLimit: number;
|
||||
nearbyDistanceRatio: number;
|
||||
}) => Set<{ id: string; elementBound: Bound }>;
|
||||
}
|
||||
).getLowZoomGestureActiveModels;
|
||||
|
||||
const selected = createFakeBlockModel('selected', 10, 10);
|
||||
const nearby = createFakeBlockModel('nearby', 28, 12);
|
||||
const far = createFakeBlockModel('far', 78, 78);
|
||||
|
||||
const activeModels = getLowZoomGestureActiveModels({
|
||||
selectedModels: new Set([selected]),
|
||||
viewportModels: new Set([selected, nearby, far]),
|
||||
viewportBounds: new Bound(0, 0, 100, 100),
|
||||
nearbyActiveBlockLimit: 1,
|
||||
nearbyDistanceRatio: 0.35,
|
||||
});
|
||||
|
||||
expect([...activeModels].map(model => model.id).sort()).toEqual([
|
||||
'nearby',
|
||||
'selected',
|
||||
]);
|
||||
});
|
||||
|
||||
test('falls back to the nearest viewport block when nothing is selected', () => {
|
||||
expect('getLowZoomGestureActiveModels' in viewportElementModule).toBe(true);
|
||||
|
||||
const getLowZoomGestureActiveModels = (
|
||||
viewportElementModule as {
|
||||
getLowZoomGestureActiveModels: (params: {
|
||||
selectedModels: Set<{ id: string; elementBound: Bound }>;
|
||||
viewportModels: Set<{ id: string; elementBound: Bound }>;
|
||||
viewportBounds: Bound;
|
||||
nearbyActiveBlockLimit: number;
|
||||
nearbyDistanceRatio: number;
|
||||
}) => Set<{ id: string; elementBound: Bound }>;
|
||||
}
|
||||
).getLowZoomGestureActiveModels;
|
||||
|
||||
const nearest = createFakeBlockModel('nearest', 46, 46);
|
||||
const farther = createFakeBlockModel('farther', 78, 78);
|
||||
|
||||
const activeModels = getLowZoomGestureActiveModels({
|
||||
selectedModels: new Set(),
|
||||
viewportModels: new Set([nearest, farther]),
|
||||
viewportBounds: new Bound(0, 0, 100, 100),
|
||||
nearbyActiveBlockLimit: 1,
|
||||
nearbyDistanceRatio: 0.35,
|
||||
});
|
||||
|
||||
expect([...activeModels].map(model => model.id)).toEqual(['nearest']);
|
||||
});
|
||||
|
||||
test('starts post-gesture recovery immediately once gesture signals fully settle', () => {
|
||||
expect('getPostGestureRecoveryDelay' in viewportModule).toBe(true);
|
||||
|
||||
const getPostGestureRecoveryDelay = (
|
||||
viewportModule as {
|
||||
getPostGestureRecoveryDelay: (params: {
|
||||
isPanning: boolean;
|
||||
isZooming: boolean;
|
||||
fallbackDelayMs: number;
|
||||
}) => number;
|
||||
}
|
||||
).getPostGestureRecoveryDelay;
|
||||
|
||||
expect(
|
||||
getPostGestureRecoveryDelay({
|
||||
isPanning: false,
|
||||
isZooming: false,
|
||||
fallbackDelayMs: 220,
|
||||
})
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('keeps fallback post-gesture delay while a gesture signal is still active', () => {
|
||||
expect('getPostGestureRecoveryDelay' in viewportModule).toBe(true);
|
||||
|
||||
const getPostGestureRecoveryDelay = (
|
||||
viewportModule as {
|
||||
getPostGestureRecoveryDelay: (params: {
|
||||
isPanning: boolean;
|
||||
isZooming: boolean;
|
||||
fallbackDelayMs: number;
|
||||
}) => number;
|
||||
}
|
||||
).getPostGestureRecoveryDelay;
|
||||
|
||||
expect(
|
||||
getPostGestureRecoveryDelay({
|
||||
isPanning: true,
|
||||
isZooming: false,
|
||||
fallbackDelayMs: 220,
|
||||
})
|
||||
).toBe(220);
|
||||
expect(
|
||||
getPostGestureRecoveryDelay({
|
||||
isPanning: false,
|
||||
isZooming: true,
|
||||
fallbackDelayMs: 220,
|
||||
})
|
||||
).toBe(220);
|
||||
});
|
||||
|
||||
test('sizes turbo renderer canvas with effective dpr at low zoom', () => {
|
||||
viewportRuntimeConfig.CANVAS_DPR_CAP_BY_ZOOM = [
|
||||
[0.5, 1],
|
||||
[0.8, 2],
|
||||
];
|
||||
setDevicePixelRatio(2);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const host = document.createElement('div');
|
||||
vi.spyOn(host, 'getBoundingClientRect').mockReturnValue(
|
||||
createRect(200, 100)
|
||||
);
|
||||
|
||||
(
|
||||
syncCanvasSize as unknown as (
|
||||
canvas: HTMLCanvasElement,
|
||||
host: HTMLElement,
|
||||
zoom: number
|
||||
) => void
|
||||
)(canvas, host, 0.4);
|
||||
|
||||
expect(canvas.width).toBe(200);
|
||||
expect(canvas.height).toBe(100);
|
||||
|
||||
(
|
||||
syncCanvasSize as unknown as (
|
||||
canvas: HTMLCanvasElement,
|
||||
host: HTMLElement,
|
||||
zoom: number
|
||||
) => void
|
||||
)(canvas, host, 0.95);
|
||||
|
||||
expect(canvas.width).toBe(400);
|
||||
expect(canvas.height).toBe(200);
|
||||
});
|
||||
|
||||
test('paints turbo placeholders with effective dpr at low zoom', () => {
|
||||
const previousTheme = document.documentElement.dataset.theme;
|
||||
document.documentElement.dataset.theme = 'light';
|
||||
|
||||
try {
|
||||
viewportRuntimeConfig.CANVAS_DPR_CAP_BY_ZOOM = [
|
||||
[0.5, 1],
|
||||
[0.8, 2],
|
||||
];
|
||||
setDevicePixelRatio(2);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const fillRect = vi.fn();
|
||||
const strokeRect = vi.fn();
|
||||
let fillStyle = '';
|
||||
let strokeStyle = '';
|
||||
vi.spyOn(canvas, 'getContext').mockReturnValue({
|
||||
get fillStyle() {
|
||||
return fillStyle;
|
||||
},
|
||||
set fillStyle(value: string) {
|
||||
fillStyle = value;
|
||||
},
|
||||
get strokeStyle() {
|
||||
return strokeStyle;
|
||||
},
|
||||
set strokeStyle(value: string) {
|
||||
strokeStyle = value;
|
||||
},
|
||||
fillRect,
|
||||
strokeRect,
|
||||
} as unknown as CanvasRenderingContext2D);
|
||||
|
||||
const layout: ViewportLayoutTree = {
|
||||
roots: [
|
||||
{
|
||||
blockId: 'root',
|
||||
type: 'affine:page',
|
||||
layout: {
|
||||
blockId: 'root',
|
||||
type: 'affine:page',
|
||||
rect: { x: 0, y: 0, w: 50, h: 20 },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
overallRect: { x: 0, y: 0, w: 50, h: 20 },
|
||||
};
|
||||
|
||||
const paintPlaceholderForTest =
|
||||
paintPlaceholder as unknown as PaintPlaceholderForTest;
|
||||
|
||||
paintPlaceholderForTest(canvas, layout, {
|
||||
zoom: 0.4,
|
||||
toViewCoord: () => [0, 0],
|
||||
});
|
||||
|
||||
expect(fillStyle).toBe('rgba(0, 0, 0, 0.04)');
|
||||
expect(strokeStyle).toBe('rgba(0, 0, 0, 0.02)');
|
||||
expect(fillRect).toHaveBeenLastCalledWith(0, 0, 20, 8);
|
||||
|
||||
paintPlaceholderForTest(canvas, layout, {
|
||||
zoom: 0.95,
|
||||
toViewCoord: () => [0, 0],
|
||||
});
|
||||
|
||||
expect(fillRect).toHaveBeenLastCalledWith(0, 0, 95, 38);
|
||||
} finally {
|
||||
document.documentElement.dataset.theme = previousTheme;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
import { ColorScheme } from '@blocksuite/affine-model';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
getAffinePlaceholderFillColor,
|
||||
getAffinePlaceholderStrokeColor,
|
||||
inferColorSchemeFromThemeMode,
|
||||
} from '../../../../shared/src/theme/placeholder-style.js';
|
||||
|
||||
describe('affine placeholder style', () => {
|
||||
it('returns subtle light placeholder colors', () => {
|
||||
expect(getAffinePlaceholderFillColor(ColorScheme.Light)).toBe(
|
||||
'rgba(0, 0, 0, 0.04)'
|
||||
);
|
||||
expect(getAffinePlaceholderStrokeColor(ColorScheme.Light)).toBe(
|
||||
'rgba(0, 0, 0, 0.02)'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns subtle dark placeholder colors', () => {
|
||||
expect(getAffinePlaceholderFillColor(ColorScheme.Dark)).toBe(
|
||||
'rgba(255, 255, 255, 0.08)'
|
||||
);
|
||||
expect(getAffinePlaceholderStrokeColor(ColorScheme.Dark)).toBe(
|
||||
'rgba(255, 255, 255, 0.04)'
|
||||
);
|
||||
});
|
||||
|
||||
it('infers color scheme from theme mode', () => {
|
||||
expect(inferColorSchemeFromThemeMode('dark')).toBe(ColorScheme.Dark);
|
||||
expect(inferColorSchemeFromThemeMode('light')).toBe(ColorScheme.Light);
|
||||
expect(inferColorSchemeFromThemeMode('')).toBe(ColorScheme.Light);
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import * as turboRendererModule from '../../../../gfx/turbo-renderer/src/turbo-renderer.js';
|
||||
|
||||
describe('viewport turbo renderer policy', () => {
|
||||
test.each([
|
||||
{ isIOS: true, zoom: 0.4, hasBitmap: true, expected: true },
|
||||
{ isIOS: true, zoom: 0.4, hasBitmap: false, expected: false },
|
||||
{ isIOS: false, zoom: 0.4, hasBitmap: true, expected: false },
|
||||
{ isIOS: true, zoom: 0.8, hasBitmap: true, expected: false },
|
||||
])(
|
||||
'prefers cached bitmap only for iOS low-zoom gestures with a bitmap %#',
|
||||
({ isIOS, zoom, hasBitmap, expected }) => {
|
||||
expect(
|
||||
'shouldPreferBitmapCacheDuringLowZoomGesture' in turboRendererModule
|
||||
).toBe(true);
|
||||
|
||||
const shouldPreferBitmapCacheDuringLowZoomGesture = (
|
||||
turboRendererModule as {
|
||||
shouldPreferBitmapCacheDuringLowZoomGesture: (params: {
|
||||
isIOS: boolean;
|
||||
zoom: number;
|
||||
hasBitmap: boolean;
|
||||
}) => boolean;
|
||||
}
|
||||
).shouldPreferBitmapCacheDuringLowZoomGesture;
|
||||
|
||||
expect(
|
||||
shouldPreferBitmapCacheDuringLowZoomGesture({
|
||||
isIOS,
|
||||
zoom,
|
||||
hasBitmap,
|
||||
})
|
||||
).toBe(expected);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
{ isIOS: true, zoom: 0.4, expected: false },
|
||||
{ isIOS: true, zoom: 0.8, expected: true },
|
||||
{ isIOS: false, zoom: 0.4, expected: true },
|
||||
])(
|
||||
'idles turbo blocks outside iOS low-zoom survival mode %#',
|
||||
({ isIOS, zoom, expected }) => {
|
||||
expect('shouldIdleTurboBlocksDuringZooming' in turboRendererModule).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
const shouldIdleTurboBlocksDuringZooming = (
|
||||
turboRendererModule as {
|
||||
shouldIdleTurboBlocksDuringZooming: (params: {
|
||||
isIOS: boolean;
|
||||
zoom: number;
|
||||
}) => boolean;
|
||||
}
|
||||
).shouldIdleTurboBlocksDuringZooming;
|
||||
|
||||
expect(
|
||||
shouldIdleTurboBlocksDuringZooming({
|
||||
isIOS,
|
||||
zoom,
|
||||
})
|
||||
).toBe(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1,3 +1,3 @@
|
||||
/* oxlint-disable no-restricted-imports */
|
||||
/* eslint-disable @typescript-eslint/no-restricted-imports */
|
||||
|
||||
export * from '@blocksuite/store';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
@@ -9,13 +7,11 @@ export default defineConfig({
|
||||
},
|
||||
plugins: [vanillaExtractPlugin()],
|
||||
test: {
|
||||
globalSetup: fileURLToPath(
|
||||
new URL('../../../scripts/vitest-global.js', import.meta.url)
|
||||
),
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
provider: 'istanbul',
|
||||
provider: 'istanbul', // or 'c8'
|
||||
reporter: ['lcov'],
|
||||
reportsDirectory: '../../../.coverage/blocksuite-affine',
|
||||
},
|
||||
|
||||
@@ -21,10 +21,13 @@
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"file-type": "^21.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2"
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -37,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
import {
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
createAttachmentBlockSnapshot,
|
||||
FOOTNOTE_DEFINITION_PREFIX,
|
||||
getFootnoteDefinitionText,
|
||||
isFootnoteDefinitionNode,
|
||||
@@ -57,15 +56,18 @@ export const attachmentBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher
|
||||
}
|
||||
walkerContext
|
||||
.openNode(
|
||||
createAttachmentBlockSnapshot({
|
||||
{
|
||||
type: 'block',
|
||||
id: nanoid(),
|
||||
flavour: AttachmentBlockSchema.model.flavour,
|
||||
props: {
|
||||
name: fileName,
|
||||
sourceId: blobId,
|
||||
footnoteIdentifier,
|
||||
style: 'citation',
|
||||
},
|
||||
}),
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
)
|
||||
.closeNode();
|
||||
|
||||
@@ -54,7 +54,7 @@ type AttachmentResolvedStateInfo = ResolvedStateInfo & {
|
||||
|
||||
@Peekable({
|
||||
enableOn: ({ model }: AttachmentBlockComponent) => {
|
||||
return model.props.type.endsWith('pdf');
|
||||
return !model.store.readonly && model.props.type.endsWith('pdf');
|
||||
},
|
||||
})
|
||||
export class AttachmentBlockComponent extends CaptionedBlockComponent<AttachmentBlockModel> {
|
||||
|
||||
@@ -144,7 +144,7 @@ export const attachmentViewDropdownMenu = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions.value}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${viewType$}
|
||||
.viewType$=${viewType$}
|
||||
></affine-view-dropdown-menu>`;
|
||||
},
|
||||
} as const satisfies ToolbarActionGroup<ToolbarAction>;
|
||||
@@ -366,7 +366,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.styleSignal=${style$}
|
||||
.style$=${style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -22,16 +22,16 @@
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27"
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/browser-playwright": "^4.1.8",
|
||||
"playwright": "=1.58.2",
|
||||
"vitest": "^4.1.8"
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -108,9 +108,7 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
}
|
||||
|
||||
open = () => {
|
||||
const link = this.link;
|
||||
if (!link) return;
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
window.open(this.link, '_blank');
|
||||
};
|
||||
|
||||
refreshData = () => {
|
||||
|
||||
@@ -237,7 +237,7 @@ const builtinToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${viewType$}
|
||||
.viewType$=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -282,7 +282,7 @@ const builtinToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.styleSignal=${model.props.style$}
|
||||
.style$=${model.props.style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -472,7 +472,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${viewType$}
|
||||
.viewType$=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -534,7 +534,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.styleSignal=${style$}
|
||||
.style$=${style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -591,7 +591,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.sizeSignal=${scale$}
|
||||
.size$=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -9,9 +8,10 @@ export default defineConfig({
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
instances: [{ browser: 'chromium' }],
|
||||
provider: playwright(),
|
||||
name: 'chromium',
|
||||
provider: 'playwright',
|
||||
isolate: false,
|
||||
providerOptions: {},
|
||||
},
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 500,
|
||||
|
||||
@@ -17,13 +17,21 @@
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"lit": "^3.2.0"
|
||||
"@types/mdast": "^4.0.4",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -36,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -25,4 +25,4 @@ export const calloutTooltip = html`<svg width="170" height="106" viewBox="0 0 17
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
`;
|
||||
`
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../widgets/slash-menu" },
|
||||
{ "path": "../../../framework/global" },
|
||||
{ "path": "../../../framework/std" },
|
||||
{ "path": "../../../framework/store" }
|
||||
]
|
||||
|
||||
@@ -26,9 +26,12 @@
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"shiki": "^3.19.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
@@ -44,5 +47,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { deleteTextCommand } from '@blocksuite/affine-inline-preset';
|
||||
import type { RichText } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
HtmlAdapter,
|
||||
pasteMiddleware,
|
||||
@@ -19,7 +18,6 @@ import {
|
||||
LifeCycleWatcher,
|
||||
LifeCycleWatcherIdentifier,
|
||||
StdIdentifier,
|
||||
TextSelection,
|
||||
type UIEventHandler,
|
||||
} from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
@@ -105,30 +103,6 @@ export class CodeBlockClipboardController extends LifeCycleWatcher {
|
||||
const e = ctx.get('clipboardState').raw;
|
||||
e.preventDefault();
|
||||
|
||||
const textSelection = this.std.selection.find(TextSelection);
|
||||
const plainText = e.clipboardData
|
||||
?.getData('text/plain')
|
||||
?.replace(/\r?\n|\r/g, '\n');
|
||||
const selectedBlockId = textSelection?.from.blockId;
|
||||
const codeBlock = selectedBlockId
|
||||
? this.std.store.getBlock(selectedBlockId)?.model
|
||||
: null;
|
||||
if (plainText && codeBlock?.flavour === 'affine:code' && selectedBlockId) {
|
||||
const richText = this.std.view
|
||||
.getBlock(selectedBlockId)
|
||||
?.querySelector<RichText>('rich-text');
|
||||
const inlineEditor = richText?.inlineEditor;
|
||||
const inlineRange = inlineEditor?.getInlineRange();
|
||||
if (inlineEditor && inlineRange) {
|
||||
inlineEditor.insertText(inlineRange, plainText);
|
||||
inlineEditor.setInlineRange({
|
||||
index: inlineRange.index + plainText.length,
|
||||
length: 0,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.std.store.captureSync();
|
||||
this.std.command
|
||||
.chain()
|
||||
|
||||
@@ -39,7 +39,10 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
private readonly _loadTheme = async (
|
||||
highlighter: HighlighterCore
|
||||
): Promise<void> => {
|
||||
if (!CodeBlockHighlighter._isHighlighterInUse(highlighter)) {
|
||||
// It is possible that by the time the highlighter is ready all instances
|
||||
// have already been unmounted. In that case there is no need to load
|
||||
// themes or update state.
|
||||
if (CodeBlockHighlighter._refCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,17 +51,7 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
const lightTheme = config?.theme?.light ?? CODE_BLOCK_DEFAULT_LIGHT_THEME;
|
||||
this._darkThemeKey = (await normalizeGetter(darkTheme)).name;
|
||||
this._lightThemeKey = (await normalizeGetter(lightTheme)).name;
|
||||
|
||||
if (!CodeBlockHighlighter._isHighlighterInUse(highlighter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await highlighter.loadTheme(darkTheme, lightTheme);
|
||||
|
||||
if (!CodeBlockHighlighter._isHighlighterInUse(highlighter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.highlighter$.value = highlighter;
|
||||
};
|
||||
|
||||
@@ -90,18 +83,30 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
}
|
||||
|
||||
override unmounted(): void {
|
||||
CodeBlockHighlighter._refCount = Math.max(
|
||||
0,
|
||||
CodeBlockHighlighter._refCount - 1
|
||||
);
|
||||
this.highlighter$.value = null;
|
||||
}
|
||||
CodeBlockHighlighter._refCount--;
|
||||
|
||||
private static _isHighlighterInUse(highlighter: HighlighterCore) {
|
||||
return (
|
||||
CodeBlockHighlighter._refCount > 0 &&
|
||||
CodeBlockHighlighter._sharedHighlighter === highlighter
|
||||
);
|
||||
// Dispose the shared highlighter **after** any in-flight creation finishes.
|
||||
if (CodeBlockHighlighter._refCount !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doDispose = (highlighter: HighlighterCore | null) => {
|
||||
if (highlighter) {
|
||||
highlighter.dispose();
|
||||
}
|
||||
CodeBlockHighlighter._sharedHighlighter = null;
|
||||
CodeBlockHighlighter._highlighterPromise = null;
|
||||
};
|
||||
|
||||
if (CodeBlockHighlighter._sharedHighlighter) {
|
||||
// Highlighter already created – dispose immediately.
|
||||
doDispose(CodeBlockHighlighter._sharedHighlighter);
|
||||
} else if (CodeBlockHighlighter._highlighterPromise) {
|
||||
// Highlighter still being created – wait for it, then dispose.
|
||||
CodeBlockHighlighter._highlighterPromise
|
||||
.then(doDispose)
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,10 +51,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
return modelPreview;
|
||||
});
|
||||
|
||||
collapsed$: Signal<boolean> = computed(
|
||||
() => !!this.model.props.collapsed$.value
|
||||
);
|
||||
|
||||
highlightTokens$: Signal<ThemedToken[][]> = signal([]);
|
||||
|
||||
languageName$: Signal<string> = computed(() => {
|
||||
@@ -421,7 +417,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
CodeBlockPreviewIdentifier(this.model.props.language ?? '')
|
||||
);
|
||||
const shouldRenderPreview = preview && previewContext;
|
||||
const collapsed = this.collapsed$.value;
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -431,7 +426,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
mobile: IS_MOBILE,
|
||||
wrap: this.model.props.wrap,
|
||||
'disable-line-numbers': !showLineNumbers,
|
||||
collapsed,
|
||||
})}
|
||||
>
|
||||
<rich-text
|
||||
@@ -459,12 +453,9 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
}}
|
||||
>
|
||||
</rich-text>
|
||||
${collapsed
|
||||
? html`<div class="code-collapsed-fade" aria-hidden="true"></div>`
|
||||
: nothing}
|
||||
<div
|
||||
style=${styleMap({
|
||||
display: shouldRenderPreview && !collapsed ? undefined : 'none',
|
||||
display: shouldRenderPreview ? undefined : 'none',
|
||||
})}
|
||||
contenteditable="false"
|
||||
class="affine-code-block-preview"
|
||||
@@ -480,10 +471,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
this.store.updateBlock(this.model, { wrap });
|
||||
}
|
||||
|
||||
setCollapsed(collapsed: boolean) {
|
||||
this.store.updateBlock(this.model, { collapsed });
|
||||
}
|
||||
|
||||
@query('rich-text')
|
||||
private accessor _richTextElement: RichText | null = null;
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { noop } from '@blocksuite/global/utils';
|
||||
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { effect } from '@preact/signals-core';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { CodeBlockToolbarContext } from '../context.js';
|
||||
|
||||
@@ -82,10 +82,18 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
|
||||
|
||||
createLitPortal({
|
||||
template: html`
|
||||
<affine-code-more-menu
|
||||
.context=${this.context}
|
||||
.moreGroups=${this.moreGroups}
|
||||
></affine-code-more-menu>
|
||||
<editor-menu-content
|
||||
data-show
|
||||
class="more-popup-menu"
|
||||
style=${styleMap({
|
||||
'--content-padding': '8px',
|
||||
'--packed-height': '4px',
|
||||
})}
|
||||
>
|
||||
<div data-size="large" data-orientation="vertical">
|
||||
${renderGroups(this.moreGroups, this.context)}
|
||||
</div>
|
||||
</editor-menu-content>
|
||||
`,
|
||||
// should be greater than block-selection z-index as selection and popover wil share the same stacking context(editor-host)
|
||||
portalStyles: {
|
||||
@@ -109,17 +117,6 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
|
||||
this.closeCurrentMenu();
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Mirror the collapsed$ signal from the block component into local @state
|
||||
// so this LitElement re-renders when it changes.
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
this._collapsed = this.context.blockComponent.collapsed$.value;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<editor-toolbar class="code-toolbar-container" data-without-bg>
|
||||
@@ -148,9 +145,6 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
|
||||
@state()
|
||||
private accessor _moreMenuOpen = false;
|
||||
|
||||
@state()
|
||||
private accessor _collapsed = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor context!: CodeBlockToolbarContext;
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
|
||||
import { renderGroups } from '@blocksuite/affine-components/toolbar';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { CodeBlockToolbarContext } from '../context.js';
|
||||
|
||||
export class AffineCodeMoreMenu extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
override firstUpdated() {
|
||||
this.disposables.add(
|
||||
this.context.blockComponent.model.propsUpdated.subscribe(({ key }) => {
|
||||
if (key === 'wrap' || key === 'lineNumber') {
|
||||
this.requestUpdate();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<editor-menu-content
|
||||
data-show
|
||||
class="more-popup-menu"
|
||||
style=${styleMap({
|
||||
'--content-padding': '8px',
|
||||
'--packed-height': '4px',
|
||||
})}
|
||||
>
|
||||
<div data-size="large" data-orientation="vertical">
|
||||
${renderGroups(this.moreGroups, this.context)}
|
||||
</div>
|
||||
</editor-menu-content>
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor context!: CodeBlockToolbarContext;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor moreGroups!: MenuItemGroup<CodeBlockToolbarContext>[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-code-more-menu': AffineCodeMoreMenu;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
CancelWrapIcon,
|
||||
CaptionIcon,
|
||||
CollapseCodeIcon,
|
||||
CopyIcon,
|
||||
DeleteIcon,
|
||||
DuplicateIcon,
|
||||
ExpandCodeIcon,
|
||||
WrapIcon,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
|
||||
@@ -87,38 +85,6 @@ export const PRIMARY_GROUPS: MenuItemGroup<CodeBlockToolbarContext>[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'collapse',
|
||||
when: ({ doc }) => !doc.readonly,
|
||||
generate: ({ blockComponent }) => {
|
||||
return {
|
||||
action: () => {
|
||||
blockComponent.setCollapsed(!blockComponent.collapsed$.value);
|
||||
},
|
||||
render: item => {
|
||||
const collapsed = blockComponent.collapsed$.value;
|
||||
const icon = collapsed ? ExpandCodeIcon : CollapseCodeIcon;
|
||||
const label = collapsed ? 'Expand code' : 'Collapse code';
|
||||
return html`
|
||||
<editor-icon-button
|
||||
class="code-toolbar-button collapse"
|
||||
aria-label=${label}
|
||||
.tooltip=${label}
|
||||
.tooltipOffset=${4}
|
||||
.iconSize=${'16px'}
|
||||
.iconContainerPadding=${4}
|
||||
@click=${(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
item.action();
|
||||
}}
|
||||
>
|
||||
${icon}
|
||||
</editor-icon-button>
|
||||
`;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'caption',
|
||||
label: 'Caption',
|
||||
@@ -208,8 +174,7 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
const currentWrap = blockComponent.model.props.wrap;
|
||||
blockComponent.setWrap(!currentWrap);
|
||||
blockComponent.setWrap(!wrapped);
|
||||
}}
|
||||
aria-label=${label}
|
||||
>
|
||||
@@ -239,10 +204,8 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
const currentLineNumber =
|
||||
blockComponent.model.props.lineNumber ?? true;
|
||||
blockComponent.store.updateBlock(blockComponent.model, {
|
||||
lineNumber: !currentLineNumber,
|
||||
lineNumber: !lineNumber,
|
||||
});
|
||||
}}
|
||||
aria-label=${label}
|
||||
|
||||
@@ -5,14 +5,12 @@ import {
|
||||
} from './code-toolbar';
|
||||
import { AffineCodeToolbar } from './code-toolbar/components/code-toolbar';
|
||||
import { LanguageListButton } from './code-toolbar/components/lang-button';
|
||||
import { AffineCodeMoreMenu } from './code-toolbar/components/more-menu';
|
||||
import { PreviewButton } from './code-toolbar/components/preview-button';
|
||||
import { AffineCodeUnit } from './highlight/affine-code-unit';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('language-list-button', LanguageListButton);
|
||||
customElements.define('affine-code-toolbar', AffineCodeToolbar);
|
||||
customElements.define('affine-code-more-menu', AffineCodeMoreMenu);
|
||||
customElements.define(AFFINE_CODE_TOOLBAR_WIDGET, AffineCodeToolbarWidget);
|
||||
customElements.define('affine-code-unit', AffineCodeUnit);
|
||||
customElements.define('affine-code', CodeBlockComponent);
|
||||
@@ -23,7 +21,6 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'language-list-button': LanguageListButton;
|
||||
'affine-code-toolbar': AffineCodeToolbar;
|
||||
'affine-code-more-menu': AffineCodeMoreMenu;
|
||||
'preview-button': PreviewButton;
|
||||
[AFFINE_CODE_TOOLBAR_WIDGET]: AffineCodeToolbarWidget;
|
||||
}
|
||||
|
||||
@@ -45,10 +45,8 @@ export class AffineCodeUnit extends ShadowlessElement {
|
||||
if (!codeBlock || !vElement) return plainContent;
|
||||
const tokens = codeBlock.highlightTokens$.value;
|
||||
if (tokens.length === 0) return plainContent;
|
||||
const line = tokens[vElement.lineIndex];
|
||||
if (!line) return plainContent;
|
||||
// copy the tokens to avoid modifying the original tokens
|
||||
const lineTokens = structuredClone(line);
|
||||
const lineTokens = structuredClone(tokens[vElement.lineIndex]);
|
||||
if (lineTokens.length === 0) return plainContent;
|
||||
|
||||
const startOffset = vElement.startOffset;
|
||||
|
||||
@@ -80,35 +80,4 @@ export const codeBlockStyles = css`
|
||||
affine-code .affine-code-block-preview {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* ── Collapsed state ──────────────────────────────────────────────── */
|
||||
|
||||
/* Clamp the rich-text to the first 8 lines */
|
||||
.affine-code-block-container.collapsed rich-text {
|
||||
display: block;
|
||||
max-height: calc(8 * var(--affine-line-height));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Reduce bottom padding so the fade sits flush with the border */
|
||||
.affine-code-block-container.collapsed {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Gradient overlay that fades to the block background */
|
||||
.affine-code-block-container .code-collapsed-fade {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 80px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
var(--affine-background-code-block)
|
||||
);
|
||||
border-radius: 0 0 10px 10px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -21,9 +21,14 @@
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2"
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -36,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -254,7 +254,6 @@ export class DataViewBlockComponent extends CaptionedBlockComponent<DataViewBloc
|
||||
dataSource: this.dataSource,
|
||||
headerWidget: this.headerWidget,
|
||||
clipboard: this.std.clipboard,
|
||||
dnd: this.std.dnd,
|
||||
notification: {
|
||||
toast: message => {
|
||||
const notification = this.std.getOptional(NotificationProvider);
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@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",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
@@ -45,5 +47,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -54,9 +54,9 @@ type Cell = {
|
||||
value: string | { delta: DeltaInsert[] };
|
||||
};
|
||||
export const processTable = (
|
||||
columns: ColumnDataType[] = [],
|
||||
children: BlockSnapshot[] = [],
|
||||
cells: SerializedCells = {}
|
||||
columns: ColumnDataType[],
|
||||
children: BlockSnapshot[],
|
||||
cells: SerializedCells
|
||||
): Table => {
|
||||
const table: Table = {
|
||||
headers: columns,
|
||||
@@ -90,17 +90,13 @@ export const processTable = (
|
||||
return;
|
||||
}
|
||||
let value: string | { delta: DeltaInsert[] };
|
||||
try {
|
||||
if (isDelta(cell.value)) {
|
||||
value = cell.value;
|
||||
} else {
|
||||
value = property.config.rawValue.toString({
|
||||
value: cell.value,
|
||||
data: col.data,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
value = '';
|
||||
if (isDelta(cell.value)) {
|
||||
value = cell.value;
|
||||
} else {
|
||||
value = property.config.rawValue.toString({
|
||||
value: cell.value,
|
||||
data: col.data,
|
||||
});
|
||||
}
|
||||
row.cells.push({
|
||||
value,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { viewPresets } from '@blocksuite/data-view/view-presets';
|
||||
import {
|
||||
DatabaseKanbanViewIcon,
|
||||
DatabaseTableViewIcon,
|
||||
TodayIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
|
||||
import { insertDatabaseBlockCommand } from '../commands';
|
||||
@@ -48,35 +47,6 @@ export const databaseSlashMenuConfig: SlashMenuConfig = {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Calendar View',
|
||||
description: 'Display items by date in a calendar.',
|
||||
searchAlias: ['database', 'calendar'],
|
||||
icon: TodayIcon(),
|
||||
group: '7_Database@1',
|
||||
when: ({ model }) =>
|
||||
!isInsideBlockByFlavour(model.store, model, 'affine:edgeless-text'),
|
||||
action: ({ std }) => {
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(getSelectedModelsCommand)
|
||||
.pipe(insertDatabaseBlockCommand, {
|
||||
viewType: viewPresets.calendarViewMeta.type,
|
||||
place: 'after',
|
||||
removeEmptyLine: true,
|
||||
})
|
||||
.pipe(({ insertedDatabaseBlockId }) => {
|
||||
if (insertedDatabaseBlockId) {
|
||||
const telemetry = std.getOptional(TelemetryProvider);
|
||||
telemetry?.track('BlockCreated', {
|
||||
blockType: 'affine:database',
|
||||
});
|
||||
}
|
||||
})
|
||||
.run();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Kanban View',
|
||||
description: 'Visualize data in a dashboard.',
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
type SingleView,
|
||||
uniMap,
|
||||
} from '@blocksuite/data-view';
|
||||
import { CalendarExternalSourceProvider } from '@blocksuite/data-view/view-presets';
|
||||
import { widgetPresets } from '@blocksuite/data-view/widget-presets';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { Rect } from '@blocksuite/global/gfx';
|
||||
@@ -151,14 +150,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
|
||||
config
|
||||
);
|
||||
});
|
||||
this.std.provider
|
||||
.getAll(CalendarExternalSourceProvider)
|
||||
.forEach(source => {
|
||||
dataSource.serviceSet(
|
||||
CalendarExternalSourceProvider(source.id),
|
||||
source
|
||||
);
|
||||
});
|
||||
});
|
||||
const id = currentViewStorage.getCurrentView(this.model.id);
|
||||
if (id && dataSource.viewManager.viewGet(id)) {
|
||||
@@ -302,12 +293,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
|
||||
widgetPresets.tools.viewOptions,
|
||||
widgetPresets.tools.tableAddRow,
|
||||
],
|
||||
calendar: [
|
||||
widgetPresets.tools.filter,
|
||||
widgetPresets.tools.search,
|
||||
widgetPresets.tools.viewOptions,
|
||||
widgetPresets.tools.tableAddRow,
|
||||
],
|
||||
});
|
||||
|
||||
private readonly viewSelection$ = computed(() => {
|
||||
@@ -442,7 +427,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
|
||||
headerWidget: this.headerWidget,
|
||||
onDrag: this.onDrag,
|
||||
clipboard: this.std.clipboard,
|
||||
dnd: this.std.dnd,
|
||||
notification: {
|
||||
toast: message => {
|
||||
const notification = this.std.getOptional(NotificationProvider);
|
||||
|
||||
@@ -47,7 +47,7 @@ export const databasePropertyConverts = [
|
||||
propertyModelPresets.multiSelectPropertyModelConfig,
|
||||
(_property, cells) => {
|
||||
const options: Record<string, SelectTag> = {};
|
||||
// oxlint-disable-next-line sonarjs/no-identical-functions
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
const getTag = (name: string) => {
|
||||
if (options[name]) return options[name];
|
||||
const tag: SelectTag = {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { viewConverts, viewPresets } from '@blocksuite/data-view/view-presets';
|
||||
export const databaseBlockViews: ViewMeta[] = [
|
||||
viewPresets.tableViewMeta,
|
||||
viewPresets.kanbanViewMeta,
|
||||
viewPresets.calendarViewMeta,
|
||||
];
|
||||
|
||||
export const databaseBlockViewMap = Object.fromEntries(
|
||||
|
||||
@@ -15,10 +15,17 @@
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0"
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -31,5 +38,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../../framework/global" },
|
||||
{ "path": "../../../framework/std" },
|
||||
{ "path": "../../../framework/store" }
|
||||
]
|
||||
|
||||
@@ -11,17 +11,25 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-block-surface": "workspace:*",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-gfx-text": "workspace:*",
|
||||
"@blocksuite/affine-inline-preset": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"lit": "^3.2.0"
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -34,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -43,11 +43,6 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
font-weight: var(--edgeless-text-font-weight);
|
||||
text-align: var(--edgeless-text-text-align);
|
||||
}
|
||||
|
||||
.edgeless-text-block-container .locked-content a[href] {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _resizeObserver = new ResizeObserver(() => {
|
||||
@@ -309,7 +304,6 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
style=${styleMap(containerStyle)}
|
||||
>
|
||||
<div
|
||||
class=${!editing && this.model.isLocked() ? 'locked-content' : ''}
|
||||
style=${styleMap({
|
||||
pointerEvents: editing ? 'auto' : 'none',
|
||||
userSelect: editing ? 'auto' : 'none',
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
"include": ["./src"],
|
||||
"references": [
|
||||
{ "path": "../surface" },
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../ext-loader" },
|
||||
{ "path": "../../gfx/text" },
|
||||
{ "path": "../../inlines/preset" },
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../widgets/edgeless-toolbar" },
|
||||
{ "path": "../../../framework/global" },
|
||||
{ "path": "../../../framework/std" },
|
||||
{ "path": "../../../framework/store" }
|
||||
|
||||
@@ -23,12 +23,19 @@
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.23",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27"
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -41,5 +48,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function renderLinkedDocInCard(
|
||||
return;
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line sonarjs/no-collapsible-if
|
||||
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||
if ('bannerContainer' in card) {
|
||||
if (card.editorMode === 'page') {
|
||||
renderPageAsBanner(card).catch(e => {
|
||||
|
||||
@@ -247,7 +247,7 @@ const conversionsActionGroup = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${viewType$}
|
||||
.viewType$=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -299,7 +299,7 @@ const builtinToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.styleSignal=${model.props.style$}
|
||||
.style$=${model.props.style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -423,7 +423,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.styleSignal=${style$}
|
||||
.style$=${style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -479,7 +479,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.sizeSignal=${scale$}
|
||||
.size$=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -199,7 +199,7 @@ const conversionsActionGroup = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${viewType$}
|
||||
.viewType$=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -466,7 +466,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.sizeSignal=${scale$}
|
||||
.size$=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
"@blocksuite/affine-block-surface": "workspace:*",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-gfx-pointer": "workspace:*",
|
||||
"@blocksuite/affine-inline-reference": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
@@ -21,10 +24,18 @@
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"lit": "^3.2.0",
|
||||
"yjs": "^13.6.27"
|
||||
"lodash-es": "^4.17.23",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -37,5 +48,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.27.0"
|
||||
"version": "0.26.3"
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ function createBuiltinToolbarConfigForExternal(
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${viewType$}
|
||||
.viewType$=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -329,7 +329,7 @@ function createBuiltinToolbarConfigForExternal(
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.styleSignal=${model.props.style$}
|
||||
.style$=${model.props.style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -514,7 +514,7 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${viewType$}
|
||||
.viewType$=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -579,7 +579,7 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.styleSignal=${style$}
|
||||
.style$=${style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -646,7 +646,7 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.sizeSignal=${scale$}
|
||||
.size$=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -117,7 +117,7 @@ export class EmbedGithubBlockComponent extends EmbedBlockComponent<
|
||||
|
||||
override renderBlock() {
|
||||
const {
|
||||
title,
|
||||
title = 'GitHub',
|
||||
githubType,
|
||||
status,
|
||||
statusReason,
|
||||
@@ -139,7 +139,7 @@ export class EmbedGithubBlockComponent extends EmbedBlockComponent<
|
||||
? getGithubStatusIcon(githubType, status, statusReason)
|
||||
: nothing;
|
||||
const statusText = loading ? '' : status;
|
||||
const titleText = loading ? 'Loading...' : title || 'GitHub';
|
||||
const titleText = loading ? 'Loading...' : title;
|
||||
const descriptionText = loading ? '' : description;
|
||||
const bannerImage =
|
||||
!loading && image
|
||||
|
||||
+4
-22
@@ -35,7 +35,7 @@ const extractBvid = (url: string) => {
|
||||
|
||||
const buildBiliPlayerEmbedUrl = (url: string) => {
|
||||
// If the user pasted the embed URL directly, keep it
|
||||
if (isValidBiliPlayerUrl(url)) {
|
||||
if (validateEmbedIframeUrl(url, biliPlayerValidationOptions)) {
|
||||
return url;
|
||||
}
|
||||
const avid = extractAvid(url);
|
||||
@@ -57,31 +57,13 @@ const buildBiliPlayerEmbedUrl = (url: string) => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
function isValidBiliPlayerUrl(url: string) {
|
||||
try {
|
||||
if (!validateEmbedIframeUrl(url, biliPlayerValidationOptions)) {
|
||||
return false;
|
||||
}
|
||||
const parsedUrl = new URL(url);
|
||||
return (
|
||||
parsedUrl.pathname === '/player.html' &&
|
||||
(!!parsedUrl.searchParams.get('aid') ||
|
||||
!!parsedUrl.searchParams.get('bvid'))
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const bilibiliConfig = {
|
||||
const bilibiliConfig = {
|
||||
name: 'bilibili',
|
||||
match: (url: string) =>
|
||||
isValidBiliPlayerUrl(url) ||
|
||||
(validateEmbedIframeUrl(url, bilibiliValidationOptions) &&
|
||||
(!!extractAvid(url) || !!extractBvid(url))),
|
||||
validateEmbedIframeUrl(url, bilibiliValidationOptions) &&
|
||||
(!!extractAvid(url) || !!extractBvid(url)),
|
||||
buildOEmbedUrl: buildBiliPlayerEmbedUrl,
|
||||
useOEmbedUrlDirectly: true,
|
||||
validateIframeUrl: (iframeUrl: string) => isValidBiliPlayerUrl(iframeUrl),
|
||||
options: {
|
||||
widthInSurface: BILIBILI_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: BILIBILI_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
|
||||
+1
-3
@@ -15,7 +15,7 @@ const excalidrawUrlValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
hostnames: ['excalidraw.com'],
|
||||
};
|
||||
|
||||
export const excalidrawConfig = {
|
||||
const excalidrawConfig = {
|
||||
name: 'excalidraw',
|
||||
match: (url: string) =>
|
||||
validateEmbedIframeUrl(url, excalidrawUrlValidationOptions),
|
||||
@@ -27,8 +27,6 @@ export const excalidrawConfig = {
|
||||
return url;
|
||||
},
|
||||
useOEmbedUrlDirectly: true,
|
||||
validateIframeUrl: (iframeUrl: string) =>
|
||||
validateEmbedIframeUrl(iframeUrl, excalidrawUrlValidationOptions),
|
||||
options: {
|
||||
widthInSurface: EXCALIDRAW_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: EXCALIDRAW_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { EmbedIframeConfigExtension } from '@blocksuite/affine-shared/services';
|
||||
|
||||
import {
|
||||
type EmbedIframeUrlValidationOptions,
|
||||
validateEmbedIframeUrl,
|
||||
} from '../../utils';
|
||||
|
||||
const GENERIC_DEFAULT_WIDTH_IN_SURFACE = 800;
|
||||
const GENERIC_DEFAULT_HEIGHT_IN_SURFACE = 600;
|
||||
const GENERIC_DEFAULT_WIDTH_PERCENT = 100;
|
||||
@@ -22,11 +17,6 @@ const AFFINE_DOMAINS = [
|
||||
'apple.getaffineapp.com', // Cloud domain for Apple app
|
||||
];
|
||||
|
||||
const genericUrlValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
protocols: ['https:'],
|
||||
hostnames: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates if a URL is suitable for generic iframe embedding
|
||||
* Allows HTTPS URLs but excludes AFFiNE domains
|
||||
@@ -37,12 +27,8 @@ function isValidGenericEmbedUrl(url: string): boolean {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
if (
|
||||
!validateEmbedIframeUrl(url, {
|
||||
...genericUrlValidationOptions,
|
||||
hostnames: [parsedUrl.hostname],
|
||||
})
|
||||
) {
|
||||
// Only allow HTTPS for security
|
||||
if (parsedUrl.protocol !== 'https:') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -63,7 +49,7 @@ function isValidGenericEmbedUrl(url: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export const genericConfig = {
|
||||
const genericConfig = {
|
||||
name: 'generic',
|
||||
match: (url: string) => isValidGenericEmbedUrl(url),
|
||||
buildOEmbedUrl: (url: string) => {
|
||||
@@ -73,7 +59,6 @@ export const genericConfig = {
|
||||
return url;
|
||||
},
|
||||
useOEmbedUrlDirectly: true,
|
||||
validateIframeUrl: (iframeUrl: string) => isValidGenericEmbedUrl(iframeUrl),
|
||||
options: {
|
||||
widthInSurface: GENERIC_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: GENERIC_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
|
||||
+1
-2
@@ -57,7 +57,7 @@ function isValidGoogleDocsUrl(url: string, strictMode = true): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export const googleDocsConfig = {
|
||||
const googleDocsConfig = {
|
||||
name: 'google-docs',
|
||||
match: (url: string) => isValidGoogleDocsUrl(url),
|
||||
buildOEmbedUrl: (url: string) => {
|
||||
@@ -67,7 +67,6 @@ export const googleDocsConfig = {
|
||||
return url;
|
||||
},
|
||||
useOEmbedUrlDirectly: true,
|
||||
validateIframeUrl: (iframeUrl: string) => isValidGoogleDocsUrl(iframeUrl),
|
||||
options: {
|
||||
widthInSurface: GOOGLE_DOCS_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: GOOGLE_DOCS_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
|
||||
+1
-26
@@ -113,29 +113,6 @@ function isValidGoogleDriveUrl(url: string, strictMode = true): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function isValidGoogleDriveIframeUrl(url: string): boolean {
|
||||
try {
|
||||
if (!validateEmbedIframeUrl(url, googleDriveUrlValidationOptions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(url);
|
||||
const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);
|
||||
|
||||
if (isValidGoogleDriveFileUrl(parsedUrl)) {
|
||||
return pathSegments[3] === 'preview';
|
||||
}
|
||||
|
||||
return (
|
||||
parsedUrl.pathname === '/embeddedfolderview' &&
|
||||
!!parsedUrl.searchParams.get('id')
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn('Invalid Google Drive iframe URL:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build embed URL for Google Drive files
|
||||
* @param fileId File ID
|
||||
@@ -194,7 +171,7 @@ function buildGoogleDriveEmbedUrl(url: string): string | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
export const googleDriveConfig = {
|
||||
const googleDriveConfig = {
|
||||
name: 'google-drive',
|
||||
match: (url: string) => isValidGoogleDriveUrl(url),
|
||||
buildOEmbedUrl: (url: string) => {
|
||||
@@ -206,8 +183,6 @@ export const googleDriveConfig = {
|
||||
return buildGoogleDriveEmbedUrl(url);
|
||||
},
|
||||
useOEmbedUrlDirectly: true,
|
||||
validateIframeUrl: (iframeUrl: string) =>
|
||||
isValidGoogleDriveIframeUrl(iframeUrl),
|
||||
options: {
|
||||
widthInSurface: GOOGLE_DRIVE_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: GOOGLE_DRIVE_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
|
||||
@@ -18,7 +18,7 @@ const miroUrlValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
hostnames: ['miro.com'],
|
||||
};
|
||||
|
||||
export const miroConfig = {
|
||||
const miroConfig = {
|
||||
name: 'miro',
|
||||
match: (url: string) => validateEmbedIframeUrl(url, miroUrlValidationOptions),
|
||||
buildOEmbedUrl: (url: string) => {
|
||||
@@ -31,12 +31,6 @@ export const miroConfig = {
|
||||
return oEmbedUrl;
|
||||
},
|
||||
useOEmbedUrlDirectly: false,
|
||||
validateIframeUrl: (iframeUrl: string) => {
|
||||
if (!validateEmbedIframeUrl(iframeUrl, miroUrlValidationOptions)) {
|
||||
return false;
|
||||
}
|
||||
return new URL(iframeUrl).pathname.startsWith('/app/live-embed/');
|
||||
},
|
||||
options: {
|
||||
widthInSurface: MIRO_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: MIRO_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
|
||||
@@ -18,12 +18,7 @@ const spotifyUrlValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
hostnames: ['open.spotify.com', 'spotify.link'],
|
||||
};
|
||||
|
||||
const spotifyIframeUrlValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
protocols: ['https:'],
|
||||
hostnames: ['open.spotify.com'],
|
||||
};
|
||||
|
||||
export const spotifyConfig = {
|
||||
const spotifyConfig = {
|
||||
name: 'spotify',
|
||||
match: (url: string) =>
|
||||
validateEmbedIframeUrl(url, spotifyUrlValidationOptions),
|
||||
@@ -37,13 +32,6 @@ export const spotifyConfig = {
|
||||
return oEmbedUrl;
|
||||
},
|
||||
useOEmbedUrlDirectly: false,
|
||||
validateIframeUrl: (iframeUrl: string) => {
|
||||
if (!validateEmbedIframeUrl(iframeUrl, spotifyIframeUrlValidationOptions)) {
|
||||
return false;
|
||||
}
|
||||
const parsedUrl = new URL(iframeUrl);
|
||||
return parsedUrl.pathname.split('/').find(Boolean) === 'embed';
|
||||
},
|
||||
options: {
|
||||
widthInSurface: SPOTIFY_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: SPOTIFY_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
|
||||
@@ -202,7 +202,7 @@ export const builtinToolbarConfig = {
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.toggle=${toggle}
|
||||
.viewTypeSignal=${signal(actions[2].label)}
|
||||
.viewType$=${signal(actions[2].label)}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -408,7 +408,7 @@ export const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewTypeSignal=${signal(actions[1].label)}
|
||||
.viewType$=${signal(actions[1].label)}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -465,7 +465,7 @@ export const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.sizeSignal=${scale$}
|
||||
.size$=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -141,7 +141,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
});
|
||||
return;
|
||||
}
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
|
||||
refreshData = async () => {
|
||||
@@ -183,12 +183,6 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
|
||||
// update model
|
||||
const iframeUrl = this._getIframeUrl(embedData) ?? currentIframeUrl;
|
||||
if (!this._validateIframeUrl(url, iframeUrl)) {
|
||||
throw new BlockSuiteError(
|
||||
ErrorCode.ValueNotExists,
|
||||
'Invalid embed iframe url'
|
||||
);
|
||||
}
|
||||
this.store.updateBlock(this.model, {
|
||||
iframeUrl,
|
||||
title: embedData?.title || previewData?.title,
|
||||
@@ -297,19 +291,6 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _validateIframeUrl = (url: string, iframeUrl?: string) => {
|
||||
if (!iframeUrl) {
|
||||
return false;
|
||||
}
|
||||
const config = this.embedIframeService?.getConfig(url);
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
return config.validateIframeUrl
|
||||
? config.validateIframeUrl(iframeUrl, url)
|
||||
: config.match(iframeUrl);
|
||||
};
|
||||
|
||||
private readonly _handleDoubleClick = () => {
|
||||
this.open();
|
||||
};
|
||||
@@ -348,16 +329,6 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
|
||||
private readonly _renderIframe = () => {
|
||||
const { iframeUrl } = this.model.props;
|
||||
if (!iframeUrl || !this._isIframeUrlAllowed(iframeUrl)) {
|
||||
return html`<embed-iframe-error-card
|
||||
.error=${new Error('Invalid iframe URL')}
|
||||
.model=${this.model}
|
||||
.onRetry=${this._handleRetry}
|
||||
.std=${this.std}
|
||||
.inSurface=${this.inSurface}
|
||||
.options=${this._statusCardOptions}
|
||||
></embed-iframe-error-card>`;
|
||||
}
|
||||
const {
|
||||
widthPercent,
|
||||
heightInNote,
|
||||
@@ -397,10 +368,6 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
: nothing}`;
|
||||
};
|
||||
|
||||
private readonly _isIframeUrlAllowed = (iframeUrl: string) => {
|
||||
return this._validateIframeUrl(this.model.props.url, iframeUrl);
|
||||
};
|
||||
|
||||
private readonly _getSourceHost = () => {
|
||||
const url = this.model.props.url ?? this.model.props.iframeUrl;
|
||||
if (!url) return null;
|
||||
@@ -470,12 +437,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
} else {
|
||||
// update iframe options, to ensure the iframe is rendered with the correct options
|
||||
this._updateIframeOptions(this.model.props.url);
|
||||
this.status$.value = this._validateIframeUrl(
|
||||
this.model.props.url,
|
||||
this.model.props.iframeUrl
|
||||
)
|
||||
? 'success'
|
||||
: 'error';
|
||||
this.status$.value = 'success';
|
||||
}
|
||||
|
||||
// refresh data when original url changes
|
||||
|
||||
@@ -9,25 +9,6 @@ export interface EmbedIframeUrlValidationOptions {
|
||||
hostnames: string[]; // Allowed hostnames, e.g. ['docs.google.com']
|
||||
}
|
||||
|
||||
function isLocalOrIpHostname(hostname: string): boolean {
|
||||
const lower = hostname.toLowerCase();
|
||||
if (
|
||||
lower === 'localhost' ||
|
||||
lower.endsWith('.localhost') ||
|
||||
lower === '0.0.0.0' ||
|
||||
lower === '::' ||
|
||||
lower === '::1'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(lower)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lower.startsWith('[') && lower.endsWith(']');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the url is allowed to embed in the iframe
|
||||
* @param url URL to validate
|
||||
@@ -42,15 +23,6 @@ export function validateEmbedIframeUrl(
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
const { protocols, hostnames } = options;
|
||||
if (
|
||||
parsedUrl.username ||
|
||||
parsedUrl.password ||
|
||||
parsedUrl.port ||
|
||||
isLocalOrIpHostname(parsedUrl.hostname)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
protocols.includes(parsedUrl.protocol) &&
|
||||
hostnames.includes(parsedUrl.hostname)
|
||||
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user