Compare commits

..

6 Commits

Author SHA1 Message Date
DarkSky c4c9e3c36d fix: retry 2026-05-30 18:05:35 +08:00
DarkSky 1a8d884f8e chore(server): add logs 2026-05-30 16:53:16 +08:00
DarkSky 91acb88a2d fix(server): mail test & retry 2026-05-30 16:30:51 +08:00
DarkSky 43704d60fb feat(server): improve calendar sync queue (#14783) 2026-04-05 11:05:35 +08:00
DarkSky 46e7e35357 feat(server): improve calendar sync queue (#14783) 2026-04-05 11:02:48 +08:00
DarkSky b98ab495bb fix(server): race condition for sync 2026-04-03 02:00:02 +08:00
1815 changed files with 39272 additions and 138054 deletions
-5
View File
@@ -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" }
+53 -89
View File
@@ -135,17 +135,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
}
}
@@ -209,8 +209,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",
@@ -249,8 +249,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",
@@ -300,22 +300,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",
@@ -369,7 +353,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",
@@ -436,6 +420,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."
@@ -485,13 +473,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.",
@@ -567,7 +548,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",
@@ -634,6 +615,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."
@@ -683,13 +668,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.",
@@ -877,14 +855,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\":\"\",\"requestTimeoutMs\":10000}\n@link https://developers.google.com/calendar/api/guides/push",
"properties": {
"enabled": {
"type": "boolean"
},
"allowNewAccounts": {
"type": "boolean"
},
"clientId": {
"type": "string"
},
@@ -903,7 +878,6 @@
},
"default": {
"enabled": false,
"allowNewAccounts": true,
"clientId": "",
"clientSecret": "",
"externalWebhookUrl": "",
@@ -1011,35 +985,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",
@@ -1049,14 +1012,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\":\"\"}",
@@ -1107,6 +1062,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\"}",
@@ -1150,6 +1112,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\":\"\"}",
@@ -1208,7 +1175,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",
@@ -1275,6 +1242,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."
@@ -1324,13 +1295,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.",
+2 -8
View File
@@ -50,14 +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
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
+7 -7
View File
@@ -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:
@@ -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 }}"
+2 -2
View File
@@ -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 }}
@@ -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: []
+2 -2
View File
@@ -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+)?)\""
],
+2 -2
View File
@@ -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
+8 -8
View File
@@ -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
+91 -51
View File
@@ -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
@@ -141,7 +138,7 @@ jobs:
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
@@ -162,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
@@ -185,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:
@@ -215,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
@@ -229,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:
@@ -259,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:
@@ -272,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()
@@ -288,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
@@ -300,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:
@@ -332,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:
@@ -362,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:
@@ -397,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:
@@ -436,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:
@@ -477,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
@@ -517,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:
@@ -540,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:
@@ -567,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:
@@ -621,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
@@ -702,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
@@ -765,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
@@ -806,7 +846,7 @@ jobs:
CARGO_TERM_COLOR: always
MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -834,7 +874,7 @@ jobs:
RUST_BACKTRACE: full
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -858,7 +898,7 @@ jobs:
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -897,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:
@@ -920,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
@@ -989,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
@@ -1062,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
@@ -1145,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
@@ -1226,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
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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'
+1 -1
View File
@@ -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@v6
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: |
+16 -104
View File
@@ -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,48 +352,24 @@ 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' &&
needs.make-distribution-macos.result == 'success' &&
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)
uses: actions/download-artifact@v4
with:
@@ -467,13 +381,11 @@ jobs:
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
@@ -483,7 +395,7 @@ jobs:
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
+7 -7
View File
@@ -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:
@@ -114,7 +114,7 @@ jobs:
- name: Cap sync
run: yarn workspace @affine/ios sync
- name: Signing By Apple Developer ID
uses: apple-actions/import-codesign-certs@v6
uses: apple-actions/import-codesign-certs@v5
id: import-codesign-certs
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12_MOBILE }}
@@ -147,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:
@@ -180,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
@@ -192,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'
+2 -3
View File
@@ -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@v8
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 == 'beta' || needs.prepare.outputs.BUILD_TYPE == 'stable' || (github.event_name == 'workflow_dispatch' && inputs.desktop_windows) }}
mobile:
name: Release Mobile
+1 -1
View File
@@ -48,7 +48,7 @@ testem.log
/typings
tsconfig.tsbuildinfo
.context
/*.md
*.md
# System Files
.DS_Store
+1 -1
View File
@@ -1 +1 @@
22.22.3
22.22.0
+7 -87
View File
@@ -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,7 +15,6 @@
".github/helm",
".git",
".vscode",
".context",
".yarnrc.yml",
".docker",
"**/.storybook",
@@ -52,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"
@@ -67,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",
{
@@ -200,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",
@@ -230,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": [
@@ -281,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": [
@@ -323,7 +269,7 @@
{
"files": ["blocksuite/**/*.ts"],
"rules": {
"eqeqeq": "off",
"eslint/eqeqeq": "off",
"typescript/no-non-null-assertion": "off",
"unicorn/prefer-array-some": "off"
}
@@ -344,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",
{
@@ -375,14 +310,6 @@
"**/e2e/**/*"
],
"rules": {
"typescript/no-floating-promises": [
"error",
{
"ignoreVoid": true,
"ignoreIIFE": false
}
],
"typescript/no-misused-promises": "off",
"no-restricted-imports": "off"
}
},
@@ -391,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"
}
}
]
}
-3
View File
@@ -4,7 +4,6 @@
.github/helm
.git
.vscode
.context
.yarnrc.yml
.docker
**/.storybook
@@ -39,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
-2
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
-940
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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
+461 -3493
View File
File diff suppressed because it is too large Load Diff
+3 -33
View File
@@ -16,7 +16,6 @@ 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"
@@ -37,32 +36,18 @@ resolver = "3"
criterion2 = { version = "3", default-features = false }
crossbeam-channel = "0.5"
dispatch2 = "0.3"
docx-parser = { git = "https://github.com/toeverything/docx-parser", rev = "380beea" }
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"
@@ -79,10 +64,8 @@ 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" }
@@ -97,11 +80,9 @@ resolver = "3"
readability = { version = "0.3.0", default-features = false }
regex = "1.10"
rubato = "0.16"
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 = [
@@ -110,6 +91,7 @@ resolver = "3"
"migrate",
"runtime-tokio",
"sqlite",
"tls-rustls",
] }
strum_macros = "0.27.0"
symphonia = { version = "0.5", features = ["all", "opt-simd"] }
@@ -129,14 +111,6 @@ resolver = "3"
tree-sitter-rust = { version = "0.24" }
tree-sitter-scala = { version = "0.24" }
tree-sitter-typescript = { version = "0.23" }
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"
uniffi = "0.29"
url = { version = "2.5" }
uuid = "1.8"
@@ -170,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" }
-2
View File
@@ -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.
+4 -3
View File
@@ -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",
@@ -298,7 +299,7 @@
"version": "0.26.3",
"devDependencies": {
"@vanilla-extract/vite-plugin": "^5.0.0",
"msw": "^2.13.2",
"vitest": "^4.0.18"
"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,46 +127,6 @@ Hello world
expect(meta?.tags).toEqual(['a', 'b']);
});
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 -1
View File
@@ -1,3 +1,3 @@
/* oxlint-disable no-restricted-imports */
/* eslint-disable @typescript-eslint/no-restricted-imports */
export * from '@blocksuite/store';
+1 -1
View File
@@ -11,7 +11,7 @@ export default defineConfig({
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",
@@ -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.0.18",
"playwright": "=1.58.2",
"vitest": "^4.0.18"
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -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",
@@ -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"
},
@@ -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",
@@ -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.0.0",
"lit": "^3.2.0",
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
@@ -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",
@@ -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",
@@ -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",
@@ -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>`
)}`;
},
+12 -1
View File
@@ -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",
@@ -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>`
)}`;
},
@@ -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,
@@ -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,
@@ -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,
@@ -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
@@ -10,7 +10,10 @@
{ "path": "../surface" },
{ "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../../gfx/pointer" },
{ "path": "../../inlines/reference" },
{ "path": "../../model" },
{ "path": "../../rich-text" },
{ "path": "../../shared" },
{ "path": "../../widgets/slash-menu" },
{ "path": "../../../framework/global" },
+6 -1
View File
@@ -22,10 +22,15 @@
"@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",
"yjs": "^13.6.27"
"rxjs": "^7.8.2",
"yjs": "^13.6.27",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
+5 -1
View File
@@ -22,10 +22,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",
"file-type": "^21.0.0",
"lit": "^3.2.0"
"lit": "^3.2.0",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
+9 -1
View File
@@ -15,16 +15,24 @@
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
"@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:*",
"@floating-ui/dom": "^1.6.13",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23",
"@types/katex": "^0.16.7",
"@types/mdast": "^4.0.4",
"katex": "^0.16.27",
"lit": "^3.2.0",
"remark-math": "^6.0.0"
"remark-math": "^6.0.0",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {
".": "./src/index.ts",
@@ -1,7 +1,7 @@
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { unsafeHTML } from '@blocksuite/affine-shared/utils';
import katex from 'katex';
import { html } from 'lit';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
export const LatexTooltip = (
str: string,
@@ -35,8 +35,7 @@ export const LatexTooltip = (
katex.renderToString(latex, {
displayMode,
output: 'mathml',
}),
{ USE_PROFILES: { html: true, mathMl: true } }
})
)}
</div>
</div>`;
+1 -1
View File
@@ -9,7 +9,7 @@ export const latexBlockStyles = css`
height: 100%;
padding: 10px 24px;
flex-direction: column;
align-items: stretch;
align-items: center;
justify-content: center;
border-radius: 4px;
overflow-x: auto;
@@ -12,8 +12,10 @@
{ "path": "../../ext-loader" },
{ "path": "../../inlines/latex" },
{ "path": "../../model" },
{ "path": "../../rich-text" },
{ "path": "../../shared" },
{ "path": "../../widgets/slash-menu" },
{ "path": "../../../framework/global" },
{ "path": "../../../framework/std" },
{ "path": "../../../framework/store" }
]
+9 -1
View File
@@ -21,9 +21,17 @@
"@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"
"lit": "^3.2.0",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"devDependencies": {
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
@@ -30,11 +30,7 @@ export function correctNumberedListsOrderToPrev(
const fn = () => {
// step 1
const parent = doc.getParent(model);
if (!parent) return;
const index = parent.children.indexOf(model);
const previousSibling = index > 0 ? parent.children[index - 1] : null;
const previousSibling = doc.getPrev(model);
if (
previousSibling &&
matchModels(previousSibling, [ListBlockModel]) &&
@@ -33,6 +33,7 @@
"@vanilla-extract/css": "^1.17.0",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
"rxjs": "^7.8.2",
"zod": "^3.25.76"
},
"exports": {

Some files were not shown because too many files have changed in this diff Show More