mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-02 02:00:49 +08:00
Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b4d25f332 | |||
| c6a99eb9cb | |||
| 77657a697b | |||
| eb953c0565 | |||
| 77c0b2ef47 | |||
| 7138fea9db | |||
| 156cfc7e76 | |||
| 2ca4973167 | |||
| a1ae7d11a3 | |||
| f41bc2d5c3 | |||
| e3391c0577 | |||
| 5806ad8a3a | |||
| 193ec14ad3 | |||
| 64149d909a | |||
| f81abe692d | |||
| aa48c1c18b | |||
| 3219b82827 | |||
| b0cc2fd389 | |||
| 558400b7db | |||
| fc5329a1be | |||
| 547ab47a5e | |||
| fee0cfa3f4 | |||
| ffc27af3ba | |||
| d975bf46fb | |||
| bfcf7fc2ba | |||
| 5a6c65085a | |||
| 4be0cba9b5 | |||
| afea9121cf | |||
| 3912d8d74d | |||
| d3ec008b0c | |||
| d0607b5ce7 | |||
| 8ca3f2d53d | |||
| 0da32d61ae | |||
| 233004f867 | |||
| cdadf8588a | |||
| 8c07ea2a0e | |||
| 91ad783973 | |||
| 9ca520a81f | |||
| 64f385817d | |||
| 9cf98d9345 | |||
| a617da99e2 | |||
| a41c5e4366 | |||
| 5b05c5a1b2 | |||
| a3379c8979 | |||
| 166372cc3e | |||
| 9b56a05159 | |||
| b0251c93cb | |||
| 5d124ee55b | |||
| dcf041a3f2 | |||
| 8ba02ed6fb | |||
| ffa3ff9d7f | |||
| f47ee2bc8a | |||
| bcf2a51d41 | |||
| 6a93566422 | |||
| 7ac8b14b65 | |||
| 16a8f17717 | |||
| 1ffb8c922c | |||
| daf536f77a | |||
| 0d2d4bb6a1 | |||
| cb9897d493 | |||
| 8ca8333cd6 | |||
| 3bf2503f55 | |||
| 59fd942f40 | |||
| d6d5ae6182 | |||
| c1a09b951f | |||
| 4ce68d74f1 | |||
| fbfcc01d14 | |||
| 1112a06623 | |||
| bbcb7e69fe | |||
| cc2f23339e | |||
| 31101a69e7 | |||
| 0b1a44863f | |||
| 8406f9656e | |||
| 121c0d172d | |||
| 8f03090780 | |||
| 8125cc0e75 | |||
| f537a75f01 | |||
| 9456a07889 | |||
| 8f571ddc30 | |||
| 13ad1beb10 | |||
| 9844ca4d54 | |||
| d7d67841b8 | |||
| 29a27b561b | |||
| 02744cec00 | |||
| 6d710f3bdc | |||
| 0b47f92134 | |||
| 9c55edeb62 | |||
| 9742e9735e | |||
| 86d65b2f64 | |||
| f34e25e122 | |||
| b5d5b71f95 | |||
| 09fa1a8e4e | |||
| c249011238 | |||
| 7f5f7e79df | |||
| fff04395bc | |||
| bbc01533d7 | |||
| e31cca3354 | |||
| 11bc333714 | |||
| 99b07c2ee1 | |||
| fc9b99cd17 | |||
| 2137f68871 | |||
| 75efa854bf | |||
| c0139abf79 | |||
| 5a38e765bd | |||
| d3dcdd47ee | |||
| 727c9d6d71 | |||
| 274f491e49 | |||
| 478138493a | |||
| 5464d1a9ce | |||
| 4c40dcacd9 | |||
| 76d28aaa38 | |||
| 86f48240ce | |||
| c5d622531c | |||
| 60acd81d4b | |||
| 78f567a178 | |||
| 784382cfb1 | |||
| 342451be1b | |||
| 2b6146727b | |||
| d5245a3273 | |||
| fff63562b1 | |||
| 4136abdd97 | |||
| e249e2e884 | |||
| 2e95d91093 | |||
| 2cb171f553 | |||
| a4e2242b8d | |||
| c90f173821 | |||
| e1e0ac2345 | |||
| bdccf4e9fd | |||
| 11cf1928b5 | |||
| 5215c73166 |
@@ -19,3 +19,8 @@ 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" }
|
||||
|
||||
@@ -62,6 +62,18 @@
|
||||
"concurrency": 10
|
||||
}
|
||||
},
|
||||
"queues.calendar": {
|
||||
"type": "object",
|
||||
"description": "The config for calendar job queue\n@default {\"concurrency\":4}",
|
||||
"properties": {
|
||||
"concurrency": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"concurrency": 4
|
||||
}
|
||||
},
|
||||
"queues.doc": {
|
||||
"type": "object",
|
||||
"description": "The config for doc job queue\n@default {\"concurrency\":1}",
|
||||
@@ -197,8 +209,8 @@
|
||||
"properties": {
|
||||
"SMTP.name": {
|
||||
"type": "string",
|
||||
"description": "Name of the email server (e.g. your domain name)\n@default \"AFFiNE Server\"\n@environment `MAILER_SERVERNAME`",
|
||||
"default": "AFFiNE Server"
|
||||
"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": ""
|
||||
},
|
||||
"SMTP.host": {
|
||||
"type": "string",
|
||||
@@ -237,8 +249,8 @@
|
||||
},
|
||||
"fallbackSMTP.name": {
|
||||
"type": "string",
|
||||
"description": "Name of the fallback email server (e.g. your domain name)\n@default \"AFFiNE Server\"",
|
||||
"default": "AFFiNE Server"
|
||||
"description": "Hostname used for fallback SMTP HELO/EHLO (e.g. mail.example.com). Leave empty to use the system hostname.\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.host": {
|
||||
"type": "string",
|
||||
@@ -843,7 +855,7 @@
|
||||
"properties": {
|
||||
"google": {
|
||||
"type": "object",
|
||||
"description": "Google Calendar integration config\n@default {\"enabled\":false,\"clientId\":\"\",\"clientSecret\":\"\",\"externalWebhookUrl\":\"\",\"webhookVerificationToken\":\"\"}\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"
|
||||
@@ -859,6 +871,9 @@
|
||||
},
|
||||
"webhookVerificationToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
@@ -866,7 +881,8 @@
|
||||
"clientId": "",
|
||||
"clientSecret": "",
|
||||
"externalWebhookUrl": "",
|
||||
"webhookVerificationToken": ""
|
||||
"webhookVerificationToken": "",
|
||||
"requestTimeoutMs": 10000
|
||||
}
|
||||
},
|
||||
"caldav": {
|
||||
@@ -971,7 +987,7 @@
|
||||
},
|
||||
"scenarios": {
|
||||
"type": "object",
|
||||
"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\"}}",
|
||||
"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\",\"coding\":\"claude-sonnet-4-5@20250929\",\"complex_text_generation\":\"gpt-5-mini\",\"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": {
|
||||
@@ -979,15 +995,24 @@
|
||||
"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",
|
||||
"complex_text_generation": "gpt-5-mini",
|
||||
"quick_decision_making": "gpt-5-mini",
|
||||
"quick_text_generation": "gemini-2.5-flash",
|
||||
"polish_and_summarize": "gemini-2.5-flash"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.profiles": {
|
||||
"type": "array",
|
||||
"description": "The profile list for copilot providers.\n@default []",
|
||||
"default": []
|
||||
},
|
||||
"providers.defaults": {
|
||||
"type": "object",
|
||||
"description": "The default provider ids for model output types and global fallback.\n@default {}",
|
||||
"default": {}
|
||||
},
|
||||
"providers.openai": {
|
||||
"type": "object",
|
||||
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.openai.com/v1\"}\n@link https://github.com/openai/openai-node",
|
||||
@@ -996,6 +1021,14 @@
|
||||
"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\":\"\"}",
|
||||
|
||||
@@ -50,8 +50,14 @@ runs:
|
||||
# https://github.com/tree-sitter/tree-sitter/issues/4186
|
||||
# pass -D_BSD_SOURCE to clang to fix the tree-sitter build issue
|
||||
run: |
|
||||
echo "CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
echo "TARGET_CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
if [[ "${{ inputs.target }}" == "aarch64-unknown-linux-gnu" ]]; then
|
||||
# napi cross-toolchain 1.0.3 headers miss AT_HWCAP2 in elf.h
|
||||
echo "CC=clang -D_BSD_SOURCE -DAT_HWCAP2=26" >> "$GITHUB_ENV"
|
||||
echo "TARGET_CC=clang -D_BSD_SOURCE -DAT_HWCAP2=26" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
echo "TARGET_CC=clang -D_BSD_SOURCE" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
@@ -53,7 +53,7 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
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@v4
|
||||
uses: actions/cache@v5
|
||||
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@v4
|
||||
uses: actions/cache@v5
|
||||
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@v4
|
||||
uses: actions/cache@v5
|
||||
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@v4
|
||||
uses: actions/cache@v5
|
||||
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@v4
|
||||
- uses: actions/cache@v5
|
||||
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@v4
|
||||
- uses: actions/cache@v5
|
||||
id: electron-cache
|
||||
if: ${{ inputs.electron-install == 'true' }}
|
||||
with:
|
||||
|
||||
@@ -25,6 +25,22 @@ spec:
|
||||
serviceAccountName: {{ include "front.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
{{- with .Values.global.database.gcloud }}
|
||||
{{- if .enabled }}
|
||||
initContainers:
|
||||
- name: wait-for-cloud-sql-proxy
|
||||
image: busybox:1.36.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
until wget -q -T 2 -O /dev/null "http://{{ $.Values.global.database.host }}:9801/startup"; do
|
||||
echo "waiting for cloud sql proxy to become ready"
|
||||
sleep 2
|
||||
done
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
|
||||
@@ -31,10 +31,10 @@ podSecurityContext:
|
||||
resources:
|
||||
limits:
|
||||
cpu: '1'
|
||||
memory: 4Gi
|
||||
memory: 6Gi
|
||||
requests:
|
||||
cpu: '1'
|
||||
memory: 2Gi
|
||||
memory: 4Gi
|
||||
|
||||
probe:
|
||||
initialDelaySeconds: 20
|
||||
|
||||
@@ -12,6 +12,10 @@ 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,6 +23,22 @@ 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 }}"
|
||||
@@ -95,11 +111,19 @@ 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,6 +13,22 @@ 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,6 +36,13 @@ resources:
|
||||
|
||||
probe:
|
||||
initialDelaySeconds: 20
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 10
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
liveness:
|
||||
failureThreshold: 12
|
||||
readiness: {}
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"groupName": "opentelemetry",
|
||||
"matchPackageNames": [
|
||||
"/^@opentelemetry/",
|
||||
"/^@google-cloud\/opentelemetry-/"
|
||||
"/^@google-cloud/opentelemetry-/"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -79,7 +79,7 @@
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": ["^rust-toolchain\\.toml?$"],
|
||||
"managerFilePatterns": ["/^rust-toolchain\\.toml?$/"],
|
||||
"matchStrings": [
|
||||
"channel\\s*=\\s*\"(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)\""
|
||||
],
|
||||
|
||||
@@ -13,5 +13,5 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/labeler@v6
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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-latest
|
||||
runs-on: ubuntu-22.04
|
||||
environment: ${{ inputs.build-type }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
file: server-native.armv7.node
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
needs:
|
||||
- build-server-native
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -202,7 +202,7 @@ jobs:
|
||||
- build-mobile
|
||||
- build-admin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- 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@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Go (for actionlint)
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Install actionlint
|
||||
@@ -82,17 +82,13 @@ 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
|
||||
@@ -111,7 +107,7 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -138,7 +134,7 @@ jobs:
|
||||
outputs:
|
||||
run-rust: ${{ steps.rust-filter.outputs.rust }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: rust-filter
|
||||
@@ -159,7 +155,7 @@ jobs:
|
||||
needs:
|
||||
- rust-test-filter
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
@@ -182,7 +178,7 @@ jobs:
|
||||
needs:
|
||||
- build-server-native
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -212,7 +208,7 @@ jobs:
|
||||
name: Check yarn binary
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Run check
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -226,9 +222,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -256,7 +252,7 @@ jobs:
|
||||
name: E2E BlockSuite Cross Browser Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -269,10 +265,13 @@ jobs:
|
||||
- name: Run playground build
|
||||
run: yarn workspace @blocksuite/playground build
|
||||
|
||||
- name: Run playwright tests
|
||||
run: |
|
||||
yarn workspace @blocksuite/integration-test test:unit
|
||||
yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only
|
||||
- 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: Upload test results
|
||||
if: always()
|
||||
@@ -282,52 +281,6 @@ 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
|
||||
@@ -340,7 +293,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -372,7 +325,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -402,9 +355,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -437,7 +390,7 @@ jobs:
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -476,7 +429,7 @@ jobs:
|
||||
- { os: macos-latest, target: aarch64-apple-darwin }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -517,7 +470,7 @@ jobs:
|
||||
- { os: windows-latest, target: aarch64-pc-windows-msvc }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: samypr100/setup-dev-drive@v3
|
||||
with:
|
||||
workspace-copy: true
|
||||
@@ -557,7 +510,7 @@ jobs:
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -580,7 +533,7 @@ jobs:
|
||||
name: Build @affine/electron renderer
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -607,7 +560,7 @@ jobs:
|
||||
needs:
|
||||
- build-native-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -661,7 +614,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -742,7 +695,7 @@ jobs:
|
||||
stack-version: 9.0.1
|
||||
security-enabled: false
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -805,7 +758,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -846,7 +799,7 @@ jobs:
|
||||
CARGO_TERM_COLOR: always
|
||||
MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -874,7 +827,7 @@ jobs:
|
||||
RUST_BACKTRACE: full
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -898,7 +851,7 @@ jobs:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -937,7 +890,7 @@ jobs:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
@@ -960,7 +913,7 @@ jobs:
|
||||
run-api: ${{ steps.decision.outputs.run_api }}
|
||||
run-e2e: ${{ steps.decision.outputs.run_e2e }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: copilot-filter
|
||||
@@ -1029,7 +982,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -1102,7 +1055,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -1185,7 +1138,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -1266,7 +1219,7 @@ jobs:
|
||||
test: true,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
timeout-minutes: 10
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- 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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.action != 'edited' || github.event.changes.title != null }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- build-images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- 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@v5
|
||||
uses: apple-actions/import-codesign-certs@v6
|
||||
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@v2
|
||||
- uses: actions/attest-build-provenance@v4
|
||||
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@v2
|
||||
- uses: actions/attest-build-provenance@v4
|
||||
if: ${{ inputs.platform == 'linux' }}
|
||||
with:
|
||||
subject-path: |
|
||||
|
||||
@@ -27,6 +27,11 @@ 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
|
||||
@@ -48,7 +53,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -73,6 +78,21 @@ 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:
|
||||
@@ -154,24 +174,45 @@ jobs:
|
||||
enable_scripts: true
|
||||
|
||||
sign-packaged-artifacts-windows_x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: package-distribution-windows-x64
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-x64
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-x64.outputs.files_to_be_signed }}
|
||||
artifact-name: packaged-win32-x64
|
||||
|
||||
sign-packaged-artifacts-windows_arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: package-distribution-windows-arm64
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-arm64
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-arm64.outputs.files_to_be_signed }}
|
||||
artifact-name: packaged-win32-arm64
|
||||
|
||||
make-windows-installer:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
inputs.desktop_windows &&
|
||||
needs.windows-signer-gate.result == 'success' &&
|
||||
needs.package-distribution-windows-x64.result == 'success' &&
|
||||
needs.package-distribution-windows-arm64.result == 'success' &&
|
||||
(
|
||||
!inputs.require-windows-signing ||
|
||||
(
|
||||
needs.sign-packaged-artifacts-windows_x64.result == 'success' &&
|
||||
needs.sign-packaged-artifacts-windows_arm64.result == 'success'
|
||||
)
|
||||
)
|
||||
}}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- package-distribution-windows-x64
|
||||
- package-distribution-windows-arm64
|
||||
- sign-packaged-artifacts-windows_x64
|
||||
- sign-packaged-artifacts-windows_arm64
|
||||
strategy:
|
||||
@@ -187,7 +228,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@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -209,11 +250,13 @@ 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'
|
||||
@@ -263,25 +306,38 @@ jobs:
|
||||
path: archive.zip
|
||||
|
||||
sign-installer-artifacts-windows-x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: make-windows-installer
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.make-windows-installer.outputs.FILES_TO_BE_SIGNED_x64 }}
|
||||
artifact-name: installer-win32-x64
|
||||
|
||||
sign-installer-artifacts-windows-arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: make-windows-installer
|
||||
if: ${{ inputs.desktop_windows && needs.windows-signer-gate.outputs.signer_available == 'true' }}
|
||||
needs:
|
||||
- windows-signer-gate
|
||||
- make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.make-windows-installer.outputs.FILES_TO_BE_SIGNED_arm64 }}
|
||||
artifact-name: installer-win32-arm64
|
||||
|
||||
finalize-installer-windows:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
inputs.desktop_windows &&
|
||||
needs.make-windows-installer.result == 'success'
|
||||
}}
|
||||
needs:
|
||||
[
|
||||
windows-signer-gate,
|
||||
make-windows-installer,
|
||||
sign-packaged-artifacts-windows_x64,
|
||||
sign-packaged-artifacts-windows_arm64,
|
||||
sign-installer-artifacts-windows-x64,
|
||||
sign-installer-artifacts-windows-arm64,
|
||||
before-make,
|
||||
@@ -299,18 +355,22 @@ 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'
|
||||
@@ -338,13 +398,15 @@ 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@v2
|
||||
- uses: actions/attest-build-provenance@v4
|
||||
if: ${{ (matrix.spec.arch == 'x64' && needs.sign-packaged-artifacts-windows_x64.result == 'success' && needs.sign-installer-artifacts-windows-x64.result == 'success') || (matrix.spec.arch == 'arm64' && needs.sign-packaged-artifacts-windows_arm64.result == 'success' && needs.sign-installer-artifacts-windows-arm64.result == 'success') }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
@@ -352,24 +414,48 @@ 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: ${{ inputs.desktop_macos && inputs.desktop_linux && inputs.desktop_windows }}
|
||||
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'
|
||||
)
|
||||
)
|
||||
}}
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Download Artifacts (macos-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -381,11 +467,13 @@ 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
|
||||
@@ -395,7 +483,7 @@ jobs:
|
||||
with:
|
||||
name: affine-linux-x64-builds
|
||||
path: ./release
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Copy Selfhost Release Files
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
needs:
|
||||
- build-ios-web
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v5
|
||||
uses: apple-actions/import-codesign-certs@v6
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v5
|
||||
- uses: actions/setup-python@v6
|
||||
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@v4
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
GIT_SHORT_HASH: ${{ steps.prepare.outputs.GIT_SHORT_HASH }}
|
||||
BUILD_TYPE: ${{ steps.prepare.outputs.BUILD_TYPE }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}'
|
||||
@@ -195,6 +195,7 @@ jobs:
|
||||
desktop_macos: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_macos }}
|
||||
desktop_windows: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_windows }}
|
||||
desktop_linux: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_linux }}
|
||||
require-windows-signing: ${{ needs.prepare.outputs.BUILD_TYPE == 'beta' || needs.prepare.outputs.BUILD_TYPE == 'stable' || (github.event_name == 'workflow_dispatch' && inputs.desktop_windows) }}
|
||||
|
||||
mobile:
|
||||
name: Release Mobile
|
||||
|
||||
@@ -48,6 +48,7 @@ testem.log
|
||||
/typings
|
||||
tsconfig.tsbuildinfo
|
||||
.context
|
||||
/*.md
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
|
||||
+85
-7
@@ -1,10 +1,18 @@
|
||||
{
|
||||
"$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
|
||||
@@ -15,6 +23,7 @@
|
||||
".github/helm",
|
||||
".git",
|
||||
".vscode",
|
||||
".context/**/*.js",
|
||||
".yarnrc.yml",
|
||||
".docker",
|
||||
"**/.storybook",
|
||||
@@ -56,9 +65,31 @@
|
||||
"react/display-name": "error",
|
||||
"react/rules-of-hooks": "error",
|
||||
"react/exhaustive-deps": "warn",
|
||||
"@typescript-eslint/prefer-for-of": "error",
|
||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
||||
"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",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
@@ -167,6 +198,29 @@
|
||||
"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",
|
||||
@@ -174,7 +228,6 @@
|
||||
"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": [
|
||||
@@ -226,11 +279,10 @@
|
||||
"ignoreTypes": true
|
||||
}
|
||||
],
|
||||
"import/sort-imports": "error",
|
||||
"import/namespace": "off",
|
||||
"import/no-webpack-loader-syntax": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"import/no-import-assign": "error",
|
||||
"no-import-assign": "error",
|
||||
"import/no-self-import": "error"
|
||||
},
|
||||
"overrides": [
|
||||
@@ -269,7 +321,7 @@
|
||||
{
|
||||
"files": ["blocksuite/**/*.ts"],
|
||||
"rules": {
|
||||
"eslint/eqeqeq": "off",
|
||||
"eqeqeq": "off",
|
||||
"typescript/no-non-null-assertion": "off",
|
||||
"unicorn/prefer-array-some": "off"
|
||||
}
|
||||
@@ -290,6 +342,17 @@
|
||||
"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",
|
||||
{
|
||||
@@ -310,6 +373,14 @@
|
||||
"**/e2e/**/*"
|
||||
],
|
||||
"rules": {
|
||||
"typescript/no-floating-promises": [
|
||||
"error",
|
||||
{
|
||||
"ignoreVoid": true,
|
||||
"ignoreIIFE": false
|
||||
}
|
||||
],
|
||||
"typescript/no-misused-promises": "off",
|
||||
"no-restricted-imports": "off"
|
||||
}
|
||||
},
|
||||
@@ -318,6 +389,13 @@
|
||||
"rules": {
|
||||
"react/rules-of-hooks": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["blocksuite/framework/std/src/view/element/lit-host.ts"],
|
||||
"rules": {
|
||||
"lit/binding-positions": "off",
|
||||
"lit/no-invalid-html": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
.github/helm
|
||||
.git
|
||||
.vscode
|
||||
.context/**/*.js
|
||||
.yarnrc.yml
|
||||
.docker
|
||||
**/.storybook
|
||||
|
||||
Vendored
+2
@@ -32,6 +32,8 @@
|
||||
"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"
|
||||
},
|
||||
|
||||
Vendored
-942
File diff suppressed because one or more lines are too long
+940
File diff suppressed because one or more lines are too long
+1
-1
@@ -12,4 +12,4 @@ npmPublishAccess: public
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.13.0.cjs
|
||||
|
||||
Generated
+2596
-446
File diff suppressed because it is too large
Load Diff
+23
-2
@@ -36,18 +36,30 @@ resolver = "3"
|
||||
criterion2 = { version = "3", default-features = false }
|
||||
crossbeam-channel = "0.5"
|
||||
dispatch2 = "0.3"
|
||||
docx-parser = { git = "https://github.com/toeverything/docx-parser" }
|
||||
docx-parser = { git = "https://github.com/toeverything/docx-parser", rev = "380beea" }
|
||||
dotenvy = "0.15"
|
||||
file-format = { version = "0.28", features = ["reader"] }
|
||||
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.1.4", default-features = false }
|
||||
log = "0.4"
|
||||
loom = { version = "0.7", features = ["checkpoint"] }
|
||||
lru = "0.16"
|
||||
memory-indexer = "0.3.0"
|
||||
matroska = "0.30"
|
||||
memory-indexer = "0.3.1"
|
||||
mermaid-rs-renderer = { git = "https://github.com/toeverything/mermaid-rs-renderer", rev = "fba9097", default-features = false }
|
||||
mimalloc = "0.1"
|
||||
mp4parse = "0.17"
|
||||
nanoid = "0.4"
|
||||
@@ -64,6 +76,7 @@ resolver = "3"
|
||||
notify = { version = "8", features = ["serde"] }
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = "0.3"
|
||||
ogg = "0.9"
|
||||
once_cell = "1"
|
||||
ordered-float = "5"
|
||||
parking_lot = "0.12"
|
||||
@@ -111,6 +124,14 @@ 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"
|
||||
|
||||
@@ -23,4 +23,6 @@ We welcome you to provide us with bug reports via and email at [security@toevery
|
||||
|
||||
Since we are an open source project, we also welcome you to provide corresponding fix PRs, we will determine specific rewards based on the evaluation results.
|
||||
|
||||
Due to limited resources, we do not accept and will not review any AI-generated security reports.
|
||||
|
||||
If the vulnerability is caused by a library we depend on, we encourage you to submit a security report to the corresponding dependent library at the same time to benefit more users.
|
||||
|
||||
@@ -78,8 +78,7 @@
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@blocksuite/sync": "workspace:*",
|
||||
"rxjs": "^7.8.2"
|
||||
"@blocksuite/sync": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -299,7 +298,7 @@
|
||||
"version": "0.26.3",
|
||||
"devDependencies": {
|
||||
"@vanilla-extract/vite-plugin": "^5.0.0",
|
||||
"msw": "^2.12.4",
|
||||
"vitest": "^3.2.4"
|
||||
"msw": "^2.13.2",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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",
|
||||
],
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,14 @@
|
||||
> [!custom] Panel
|
||||
> Body line
|
||||
|
||||
![[archive.zip]]
|
||||
|
||||
[^1]
|
||||
|
||||
---
|
||||
|
||||
after note
|
||||
|
||||
[[linked]]
|
||||
|
||||
[^1]: reference body
|
||||
@@ -0,0 +1 @@
|
||||
plain linked page
|
||||
@@ -1,4 +1,10 @@
|
||||
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { basename, resolve } from 'node:path';
|
||||
|
||||
import {
|
||||
MarkdownTransformer,
|
||||
ObsidianTransformer,
|
||||
} from '@blocksuite/affine/widgets/linked-doc';
|
||||
import {
|
||||
DefaultTheme,
|
||||
NoteDisplayMode,
|
||||
@@ -8,13 +14,18 @@ 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';
|
||||
@@ -29,6 +40,138 @@ 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 = {
|
||||
@@ -127,6 +270,46 @@ 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,3 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-restricted-imports */
|
||||
/* oxlint-disable no-restricted-imports */
|
||||
|
||||
export * from '@blocksuite/store';
|
||||
|
||||
@@ -11,7 +11,7 @@ export default defineConfig({
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
provider: 'istanbul', // or 'c8'
|
||||
provider: 'istanbul',
|
||||
reporter: ['lcov'],
|
||||
reportsDirectory: '../../../.coverage/blocksuite-affine',
|
||||
},
|
||||
|
||||
@@ -21,13 +21,10 @@
|
||||
"@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",
|
||||
"zod": "^3.25.76"
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import {
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
createAttachmentBlockSnapshot,
|
||||
FOOTNOTE_DEFINITION_PREFIX,
|
||||
getFootnoteDefinitionText,
|
||||
isFootnoteDefinitionNode,
|
||||
@@ -56,18 +57,15 @@ export const attachmentBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher
|
||||
}
|
||||
walkerContext
|
||||
.openNode(
|
||||
{
|
||||
type: 'block',
|
||||
createAttachmentBlockSnapshot({
|
||||
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.store.readonly && model.props.type.endsWith('pdf');
|
||||
return 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}
|
||||
.viewType$=${viewType$}
|
||||
.viewTypeSignal=${viewType$}
|
||||
></affine-view-dropdown-menu>`;
|
||||
},
|
||||
} as const satisfies ToolbarActionGroup<ToolbarAction>;
|
||||
@@ -366,7 +366,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.style$=${style$}
|
||||
.styleSignal=${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",
|
||||
"zod": "^3.25.76"
|
||||
"yjs": "^13.6.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"playwright": "=1.58.2",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -108,7 +108,9 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
}
|
||||
|
||||
open = () => {
|
||||
window.open(this.link, '_blank');
|
||||
const link = this.link;
|
||||
if (!link) return;
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
refreshData = () => {
|
||||
|
||||
@@ -237,7 +237,7 @@ const builtinToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewType$=${viewType$}
|
||||
.viewTypeSignal=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -282,7 +282,7 @@ const builtinToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.style$=${model.props.style$}
|
||||
.styleSignal=${model.props.style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -472,7 +472,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewType$=${viewType$}
|
||||
.viewTypeSignal=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -534,7 +534,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.style$=${style$}
|
||||
.styleSignal=${style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -591,7 +591,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.size$=${scale$}
|
||||
.sizeSignal=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -8,10 +9,9 @@ export default defineConfig({
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
name: 'chromium',
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }],
|
||||
provider: playwright(),
|
||||
isolate: false,
|
||||
providerOptions: {},
|
||||
},
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 500,
|
||||
|
||||
@@ -17,21 +17,13 @@
|
||||
"@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",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"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,7 +14,6 @@
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../widgets/slash-menu" },
|
||||
{ "path": "../../../framework/global" },
|
||||
{ "path": "../../../framework/std" },
|
||||
{ "path": "../../../framework/store" }
|
||||
]
|
||||
|
||||
@@ -26,12 +26,9 @@
|
||||
"@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"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,6 @@ import { MoreVerticalIcon } from '@blocksuite/icons/lit';
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
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,18 +81,10 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
|
||||
|
||||
createLitPortal({
|
||||
template: 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>
|
||||
<affine-code-more-menu
|
||||
.context=${this.context}
|
||||
.moreGroups=${this.moreGroups}
|
||||
></affine-code-more-menu>
|
||||
`,
|
||||
// should be greater than block-selection z-index as selection and popover wil share the same stacking context(editor-host)
|
||||
portalStyles: {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,8 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
blockComponent.setWrap(!wrapped);
|
||||
const currentWrap = blockComponent.model.props.wrap;
|
||||
blockComponent.setWrap(!currentWrap);
|
||||
}}
|
||||
aria-label=${label}
|
||||
>
|
||||
@@ -204,8 +205,10 @@ export const toggleGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
const currentLineNumber =
|
||||
blockComponent.model.props.lineNumber ?? true;
|
||||
blockComponent.store.updateBlock(blockComponent.model, {
|
||||
lineNumber: !lineNumber,
|
||||
lineNumber: !currentLineNumber,
|
||||
});
|
||||
}}
|
||||
aria-label=${label}
|
||||
|
||||
@@ -5,12 +5,14 @@ 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);
|
||||
@@ -21,6 +23,7 @@ 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,8 +45,10 @@ 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(tokens[vElement.lineIndex]);
|
||||
const lineTokens = structuredClone(line);
|
||||
if (lineTokens.length === 0) return plainContent;
|
||||
|
||||
const startOffset = vElement.startOffset;
|
||||
|
||||
@@ -21,14 +21,9 @@
|
||||
"@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",
|
||||
"zod": "^3.25.76"
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -26,13 +26,11 @@
|
||||
"@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"
|
||||
},
|
||||
|
||||
@@ -47,7 +47,7 @@ export const databasePropertyConverts = [
|
||||
propertyModelPresets.multiSelectPropertyModelConfig,
|
||||
(_property, cells) => {
|
||||
const options: Record<string, SelectTag> = {};
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
// oxlint-disable-next-line sonarjs/no-identical-functions
|
||||
const getTag = (name: string) => {
|
||||
if (options[name]) return options[name];
|
||||
const tag: SelectTag = {
|
||||
|
||||
@@ -15,17 +15,10 @@
|
||||
"@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",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../../framework/global" },
|
||||
{ "path": "../../../framework/std" },
|
||||
{ "path": "../../../framework/store" }
|
||||
]
|
||||
|
||||
@@ -11,25 +11,17 @@
|
||||
"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",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
"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,19 +23,12 @@
|
||||
"@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",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"yjs": "^13.6.27"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -25,7 +25,7 @@ export function renderLinkedDocInCard(
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||
// oxlint-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}
|
||||
.viewType$=${viewType$}
|
||||
.viewTypeSignal=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -299,7 +299,7 @@ const builtinToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.style$=${model.props.style$}
|
||||
.styleSignal=${model.props.style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -423,7 +423,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.style$=${style$}
|
||||
.styleSignal=${style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -479,7 +479,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.size$=${scale$}
|
||||
.sizeSignal=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -199,7 +199,7 @@ const conversionsActionGroup = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewType$=${viewType$}
|
||||
.viewTypeSignal=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -466,7 +466,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.size$=${scale$}
|
||||
.sizeSignal=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -13,10 +13,7 @@
|
||||
"@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:*",
|
||||
@@ -24,18 +21,10 @@
|
||||
"@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",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"yjs": "^13.6.27"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -281,7 +281,7 @@ function createBuiltinToolbarConfigForExternal(
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewType$=${viewType$}
|
||||
.viewTypeSignal=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -329,7 +329,7 @@ function createBuiltinToolbarConfigForExternal(
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.style$=${model.props.style$}
|
||||
.styleSignal=${model.props.style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -514,7 +514,7 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewType$=${viewType$}
|
||||
.viewTypeSignal=${viewType$}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -579,7 +579,7 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.style$=${style$}
|
||||
.styleSignal=${style$}
|
||||
></affine-card-style-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -646,7 +646,7 @@ const createBuiltinSurfaceToolbarConfigForExternal = (
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.size$=${scale$}
|
||||
.sizeSignal=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
@@ -202,7 +202,7 @@ export const builtinToolbarConfig = {
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.toggle=${toggle}
|
||||
.viewType$=${signal(actions[2].label)}
|
||||
.viewTypeSignal=${signal(actions[2].label)}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -408,7 +408,7 @@ export const builtinSurfaceToolbarConfig = {
|
||||
@toggle=${onToggle}
|
||||
.actions=${actions}
|
||||
.context=${ctx}
|
||||
.viewType$=${signal(actions[1].label)}
|
||||
.viewTypeSignal=${signal(actions[1].label)}
|
||||
></affine-view-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
@@ -465,7 +465,7 @@ export const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.size$=${scale$}
|
||||
.sizeSignal=${scale$}
|
||||
></affine-size-dropdown-menu>`
|
||||
)}`;
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -10,10 +10,7 @@
|
||||
{ "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" },
|
||||
|
||||
@@ -22,15 +22,10 @@
|
||||
"@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",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"yjs": "^13.6.27"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -22,14 +22,10 @@
|
||||
"@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",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -15,24 +15,16 @@
|
||||
"@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",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"remark-math": "^6.0.0"
|
||||
},
|
||||
"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,7 +35,8 @@ export const LatexTooltip = (
|
||||
katex.renderToString(latex, {
|
||||
displayMode,
|
||||
output: 'mathml',
|
||||
})
|
||||
}),
|
||||
{ USE_PROFILES: { html: true, mathMl: true } }
|
||||
)}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -12,10 +12,8 @@
|
||||
{ "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" }
|
||||
]
|
||||
|
||||
@@ -21,17 +21,9 @@
|
||||
"@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",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -30,7 +30,11 @@ export function correctNumberedListsOrderToPrev(
|
||||
|
||||
const fn = () => {
|
||||
// step 1
|
||||
const previousSibling = doc.getPrev(model);
|
||||
const parent = doc.getParent(model);
|
||||
if (!parent) return;
|
||||
const index = parent.children.indexOf(model);
|
||||
const previousSibling = index > 0 ? parent.children[index - 1] : null;
|
||||
|
||||
if (
|
||||
previousSibling &&
|
||||
matchModels(previousSibling, [ListBlockModel]) &&
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.23",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -273,7 +273,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
@select=${onSelect}
|
||||
@toggle=${onToggle}
|
||||
.format=${format}
|
||||
.size$=${scale$}
|
||||
.sizeSignal=${scale$}
|
||||
></affine-size-dropdown-menu>`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -240,7 +240,7 @@ export const TodoTooltip = html`<svg width="170" height="68" viewBox="0 0 170 68
|
||||
<text fill="#8E8D91" xml:space="preserve" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0px"><tspan x="28" y="47.6364">Make a list for building preview.</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
`
|
||||
`;
|
||||
|
||||
export const tooltips: Record<string, SlashMenuTooltip> = {
|
||||
Text: {
|
||||
|
||||
@@ -221,6 +221,12 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent(
|
||||
}
|
||||
}
|
||||
|
||||
override getCSSScaleVal(): number {
|
||||
const baseScale = super.getCSSScaleVal();
|
||||
const extraScale = this.model.props.edgeless?.scale ?? 1;
|
||||
return baseScale * extraScale;
|
||||
}
|
||||
|
||||
override getRenderingRect() {
|
||||
const { xywh, edgeless } = this.model.props;
|
||||
const { collapse, scale = 1 } = edgeless;
|
||||
@@ -255,7 +261,6 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent(
|
||||
|
||||
const style = {
|
||||
borderRadius: borderRadius + 'px',
|
||||
transform: `scale(${scale})`,
|
||||
};
|
||||
|
||||
const extra = this._editing ? ACTIVE_NOTE_EXTRA_PADDING : 0;
|
||||
@@ -454,6 +459,28 @@ export const EdgelessNoteInteraction =
|
||||
return;
|
||||
}
|
||||
|
||||
let isClickOnTitle = false;
|
||||
const titleRect = view
|
||||
.querySelector('edgeless-page-block-title')
|
||||
?.getBoundingClientRect();
|
||||
|
||||
if (titleRect) {
|
||||
const titleBound = new Bound(
|
||||
titleRect.x,
|
||||
titleRect.y,
|
||||
titleRect.width,
|
||||
titleRect.height
|
||||
);
|
||||
if (titleBound.isPointInBound([e.clientX, e.clientY])) {
|
||||
isClickOnTitle = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isClickOnTitle) {
|
||||
handleNativeRangeAtPoint(e.clientX, e.clientY);
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.children.length === 0) {
|
||||
const blockId = std.store.addBlock(
|
||||
'affine:paragraph',
|
||||
@@ -489,6 +516,9 @@ export const EdgelessNoteInteraction =
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
} else if (multiSelect && alreadySelected && editing) {
|
||||
// range selection using Shift-click when editing
|
||||
return;
|
||||
} else {
|
||||
context.default(context);
|
||||
}
|
||||
|
||||
@@ -20,13 +20,10 @@
|
||||
"@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",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -283,7 +283,7 @@ export const paragraphBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
const { walkerContext } = context;
|
||||
switch (o.node.tagName) {
|
||||
case 'div': {
|
||||
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||
// oxlint-disable-next-line sonarjs/no-collapsible-if
|
||||
if (
|
||||
o.parent?.node.type === 'element' &&
|
||||
o.parent.node.tagName !== 'li' &&
|
||||
|
||||
@@ -193,7 +193,7 @@ export const paragraphBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatch
|
||||
const { walkerContext } = context;
|
||||
switch (o.node.tagName) {
|
||||
case 'div': {
|
||||
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||
// oxlint-disable-next-line sonarjs/no-collapsible-if
|
||||
if (
|
||||
o.parent?.node.type === 'element' &&
|
||||
!(
|
||||
|
||||
@@ -29,7 +29,6 @@ import { query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
|
||||
import { ParagraphBlockConfigExtension } from './paragraph-block-config.js';
|
||||
import { paragraphBlockStyles } from './styles.js';
|
||||
@@ -251,15 +250,18 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
|
||||
|
||||
let style = html``;
|
||||
if (this.model.props.type$.value.startsWith('h') && collapsed) {
|
||||
const collapsedSiblingStyles = collapsedSiblings
|
||||
.map(
|
||||
sibling => `
|
||||
[data-block-id="${sibling.id}"] {
|
||||
display: none !important;
|
||||
}
|
||||
`
|
||||
)
|
||||
.join('\n');
|
||||
style = html`
|
||||
<style>
|
||||
${collapsedSiblings.map(sibling =>
|
||||
unsafeHTML(`
|
||||
[data-block-id="${sibling.id}"] {
|
||||
display: none !important;
|
||||
}
|
||||
`)
|
||||
)}
|
||||
${collapsedSiblingStyles}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -34,25 +34,19 @@
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/data-view": "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/lodash-es": "^4.17.12",
|
||||
"dompurify": "^3.3.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.23",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"yjs": "^13.6.27"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -544,7 +544,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
// oxlint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < blocksInsideFrame.length; i++) {
|
||||
const element = blocksInsideFrame[i];
|
||||
await _drawTopLevelBlock(element, true);
|
||||
@@ -645,7 +645,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
// oxlint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let index = 0; index < content.length; index++) {
|
||||
const blockSnapshot = content[index];
|
||||
if (blockSnapshot.flavour === 'affine:note') {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
FrameBlockModel,
|
||||
ImageBlockModel,
|
||||
isExternalEmbedModel,
|
||||
MindmapElementModel,
|
||||
NoteBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
@@ -401,7 +402,17 @@ function reorderElements(
|
||||
) {
|
||||
if (!models.length) return;
|
||||
|
||||
for (const model of models) {
|
||||
const normalizedModels = Array.from(
|
||||
new Map(
|
||||
models.map(model => {
|
||||
const reorderTarget =
|
||||
model.group instanceof MindmapElementModel ? model.group : model;
|
||||
return [reorderTarget.id, reorderTarget];
|
||||
})
|
||||
).values()
|
||||
);
|
||||
|
||||
for (const model of normalizedModels) {
|
||||
const index = ctx.gfx.layer.getReorderedIndex(model, type);
|
||||
|
||||
// block should be updated in transaction
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../widgets/edgeless-selected-rect" },
|
||||
{ "path": "../../widgets/edgeless-toolbar" },
|
||||
{ "path": "../../data-view" },
|
||||
{ "path": "../../../framework/global" },
|
||||
|
||||
@@ -22,17 +22,8 @@
|
||||
"@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",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.23",
|
||||
"nanoid": "^5.1.6",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"lit": "^3.2.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -42,7 +42,7 @@ export const FrameTooltip = html`<svg width="170" height="89" viewBox="0 0 170 8
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
`
|
||||
`;
|
||||
|
||||
// prettier-ignore
|
||||
export const MindMapTooltip = html`<svg width="170" height="106" viewBox="0 0 170 106" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -108,4 +108,4 @@ export const MindMapTooltip = html`<svg width="170" height="106" viewBox="0 0 17
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
`
|
||||
`;
|
||||
|
||||
@@ -10,17 +10,13 @@
|
||||
"author": "toeverything",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
@@ -29,11 +25,10 @@
|
||||
"nanoid": "^5.1.6",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"yjs": "^13.6.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
||||
@@ -2,16 +2,24 @@ import { type Color, ColorScheme } from '@blocksuite/affine-model';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import { requestConnectedFrame } from '@blocksuite/affine-shared/utils';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import type { IBound } from '@blocksuite/global/gfx';
|
||||
import { getBoundWithRotation, intersects } from '@blocksuite/global/gfx';
|
||||
import {
|
||||
Bound,
|
||||
getBoundWithRotation,
|
||||
type IBound,
|
||||
intersects,
|
||||
} from '@blocksuite/global/gfx';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import type {
|
||||
GfxCompatibleInterface,
|
||||
GfxController,
|
||||
GfxLocalElementModel,
|
||||
GridManager,
|
||||
LayerManager,
|
||||
SurfaceBlockModel,
|
||||
Viewport,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import { effect } from '@preact/signals-core';
|
||||
import last from 'lodash-es/last';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@@ -40,11 +48,82 @@ type RendererOptions = {
|
||||
surfaceModel: SurfaceBlockModel;
|
||||
};
|
||||
|
||||
export type CanvasRenderPassMetrics = {
|
||||
overlayCount: number;
|
||||
placeholderElementCount: number;
|
||||
renderByBoundCallCount: number;
|
||||
renderedElementCount: number;
|
||||
visibleElementCount: number;
|
||||
};
|
||||
|
||||
export type CanvasMemorySnapshot = {
|
||||
bytes: number;
|
||||
datasetLayerId: string | null;
|
||||
height: number;
|
||||
kind: 'main' | 'stacking';
|
||||
width: number;
|
||||
zIndex: string;
|
||||
};
|
||||
|
||||
export type CanvasRendererDebugMetrics = {
|
||||
canvasLayerCount: number;
|
||||
canvasMemoryBytes: number;
|
||||
canvasMemorySnapshots: CanvasMemorySnapshot[];
|
||||
canvasMemoryMegabytes: number;
|
||||
canvasPixelCount: number;
|
||||
coalescedRefreshCount: number;
|
||||
dirtyLayerRenderCount: number;
|
||||
fallbackElementCount: number;
|
||||
lastRenderDurationMs: number;
|
||||
lastRenderMetrics: CanvasRenderPassMetrics;
|
||||
maxRenderDurationMs: number;
|
||||
pooledStackingCanvasCount: number;
|
||||
refreshCount: number;
|
||||
renderCount: number;
|
||||
stackingCanvasCount: number;
|
||||
totalLayerCount: number;
|
||||
totalRenderDurationMs: number;
|
||||
visibleStackingCanvasCount: number;
|
||||
};
|
||||
|
||||
type MutableCanvasRendererDebugMetrics = Omit<
|
||||
CanvasRendererDebugMetrics,
|
||||
| 'canvasLayerCount'
|
||||
| 'canvasMemoryBytes'
|
||||
| 'canvasMemoryMegabytes'
|
||||
| 'canvasPixelCount'
|
||||
| 'canvasMemorySnapshots'
|
||||
| 'pooledStackingCanvasCount'
|
||||
| 'stackingCanvasCount'
|
||||
| 'totalLayerCount'
|
||||
| 'visibleStackingCanvasCount'
|
||||
>;
|
||||
|
||||
type RenderPassStats = CanvasRenderPassMetrics;
|
||||
|
||||
type StackingCanvasState = {
|
||||
bound: Bound | null;
|
||||
layerId: string | null;
|
||||
};
|
||||
|
||||
type RefreshTarget =
|
||||
| { type: 'all' }
|
||||
| { type: 'main' }
|
||||
| { type: 'element'; element: SurfaceElementModel | GfxLocalElementModel }
|
||||
| {
|
||||
type: 'elements';
|
||||
elements: Array<SurfaceElementModel | GfxLocalElementModel>;
|
||||
};
|
||||
|
||||
const STACKING_CANVAS_PADDING = 32;
|
||||
|
||||
export class CanvasRenderer {
|
||||
private _container!: HTMLElement;
|
||||
|
||||
private readonly _disposables = new DisposableGroup();
|
||||
|
||||
private readonly _gfx: GfxController;
|
||||
|
||||
private readonly _turboEnabled: () => boolean;
|
||||
|
||||
private readonly _overlays = new Set<Overlay>();
|
||||
@@ -53,6 +132,37 @@ export class CanvasRenderer {
|
||||
|
||||
private _stackingCanvas: HTMLCanvasElement[] = [];
|
||||
|
||||
private readonly _stackingCanvasPool: HTMLCanvasElement[] = [];
|
||||
|
||||
private readonly _stackingCanvasState = new WeakMap<
|
||||
HTMLCanvasElement,
|
||||
StackingCanvasState
|
||||
>();
|
||||
|
||||
private readonly _dirtyStackingCanvasIndexes = new Set<number>();
|
||||
|
||||
private _mainCanvasDirty = true;
|
||||
|
||||
private _needsFullRender = true;
|
||||
|
||||
private _debugMetrics: MutableCanvasRendererDebugMetrics = {
|
||||
refreshCount: 0,
|
||||
coalescedRefreshCount: 0,
|
||||
renderCount: 0,
|
||||
totalRenderDurationMs: 0,
|
||||
lastRenderDurationMs: 0,
|
||||
maxRenderDurationMs: 0,
|
||||
lastRenderMetrics: {
|
||||
renderByBoundCallCount: 0,
|
||||
visibleElementCount: 0,
|
||||
renderedElementCount: 0,
|
||||
placeholderElementCount: 0,
|
||||
overlayCount: 0,
|
||||
},
|
||||
dirtyLayerRenderCount: 0,
|
||||
fallbackElementCount: 0,
|
||||
};
|
||||
|
||||
canvas: HTMLCanvasElement;
|
||||
|
||||
ctx: CanvasRenderingContext2D;
|
||||
@@ -89,6 +199,7 @@ export class CanvasRenderer {
|
||||
this.layerManager = options.layerManager;
|
||||
this.grid = options.gridManager;
|
||||
this.provider = options.provider ?? {};
|
||||
this._gfx = this.std.get(GfxControllerIdentifier);
|
||||
|
||||
this._turboEnabled = () => {
|
||||
const featureFlagService = options.std.get(FeatureFlagService);
|
||||
@@ -132,15 +243,199 @@ export class CanvasRenderer {
|
||||
};
|
||||
}
|
||||
|
||||
private _applyStackingCanvasLayout(
|
||||
canvas: HTMLCanvasElement,
|
||||
bound: Bound | null,
|
||||
dpr = window.devicePixelRatio
|
||||
) {
|
||||
const state =
|
||||
this._stackingCanvasState.get(canvas) ??
|
||||
({
|
||||
bound: null,
|
||||
layerId: canvas.dataset.layerId ?? null,
|
||||
} satisfies StackingCanvasState);
|
||||
|
||||
if (!bound || bound.w <= 0 || bound.h <= 0) {
|
||||
canvas.style.display = 'none';
|
||||
canvas.style.left = '0px';
|
||||
canvas.style.top = '0px';
|
||||
canvas.style.width = '0px';
|
||||
canvas.style.height = '0px';
|
||||
canvas.style.transform = '';
|
||||
canvas.width = 0;
|
||||
canvas.height = 0;
|
||||
state.bound = null;
|
||||
state.layerId = canvas.dataset.layerId ?? null;
|
||||
this._stackingCanvasState.set(canvas, state);
|
||||
return;
|
||||
}
|
||||
|
||||
const { viewportBounds, zoom, viewScale } = this.viewport;
|
||||
const width = bound.w * zoom;
|
||||
const height = bound.h * zoom;
|
||||
const left = (bound.x - viewportBounds.x) * zoom;
|
||||
const top = (bound.y - viewportBounds.y) * zoom;
|
||||
const actualWidth = Math.max(1, Math.ceil(width * dpr));
|
||||
const actualHeight = Math.max(1, Math.ceil(height * dpr));
|
||||
const transform = `translate(${left}px, ${top}px) scale(${1 / viewScale})`;
|
||||
|
||||
if (canvas.style.display !== 'block') {
|
||||
canvas.style.display = 'block';
|
||||
}
|
||||
if (canvas.style.left !== '0px') {
|
||||
canvas.style.left = '0px';
|
||||
}
|
||||
if (canvas.style.top !== '0px') {
|
||||
canvas.style.top = '0px';
|
||||
}
|
||||
if (canvas.style.width !== `${width}px`) {
|
||||
canvas.style.width = `${width}px`;
|
||||
}
|
||||
if (canvas.style.height !== `${height}px`) {
|
||||
canvas.style.height = `${height}px`;
|
||||
}
|
||||
if (canvas.style.transform !== transform) {
|
||||
canvas.style.transform = transform;
|
||||
}
|
||||
if (canvas.style.transformOrigin !== 'top left') {
|
||||
canvas.style.transformOrigin = 'top left';
|
||||
}
|
||||
|
||||
if (canvas.width !== actualWidth) {
|
||||
canvas.width = actualWidth;
|
||||
}
|
||||
|
||||
if (canvas.height !== actualHeight) {
|
||||
canvas.height = actualHeight;
|
||||
}
|
||||
|
||||
state.bound = bound;
|
||||
state.layerId = canvas.dataset.layerId ?? null;
|
||||
this._stackingCanvasState.set(canvas, state);
|
||||
}
|
||||
|
||||
private _clampBoundToViewport(bound: Bound, viewportBounds: Bound) {
|
||||
const minX = Math.max(bound.x, viewportBounds.x);
|
||||
const minY = Math.max(bound.y, viewportBounds.y);
|
||||
const maxX = Math.min(bound.maxX, viewportBounds.maxX);
|
||||
const maxY = Math.min(bound.maxY, viewportBounds.maxY);
|
||||
|
||||
if (maxX <= minX || maxY <= minY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Bound(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
private _createCanvasForLayer(
|
||||
onCreated?: (canvas: HTMLCanvasElement) => void
|
||||
) {
|
||||
const reused = this._stackingCanvasPool.pop();
|
||||
|
||||
if (reused) {
|
||||
return reused;
|
||||
}
|
||||
|
||||
const created = document.createElement('canvas');
|
||||
onCreated?.(created);
|
||||
return created;
|
||||
}
|
||||
|
||||
private _findLayerIndexByElement(
|
||||
element: SurfaceElementModel | GfxLocalElementModel
|
||||
) {
|
||||
const canvasLayers = this.layerManager.getCanvasLayers();
|
||||
const index = canvasLayers.findIndex(layer =>
|
||||
layer.elements.some(layerElement => layerElement.id === element.id)
|
||||
);
|
||||
|
||||
return index === -1 ? null : index;
|
||||
}
|
||||
|
||||
private _getLayerRenderBound(
|
||||
elements: SurfaceElementModel[],
|
||||
viewportBounds: Bound
|
||||
) {
|
||||
let layerBound: Bound | null = null;
|
||||
|
||||
for (const element of elements) {
|
||||
const display = (element.display ?? true) && !element.hidden;
|
||||
|
||||
if (!display) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const elementBound = Bound.from(getBoundWithRotation(element));
|
||||
|
||||
if (!intersects(elementBound, viewportBounds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
layerBound = layerBound ? layerBound.unite(elementBound) : elementBound;
|
||||
}
|
||||
|
||||
if (!layerBound) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._clampBoundToViewport(
|
||||
layerBound.expand(STACKING_CANVAS_PADDING),
|
||||
viewportBounds
|
||||
);
|
||||
}
|
||||
|
||||
private _getResolvedStackingCanvasBound(
|
||||
canvas: HTMLCanvasElement,
|
||||
bound: Bound | null
|
||||
) {
|
||||
if (!bound || !this._gfx.tool.dragging$.peek()) {
|
||||
return bound;
|
||||
}
|
||||
|
||||
const previousBound = this._stackingCanvasState.get(canvas)?.bound;
|
||||
|
||||
return previousBound ? previousBound.unite(bound) : bound;
|
||||
}
|
||||
|
||||
private _invalidate(target: RefreshTarget = { type: 'all' }) {
|
||||
if (target.type === 'all') {
|
||||
this._needsFullRender = true;
|
||||
this._mainCanvasDirty = true;
|
||||
this._dirtyStackingCanvasIndexes.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._needsFullRender) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.type === 'main') {
|
||||
this._mainCanvasDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const elements =
|
||||
target.type === 'element' ? [target.element] : target.elements;
|
||||
|
||||
for (const element of elements) {
|
||||
const layerIndex = this._findLayerIndexByElement(element);
|
||||
|
||||
if (layerIndex === null || layerIndex >= this._stackingCanvas.length) {
|
||||
this._mainCanvasDirty = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
this._dirtyStackingCanvasIndexes.add(layerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private _resetPooledCanvas(canvas: HTMLCanvasElement) {
|
||||
canvas.dataset.layerId = '';
|
||||
this._applyStackingCanvasLayout(canvas, null);
|
||||
}
|
||||
|
||||
private _initStackingCanvas(onCreated?: (canvas: HTMLCanvasElement) => void) {
|
||||
const layer = this.layerManager;
|
||||
const updateStackingCanvasSize = (canvases: HTMLCanvasElement[]) => {
|
||||
this._stackingCanvas = canvases;
|
||||
|
||||
const sizeUpdater = this._canvasSizeUpdater();
|
||||
|
||||
canvases.filter(sizeUpdater.filter).forEach(sizeUpdater.update);
|
||||
};
|
||||
const updateStackingCanvas = () => {
|
||||
/**
|
||||
* we already have a main canvas, so the last layer should be skipped
|
||||
@@ -159,11 +454,7 @@ export class CanvasRenderer {
|
||||
const created = i < currentCanvases.length;
|
||||
const canvas = created
|
||||
? currentCanvases[i]
|
||||
: document.createElement('canvas');
|
||||
|
||||
if (!created) {
|
||||
onCreated?.(canvas);
|
||||
}
|
||||
: this._createCanvasForLayer(onCreated);
|
||||
|
||||
canvas.dataset.layerId = `[${layer.indexes[0]}--${layer.indexes[1]}]`;
|
||||
canvas.style.zIndex = layer.zIndex.toString();
|
||||
@@ -171,7 +462,6 @@ export class CanvasRenderer {
|
||||
}
|
||||
|
||||
this._stackingCanvas = canvases;
|
||||
updateStackingCanvasSize(canvases);
|
||||
|
||||
if (currentCanvases.length !== canvases.length) {
|
||||
const diff = canvases.length - currentCanvases.length;
|
||||
@@ -189,12 +479,16 @@ export class CanvasRenderer {
|
||||
payload.added = canvases.slice(-diff);
|
||||
} else {
|
||||
payload.removed = currentCanvases.slice(diff);
|
||||
payload.removed.forEach(canvas => {
|
||||
this._resetPooledCanvas(canvas);
|
||||
this._stackingCanvasPool.push(canvas);
|
||||
});
|
||||
}
|
||||
|
||||
this.stackingCanvasUpdated.next(payload);
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
this.refresh({ type: 'all' });
|
||||
};
|
||||
|
||||
this._disposables.add(
|
||||
@@ -211,7 +505,7 @@ export class CanvasRenderer {
|
||||
|
||||
this._disposables.add(
|
||||
this.viewport.viewportUpdated.subscribe(() => {
|
||||
this.refresh();
|
||||
this.refresh({ type: 'all' });
|
||||
})
|
||||
);
|
||||
|
||||
@@ -222,7 +516,6 @@ export class CanvasRenderer {
|
||||
sizeUpdatedRafId = null;
|
||||
this._resetSize();
|
||||
this._render();
|
||||
this.refresh();
|
||||
}, this._container);
|
||||
})
|
||||
);
|
||||
@@ -233,69 +526,212 @@ export class CanvasRenderer {
|
||||
|
||||
if (this.usePlaceholder !== shouldRenderPlaceholders) {
|
||||
this.usePlaceholder = shouldRenderPlaceholders;
|
||||
this.refresh();
|
||||
this.refresh({ type: 'all' });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
let wasDragging = false;
|
||||
this._disposables.add(
|
||||
effect(() => {
|
||||
const isDragging = this._gfx.tool.dragging$.value;
|
||||
|
||||
if (wasDragging && !isDragging) {
|
||||
this.refresh({ type: 'all' });
|
||||
}
|
||||
|
||||
wasDragging = isDragging;
|
||||
})
|
||||
);
|
||||
|
||||
this.usePlaceholder = false;
|
||||
}
|
||||
|
||||
private _createRenderPassStats(): RenderPassStats {
|
||||
return {
|
||||
renderByBoundCallCount: 0,
|
||||
visibleElementCount: 0,
|
||||
renderedElementCount: 0,
|
||||
placeholderElementCount: 0,
|
||||
overlayCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
private _getCanvasMemorySnapshots(): CanvasMemorySnapshot[] {
|
||||
return [this.canvas, ...this._stackingCanvas].map((canvas, index) => {
|
||||
return {
|
||||
kind: index === 0 ? 'main' : 'stacking',
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
bytes: canvas.width * canvas.height * 4,
|
||||
zIndex: canvas.style.zIndex,
|
||||
datasetLayerId: canvas.dataset.layerId ?? null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private _render() {
|
||||
const renderStart = performance.now();
|
||||
const { viewportBounds, zoom } = this.viewport;
|
||||
const { ctx } = this;
|
||||
const dpr = window.devicePixelRatio;
|
||||
const scale = zoom * dpr;
|
||||
const matrix = new DOMMatrix().scaleSelf(scale);
|
||||
const renderStats = this._createRenderPassStats();
|
||||
const fullRender = this._needsFullRender;
|
||||
const stackingIndexesToRender = fullRender
|
||||
? this._stackingCanvas.map((_, idx) => idx)
|
||||
: [...this._dirtyStackingCanvasIndexes];
|
||||
/**
|
||||
* if a layer does not have a corresponding canvas
|
||||
* its element will be add to this array and drawing on the
|
||||
* main canvas
|
||||
*/
|
||||
let fallbackElement: SurfaceElementModel[] = [];
|
||||
const allCanvasLayers = this.layerManager.getCanvasLayers();
|
||||
const viewportBound = Bound.from(viewportBounds);
|
||||
|
||||
this.layerManager.getCanvasLayers().forEach((layer, idx) => {
|
||||
if (!this._stackingCanvas[idx]) {
|
||||
fallbackElement = fallbackElement.concat(layer.elements);
|
||||
return;
|
||||
for (const idx of stackingIndexesToRender) {
|
||||
const layer = allCanvasLayers[idx];
|
||||
const canvas = this._stackingCanvas[idx];
|
||||
|
||||
if (!layer || !canvas) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const canvas = this._stackingCanvas[idx];
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
const rc = new RoughCanvas(ctx.canvas);
|
||||
const layerRenderBound = this._getLayerRenderBound(
|
||||
layer.elements,
|
||||
viewportBound
|
||||
);
|
||||
const resolvedLayerRenderBound = this._getResolvedStackingCanvasBound(
|
||||
canvas,
|
||||
layerRenderBound
|
||||
);
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this._applyStackingCanvasLayout(canvas, resolvedLayerRenderBound);
|
||||
|
||||
if (
|
||||
!resolvedLayerRenderBound ||
|
||||
canvas.width === 0 ||
|
||||
canvas.height === 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const layerCtx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
const layerRc = new RoughCanvas(layerCtx.canvas);
|
||||
|
||||
layerCtx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
layerCtx.save();
|
||||
layerCtx.setTransform(matrix);
|
||||
|
||||
this._renderByBound(
|
||||
layerCtx,
|
||||
matrix,
|
||||
layerRc,
|
||||
resolvedLayerRenderBound,
|
||||
layer.elements,
|
||||
false,
|
||||
renderStats
|
||||
);
|
||||
}
|
||||
|
||||
if (fullRender || this._mainCanvasDirty) {
|
||||
allCanvasLayers.forEach((layer, idx) => {
|
||||
if (!this._stackingCanvas[idx]) {
|
||||
fallbackElement = fallbackElement.concat(layer.elements);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
ctx.save();
|
||||
ctx.setTransform(matrix);
|
||||
|
||||
this._renderByBound(ctx, matrix, rc, viewportBounds, layer.elements);
|
||||
});
|
||||
this._renderByBound(
|
||||
ctx,
|
||||
matrix,
|
||||
new RoughCanvas(ctx.canvas),
|
||||
viewportBounds,
|
||||
fallbackElement,
|
||||
true,
|
||||
renderStats
|
||||
);
|
||||
}
|
||||
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
ctx.save();
|
||||
|
||||
ctx.setTransform(matrix);
|
||||
|
||||
this._renderByBound(
|
||||
ctx,
|
||||
matrix,
|
||||
new RoughCanvas(ctx.canvas),
|
||||
viewportBounds,
|
||||
fallbackElement,
|
||||
true
|
||||
const canvasMemorySnapshots = this._getCanvasMemorySnapshots();
|
||||
const canvasMemoryBytes = canvasMemorySnapshots.reduce(
|
||||
(sum, snapshot) => sum + snapshot.bytes,
|
||||
0
|
||||
);
|
||||
const layerTypes = this.layerManager.layers.map(layer => layer.type);
|
||||
const renderDurationMs = performance.now() - renderStart;
|
||||
|
||||
this._debugMetrics.renderCount += 1;
|
||||
this._debugMetrics.totalRenderDurationMs += renderDurationMs;
|
||||
this._debugMetrics.lastRenderDurationMs = renderDurationMs;
|
||||
this._debugMetrics.maxRenderDurationMs = Math.max(
|
||||
this._debugMetrics.maxRenderDurationMs,
|
||||
renderDurationMs
|
||||
);
|
||||
this._debugMetrics.lastRenderMetrics = renderStats;
|
||||
this._debugMetrics.fallbackElementCount = fallbackElement.length;
|
||||
this._debugMetrics.dirtyLayerRenderCount = stackingIndexesToRender.length;
|
||||
|
||||
this._lastDebugSnapshot = {
|
||||
canvasMemorySnapshots,
|
||||
canvasMemoryBytes,
|
||||
canvasPixelCount: canvasMemorySnapshots.reduce(
|
||||
(sum, snapshot) => sum + snapshot.width * snapshot.height,
|
||||
0
|
||||
),
|
||||
stackingCanvasCount: this._stackingCanvas.length,
|
||||
canvasLayerCount: layerTypes.filter(type => type === 'canvas').length,
|
||||
totalLayerCount: layerTypes.length,
|
||||
pooledStackingCanvasCount: this._stackingCanvasPool.length,
|
||||
visibleStackingCanvasCount: this._stackingCanvas.filter(
|
||||
canvas => canvas.width > 0 && canvas.height > 0
|
||||
).length,
|
||||
};
|
||||
|
||||
this._needsFullRender = false;
|
||||
this._mainCanvasDirty = false;
|
||||
this._dirtyStackingCanvasIndexes.clear();
|
||||
}
|
||||
|
||||
private _lastDebugSnapshot: Pick<
|
||||
CanvasRendererDebugMetrics,
|
||||
| 'canvasMemoryBytes'
|
||||
| 'canvasMemorySnapshots'
|
||||
| 'canvasPixelCount'
|
||||
| 'canvasLayerCount'
|
||||
| 'pooledStackingCanvasCount'
|
||||
| 'stackingCanvasCount'
|
||||
| 'totalLayerCount'
|
||||
| 'visibleStackingCanvasCount'
|
||||
> = {
|
||||
canvasMemoryBytes: 0,
|
||||
canvasMemorySnapshots: [],
|
||||
canvasPixelCount: 0,
|
||||
canvasLayerCount: 0,
|
||||
pooledStackingCanvasCount: 0,
|
||||
stackingCanvasCount: 0,
|
||||
totalLayerCount: 0,
|
||||
visibleStackingCanvasCount: 0,
|
||||
};
|
||||
|
||||
private _renderByBound(
|
||||
ctx: CanvasRenderingContext2D | null,
|
||||
matrix: DOMMatrix,
|
||||
rc: RoughCanvas,
|
||||
bound: IBound,
|
||||
surfaceElements?: SurfaceElementModel[],
|
||||
overLay: boolean = false
|
||||
overLay: boolean = false,
|
||||
renderStats?: RenderPassStats
|
||||
) {
|
||||
if (!ctx) return;
|
||||
|
||||
renderStats && (renderStats.renderByBoundCallCount += 1);
|
||||
|
||||
const elements =
|
||||
surfaceElements ??
|
||||
(this.grid.search(bound, {
|
||||
@@ -305,10 +741,12 @@ export class CanvasRenderer {
|
||||
for (const element of elements) {
|
||||
const display = (element.display ?? true) && !element.hidden;
|
||||
if (display && intersects(getBoundWithRotation(element), bound)) {
|
||||
renderStats && (renderStats.visibleElementCount += 1);
|
||||
if (
|
||||
this.usePlaceholder &&
|
||||
!(element as GfxCompatibleInterface).forceFullRender
|
||||
) {
|
||||
renderStats && (renderStats.placeholderElementCount += 1);
|
||||
ctx.save();
|
||||
ctx.fillStyle = 'rgba(200, 200, 200, 0.5)';
|
||||
const drawX = element.x - bound.x;
|
||||
@@ -316,6 +754,7 @@ export class CanvasRenderer {
|
||||
ctx.fillRect(drawX, drawY, element.w, element.h);
|
||||
ctx.restore();
|
||||
} else {
|
||||
renderStats && (renderStats.renderedElementCount += 1);
|
||||
ctx.save();
|
||||
const renderFn = this.std.getOptional<ElementRenderer>(
|
||||
ElementRendererIdentifier(element.type)
|
||||
@@ -333,6 +772,7 @@ export class CanvasRenderer {
|
||||
}
|
||||
|
||||
if (overLay) {
|
||||
renderStats && (renderStats.overlayCount += this._overlays.size);
|
||||
for (const overlay of this._overlays) {
|
||||
ctx.save();
|
||||
ctx.translate(-bound.x, -bound.y);
|
||||
@@ -348,33 +788,38 @@ export class CanvasRenderer {
|
||||
const sizeUpdater = this._canvasSizeUpdater();
|
||||
|
||||
sizeUpdater.update(this.canvas);
|
||||
|
||||
this._stackingCanvas.forEach(sizeUpdater.update);
|
||||
this.refresh();
|
||||
this._invalidate({ type: 'all' });
|
||||
}
|
||||
|
||||
private _watchSurface(surfaceModel: SurfaceBlockModel) {
|
||||
this._disposables.add(
|
||||
surfaceModel.elementAdded.subscribe(() => this.refresh())
|
||||
surfaceModel.elementAdded.subscribe(() => this.refresh({ type: 'all' }))
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.elementRemoved.subscribe(() => this.refresh())
|
||||
surfaceModel.elementRemoved.subscribe(() => this.refresh({ type: 'all' }))
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.localElementAdded.subscribe(() => this.refresh())
|
||||
surfaceModel.localElementAdded.subscribe(() =>
|
||||
this.refresh({ type: 'all' })
|
||||
)
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.localElementDeleted.subscribe(() => this.refresh())
|
||||
surfaceModel.localElementDeleted.subscribe(() =>
|
||||
this.refresh({ type: 'all' })
|
||||
)
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.localElementUpdated.subscribe(() => this.refresh())
|
||||
surfaceModel.localElementUpdated.subscribe(({ model }) => {
|
||||
this.refresh({ type: 'element', element: model });
|
||||
})
|
||||
);
|
||||
|
||||
this._disposables.add(
|
||||
surfaceModel.elementUpdated.subscribe(payload => {
|
||||
// ignore externalXYWH update cause it's updated by the renderer
|
||||
if (payload.props['externalXYWH']) return;
|
||||
this.refresh();
|
||||
const element = surfaceModel.getElementById(payload.id);
|
||||
this.refresh(element ? { type: 'element', element } : { type: 'all' });
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -382,7 +827,7 @@ export class CanvasRenderer {
|
||||
addOverlay(overlay: Overlay) {
|
||||
overlay.setRenderer(this);
|
||||
this._overlays.add(overlay);
|
||||
this.refresh();
|
||||
this.refresh({ type: 'main' });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,7 +839,7 @@ export class CanvasRenderer {
|
||||
container.append(this.canvas);
|
||||
|
||||
this._resetSize();
|
||||
this.refresh();
|
||||
this.refresh({ type: 'all' });
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -453,8 +898,46 @@ export class CanvasRenderer {
|
||||
return this.provider.getPropertyValue?.(property) ?? '';
|
||||
}
|
||||
|
||||
refresh() {
|
||||
if (this._refreshRafId !== null) return;
|
||||
getDebugMetrics(): CanvasRendererDebugMetrics {
|
||||
return {
|
||||
...this._debugMetrics,
|
||||
...this._lastDebugSnapshot,
|
||||
canvasMemoryMegabytes:
|
||||
this._lastDebugSnapshot.canvasMemoryBytes / 1024 / 1024,
|
||||
};
|
||||
}
|
||||
|
||||
resetDebugMetrics() {
|
||||
this._debugMetrics = {
|
||||
refreshCount: 0,
|
||||
coalescedRefreshCount: 0,
|
||||
renderCount: 0,
|
||||
totalRenderDurationMs: 0,
|
||||
lastRenderDurationMs: 0,
|
||||
maxRenderDurationMs: 0,
|
||||
lastRenderMetrics: this._createRenderPassStats(),
|
||||
dirtyLayerRenderCount: 0,
|
||||
fallbackElementCount: 0,
|
||||
};
|
||||
this._lastDebugSnapshot = {
|
||||
canvasMemoryBytes: 0,
|
||||
canvasMemorySnapshots: [],
|
||||
canvasPixelCount: 0,
|
||||
canvasLayerCount: 0,
|
||||
pooledStackingCanvasCount: 0,
|
||||
stackingCanvasCount: 0,
|
||||
totalLayerCount: 0,
|
||||
visibleStackingCanvasCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
refresh(target: RefreshTarget = { type: 'all' }) {
|
||||
this._debugMetrics.refreshCount += 1;
|
||||
this._invalidate(target);
|
||||
if (this._refreshRafId !== null) {
|
||||
this._debugMetrics.coalescedRefreshCount += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
this._refreshRafId = requestConnectedFrame(() => {
|
||||
this._refreshRafId = null;
|
||||
@@ -469,6 +952,6 @@ export class CanvasRenderer {
|
||||
|
||||
overlay.setRenderer(null);
|
||||
this._overlays.delete(overlay);
|
||||
this.refresh();
|
||||
this.refresh({ type: 'main' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,30 +354,37 @@ export class DomRenderer {
|
||||
this._disposables.add(
|
||||
surfaceModel.elementAdded.subscribe(payload => {
|
||||
this._markElementDirty(payload.id, UpdateType.ELEMENT_ADDED);
|
||||
this._markViewportDirty();
|
||||
this.refresh();
|
||||
})
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.elementRemoved.subscribe(payload => {
|
||||
this._markElementDirty(payload.id, UpdateType.ELEMENT_REMOVED);
|
||||
this._markViewportDirty();
|
||||
this.refresh();
|
||||
})
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.localElementAdded.subscribe(payload => {
|
||||
this._markElementDirty(payload.id, UpdateType.ELEMENT_ADDED);
|
||||
this._markViewportDirty();
|
||||
this.refresh();
|
||||
})
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.localElementDeleted.subscribe(payload => {
|
||||
this._markElementDirty(payload.id, UpdateType.ELEMENT_REMOVED);
|
||||
this._markViewportDirty();
|
||||
this.refresh();
|
||||
})
|
||||
);
|
||||
this._disposables.add(
|
||||
surfaceModel.localElementUpdated.subscribe(payload => {
|
||||
this._markElementDirty(payload.model.id, UpdateType.ELEMENT_UPDATED);
|
||||
if (payload.props['index'] || payload.props['groupId']) {
|
||||
this._markViewportDirty();
|
||||
}
|
||||
this.refresh();
|
||||
})
|
||||
);
|
||||
@@ -387,6 +394,9 @@ export class DomRenderer {
|
||||
// ignore externalXYWH update cause it's updated by the renderer
|
||||
if (payload.props['externalXYWH']) return;
|
||||
this._markElementDirty(payload.id, UpdateType.ELEMENT_UPDATED);
|
||||
if (payload.props['index'] || payload.props['childIds']) {
|
||||
this._markViewportDirty();
|
||||
}
|
||||
this.refresh();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -281,7 +281,7 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
// oxlint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
override async dragStart(e: PointerEventState) {
|
||||
const { preventDefaultState, handledByView } =
|
||||
this.interactivity?.dispatchEvent('dragstart', e) ?? {};
|
||||
|
||||
@@ -153,7 +153,7 @@ export class AStarRunner {
|
||||
}
|
||||
const neighbors = this._neighbors(current);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
// oxlint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < neighbors.length; i++) {
|
||||
const next = neighbors[i];
|
||||
const curCosts = this._costSoFar.get(current);
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import type { FontFamily } from '@blocksuite/affine-model';
|
||||
import { IS_FIREFOX } from '@blocksuite/global/env';
|
||||
|
||||
function normalizeFontFamily(fontFamily: FontFamily | string): string {
|
||||
let s = fontFamily.trim();
|
||||
if (
|
||||
(s.startsWith('"') && s.endsWith('"')) ||
|
||||
(s.startsWith("'") && s.endsWith("'"))
|
||||
) {
|
||||
s = s.slice(1, -1);
|
||||
}
|
||||
return s.toLowerCase();
|
||||
}
|
||||
|
||||
export function wrapFontFamily(fontFamily: FontFamily | string): string {
|
||||
return `"${fontFamily}"`;
|
||||
}
|
||||
@@ -21,11 +32,9 @@ export const getFontFaces = IS_FIREFOX
|
||||
}
|
||||
: () => [...document.fonts.keys()];
|
||||
|
||||
export const isSameFontFamily = IS_FIREFOX
|
||||
? (fontFamily: FontFamily | string) => (fontFace: FontFace) =>
|
||||
fontFace.family === `"${fontFamily}"`
|
||||
: (fontFamily: FontFamily | string) => (fontFace: FontFace) =>
|
||||
fontFace.family === fontFamily;
|
||||
export const isSameFontFamily =
|
||||
(fontFamily: FontFamily | string) => (fontFace: FontFace) =>
|
||||
normalizeFontFamily(fontFace.family) === normalizeFontFamily(fontFamily);
|
||||
|
||||
export function getFontFacesByFontFamily(
|
||||
fontFamily: FontFamily | string
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user