mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
Compare commits
2 Commits
darksky/na
...
l-sun/enab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac76e5b949 | ||
|
|
0bc1005b96 |
@@ -5,14 +5,7 @@ rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-args=-Wl,--warn-unresolved-symbols"]
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = [
|
||||
"-C",
|
||||
"link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains",
|
||||
"-C",
|
||||
"link-args=-all_load",
|
||||
"-C",
|
||||
"link-args=-weak_framework ScreenCaptureKit",
|
||||
]
|
||||
rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains", "-C", "link-args=-all_load", "-C", "link-args=-weak_framework ScreenCaptureKit"]
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=21032
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=21031
|
||||
# https://github.com/rust-lang/rust/issues/134820
|
||||
|
||||
@@ -6,6 +6,7 @@ yarn install
|
||||
|
||||
# Build Server Dependencies
|
||||
yarn affine @affine/server-native build
|
||||
yarn affine @affine/reader build
|
||||
|
||||
# Create database
|
||||
yarn affine @affine/server prisma migrate reset -f
|
||||
|
||||
@@ -2,8 +2,6 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
image: mcr.microsoft.com/devcontainers/base:bookworm
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
|
||||
@@ -148,11 +148,6 @@
|
||||
"description": "Whether allow new registrations.\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"allowSignupForOauth": {
|
||||
"type": "boolean",
|
||||
"description": "Whether allow new registrations via configured oauth.\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"requireEmailDomainVerification": {
|
||||
"type": "boolean",
|
||||
"description": "Whether require email domain record verification before accessing restricted resources.\n@default false",
|
||||
@@ -195,11 +190,6 @@
|
||||
"type": "object",
|
||||
"description": "Configuration for mailer module",
|
||||
"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"
|
||||
},
|
||||
"SMTP.host": {
|
||||
"type": "string",
|
||||
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"\n@environment `MAILER_HOST`",
|
||||
@@ -222,12 +212,12 @@
|
||||
},
|
||||
"SMTP.sender": {
|
||||
"type": "string",
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"AFFiNE Self Hosted <noreply@example.com>\"\n@environment `MAILER_SENDER`",
|
||||
"default": "AFFiNE Self Hosted <noreply@example.com>"
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Team <noreply@affine.pro>\")\n@default \"\"\n@environment `MAILER_SENDER`",
|
||||
"default": ""
|
||||
},
|
||||
"SMTP.ignoreTLS": {
|
||||
"type": "boolean",
|
||||
"description": "Whether ignore email server's TLS certificate verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`",
|
||||
"description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`",
|
||||
"default": false
|
||||
},
|
||||
"fallbackDomains": {
|
||||
@@ -235,11 +225,6 @@
|
||||
"description": "The emails from these domains are always sent using the fallback SMTP server.\n@default []",
|
||||
"default": []
|
||||
},
|
||||
"fallbackSMTP.name": {
|
||||
"type": "string",
|
||||
"description": "Name of the fallback email server (e.g. your domain name)\n@default \"AFFiNE Server\"",
|
||||
"default": "AFFiNE Server"
|
||||
},
|
||||
"fallbackSMTP.host": {
|
||||
"type": "string",
|
||||
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"",
|
||||
@@ -262,12 +247,12 @@
|
||||
},
|
||||
"fallbackSMTP.sender": {
|
||||
"type": "string",
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"\"",
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Team <noreply@affine.pro>\")\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.ignoreTLS": {
|
||||
"type": "boolean",
|
||||
"description": "Whether ignore email server's TLS certificate verification. Enable it for self-signed certificates.\n@default false",
|
||||
"description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
@@ -397,7 +382,7 @@
|
||||
},
|
||||
"urlPrefix": {
|
||||
"type": "string",
|
||||
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
},
|
||||
"signKey": {
|
||||
"type": "string",
|
||||
@@ -518,7 +503,7 @@
|
||||
},
|
||||
"urlPrefix": {
|
||||
"type": "string",
|
||||
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
},
|
||||
"signKey": {
|
||||
"type": "string",
|
||||
@@ -611,6 +596,11 @@
|
||||
"type": "object",
|
||||
"description": "Configuration for flags module",
|
||||
"properties": {
|
||||
"earlyAccessControl": {
|
||||
"type": "boolean",
|
||||
"description": "Only allow users with early access features to access the app\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"allowGuestDemoWorkspace": {
|
||||
"type": "boolean",
|
||||
"description": "Whether allow guest users to create demo workspaces.\n@default true",
|
||||
@@ -679,16 +669,16 @@
|
||||
},
|
||||
"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\":\"claude-sonnet-4@20250514\",\"embedding\":\"gemini-embedding-001\",\"image\":\"gpt-image-1\",\"rerank\":\"gpt-4.1\",\"coding\":\"claude-sonnet-4@20250514\",\"complex_text_generation\":\"gpt-4o-2024-08-06\",\"quick_decision_making\":\"gpt-5-mini\",\"quick_text_generation\":\"gemini-2.5-flash\",\"polish_and_summarize\":\"gemini-2.5-flash\"}}",
|
||||
"default": {
|
||||
"override_enabled": false,
|
||||
"scenarios": {
|
||||
"audio_transcribing": "gemini-2.5-flash",
|
||||
"chat": "gemini-2.5-flash",
|
||||
"chat": "claude-sonnet-4@20250514",
|
||||
"embedding": "gemini-embedding-001",
|
||||
"image": "gpt-image-1",
|
||||
"rerank": "gpt-4.1",
|
||||
"coding": "claude-sonnet-4-5@20250929",
|
||||
"coding": "claude-sonnet-4@20250514",
|
||||
"complex_text_generation": "gpt-4o-2024-08-06",
|
||||
"quick_decision_making": "gpt-5-mini",
|
||||
"quick_text_generation": "gemini-2.5-flash",
|
||||
@@ -923,7 +913,7 @@
|
||||
},
|
||||
"urlPrefix": {
|
||||
"type": "string",
|
||||
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
},
|
||||
"signKey": {
|
||||
"type": "string",
|
||||
@@ -1103,33 +1093,18 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"type": "string",
|
||||
"description": "[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.\n@default \"\"\n@environment `STRIPE_API_KEY`",
|
||||
"description": "Stripe API key to enable payment service.\n@default \"\"\n@environment `STRIPE_API_KEY`",
|
||||
"default": ""
|
||||
},
|
||||
"webhookKey": {
|
||||
"type": "string",
|
||||
"description": "[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`",
|
||||
"description": "Stripe webhook key to enable payment service.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`",
|
||||
"default": ""
|
||||
},
|
||||
"stripe": {
|
||||
"type": "object",
|
||||
"description": "Stripe sdk options and credentials\n@default {\"apiKey\":\"\",\"webhookKey\":\"\"}\n@link https://docs.stripe.com/api",
|
||||
"default": {
|
||||
"apiKey": "",
|
||||
"webhookKey": ""
|
||||
}
|
||||
},
|
||||
"revenuecat": {
|
||||
"type": "object",
|
||||
"description": "RevenueCat integration configs\n@default {\"enabled\":false,\"apiKey\":\"\",\"projectId\":\"\",\"webhookAuth\":\"\",\"environment\":\"production\",\"productMap\":{}}\n@link https://www.revenuecat.com/docs/",
|
||||
"default": {
|
||||
"enabled": false,
|
||||
"apiKey": "",
|
||||
"projectId": "",
|
||||
"webhookAuth": "",
|
||||
"environment": "production",
|
||||
"productMap": {}
|
||||
}
|
||||
"description": "Stripe sdk options\n@default {}\n@link https://docs.stripe.com/api",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
8
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
@@ -74,11 +74,3 @@ body:
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
Tip: You can attach images here
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is your content generated by AI?
|
||||
description: >
|
||||
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
|
||||
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
|
||||
options:
|
||||
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
8
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
@@ -35,11 +35,3 @@ body:
|
||||
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/canary/CONTRIBUTING.md) to get started.
|
||||
options:
|
||||
- label: Yes I'd like to help by submitting a PR!
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is your content generated by AI?
|
||||
description: >
|
||||
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
|
||||
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
|
||||
options:
|
||||
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.
|
||||
|
||||
6
.github/actions/build-rust/action.yml
vendored
6
.github/actions/build-rust/action.yml
vendored
@@ -75,11 +75,7 @@ runs:
|
||||
shell: bash
|
||||
if: ${{ runner.os != 'Windows' && inputs.no-build != 'true' }}
|
||||
run: |
|
||||
if [[ "${{ inputs.target }}" == "x86_64-unknown-linux-gnu" ]]; then
|
||||
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }}
|
||||
else
|
||||
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
|
||||
fi
|
||||
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
|
||||
env:
|
||||
DEBUG: 'napi:*'
|
||||
|
||||
|
||||
5
.github/actions/server-test-env/action.yml
vendored
5
.github/actions/server-test-env/action.yml
vendored
@@ -4,6 +4,11 @@ description: 'Prepare Server Test Environment'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Bundle @affine/reader
|
||||
shell: bash
|
||||
run: |
|
||||
yarn affine @affine/reader build
|
||||
|
||||
- name: Initialize database
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
2
.github/helm/affine/Chart.yaml
vendored
2
.github/helm/affine/Chart.yaml
vendored
@@ -3,4 +3,4 @@ name: affine
|
||||
description: AFFiNE cloud chart
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.25.7"
|
||||
appVersion: "0.22.4"
|
||||
|
||||
2
.github/helm/affine/charts/doc/Chart.yaml
vendored
2
.github/helm/affine/charts/doc/Chart.yaml
vendored
@@ -3,7 +3,7 @@ name: doc
|
||||
description: AFFiNE doc server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.25.7"
|
||||
appVersion: "0.22.4"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -3,7 +3,7 @@ name: graphql
|
||||
description: AFFiNE GraphQL server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.25.7"
|
||||
appVersion: "0.22.4"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -3,7 +3,7 @@ name: renderer
|
||||
description: AFFiNE renderer server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.25.7"
|
||||
appVersion: "0.22.4"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
2
.github/helm/affine/charts/sync/Chart.yaml
vendored
2
.github/helm/affine/charts/sync/Chart.yaml
vendored
@@ -3,7 +3,7 @@ name: sync
|
||||
description: AFFiNE Sync Server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.25.7"
|
||||
appVersion: "0.22.4"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
2
.github/workflows/build-images.yml
vendored
2
.github/workflows/build-images.yml
vendored
@@ -187,6 +187,8 @@ jobs:
|
||||
path: ./packages/backend/native
|
||||
- name: List server-native files
|
||||
run: ls -alh ./packages/backend/native
|
||||
- name: Build @affine/reader
|
||||
run: yarn workspace @affine/reader build
|
||||
- name: Build Server
|
||||
run: yarn workspace @affine/server build
|
||||
- name: Upload server dist
|
||||
|
||||
11
.github/workflows/build-test.yml
vendored
11
.github/workflows/build-test.yml
vendored
@@ -19,7 +19,7 @@ env:
|
||||
APP_NAME: affine
|
||||
AFFINE_ENV: dev
|
||||
COVERAGE: true
|
||||
MACOSX_DEPLOYMENT_TARGET: '11.6'
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
DEPLOYMENT_TYPE: affine
|
||||
AFFINE_INDEXER_ENABLED: true
|
||||
|
||||
@@ -152,6 +152,11 @@ jobs:
|
||||
name: server-native.node
|
||||
path: ./packages/backend/native
|
||||
|
||||
- name: Bundle @affine/reader
|
||||
shell: bash
|
||||
run: |
|
||||
yarn workspace @affine/reader build
|
||||
|
||||
- name: Run Check
|
||||
run: |
|
||||
yarn affine init
|
||||
@@ -807,7 +812,7 @@ jobs:
|
||||
settings:
|
||||
- { target: 'x86_64-unknown-linux-gnu', os: 'ubuntu-latest' }
|
||||
- { target: 'aarch64-unknown-linux-gnu', os: 'ubuntu-24.04-arm' }
|
||||
- { target: 'x86_64-apple-darwin', os: 'macos-15-intel' }
|
||||
- { target: 'x86_64-apple-darwin', os: 'macos-13' }
|
||||
- { target: 'aarch64-apple-darwin', os: 'macos-latest' }
|
||||
- { target: 'x86_64-pc-windows-msvc', os: 'windows-latest' }
|
||||
- { target: 'aarch64-pc-windows-msvc', os: 'windows-11-arm' }
|
||||
@@ -1351,7 +1356,7 @@ jobs:
|
||||
run: |
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
|
||||
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak update
|
||||
# some flatpak deps need git protocol.file.allow
|
||||
git config --global protocol.file.allow always
|
||||
|
||||
226
.github/workflows/release-desktop-platform.yml
vendored
226
.github/workflows/release-desktop-platform.yml
vendored
@@ -1,226 +0,0 @@
|
||||
name: Release Desktop Platform
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_type:
|
||||
required: true
|
||||
type: string
|
||||
app_version:
|
||||
required: true
|
||||
type: string
|
||||
git_short_hash:
|
||||
required: true
|
||||
type: string
|
||||
runner:
|
||||
required: true
|
||||
type: string
|
||||
platform:
|
||||
required: true
|
||||
type: string
|
||||
arch:
|
||||
required: true
|
||||
type: string
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
apple_codesign:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
install_linux_deps:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
enable_scripts:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
outputs:
|
||||
files_to_be_signed:
|
||||
description: Files to be signed (Windows only)
|
||||
value: ${{ jobs.build.outputs.files_to_be_signed }}
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
security-events: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ inputs.runner }}
|
||||
outputs:
|
||||
files_to_be_signed: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
RELEASE_VERSION: ${{ inputs.app_version }}
|
||||
DEBUG: 'affine:*,napi:*'
|
||||
APP_NAME: affine
|
||||
MACOSX_DEPLOYMENT_TARGET: '12.0'
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app_version }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app_version }}
|
||||
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
|
||||
hard-link-nm: false
|
||||
nmHoistingLimits: workspaces
|
||||
enableScripts: ${{ inputs.enable_scripts }}
|
||||
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ inputs.target }}
|
||||
package: '@affine/native'
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn affine @affine/electron build
|
||||
|
||||
- name: Signing By Apple Developer ID
|
||||
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||
|
||||
- name: Install additional dependencies on Linux
|
||||
if: ${{ inputs.platform == 'linux' && inputs.install_linux_deps }}
|
||||
run: |
|
||||
df -h
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
|
||||
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak update
|
||||
# some flatpak deps need git protocol.file.allow
|
||||
git config --global protocol.file.allow always
|
||||
# clean up apt cache to save disk space
|
||||
sudo -E apt-get -y purge azure-cli* zulu* hhvm* llvm* firefox* google* dotnet* aspnetcore* powershell* adoptopenjdk* mysql* php* mongodb* moby* snap* || true
|
||||
sudo -E apt-get -qq autoremove --purge
|
||||
sudo rm -rf /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL /usr/local/lib/android
|
||||
sudo apt-get clean
|
||||
rm -rf ~/.cache/yarn ~/.npm
|
||||
df -h
|
||||
|
||||
- name: Remove nbstore node_modules (darwin/linux)
|
||||
if: ${{ inputs.platform != 'win32' }}
|
||||
shell: bash
|
||||
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
|
||||
run: |
|
||||
cargo clean
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: Remove nbstore node_modules (windows)
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: make
|
||||
if: ${{ inputs.platform != 'win32' }}
|
||||
run: yarn affine @affine/electron make --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: package
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
run: |
|
||||
yarn affine @affine/electron package --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: signing DMG
|
||||
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
|
||||
run: |
|
||||
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
|
||||
|
||||
- name: Save artifacts (mac)
|
||||
if: ${{ inputs.platform == 'darwin' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
|
||||
|
||||
- name: Save artifacts (linux)
|
||||
if: ${{ inputs.platform == 'linux' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
|
||||
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
|
||||
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
|
||||
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
|
||||
if: ${{ inputs.platform == 'linux' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
|
||||
|
||||
- name: Upload Artifact
|
||||
if: ${{ inputs.platform == 'darwin' || inputs.platform == 'linux' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: affine-${{ inputs.platform }}-${{ inputs.arch }}-builds
|
||||
path: builds
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
shell: pwsh
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
|
||||
|
||||
- name: Save packaged artifacts for signing
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packaged-${{ inputs.platform }}-${{ inputs.arch }}
|
||||
path: |
|
||||
archive.zip
|
||||
!**/*.map
|
||||
304
.github/workflows/release-desktop.yml
vendored
304
.github/workflows/release-desktop.yml
vendored
@@ -12,21 +12,6 @@ on:
|
||||
git-short-hash:
|
||||
required: true
|
||||
type: string
|
||||
desktop_macos:
|
||||
description: 'Desktop - macOS'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
desktop_windows:
|
||||
description: 'Desktop - Windows'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
desktop_linux:
|
||||
description: 'Desktop - Linux'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
@@ -40,11 +25,10 @@ env:
|
||||
RELEASE_VERSION: ${{ inputs.app-version }}
|
||||
DEBUG: 'affine:*,napi:*'
|
||||
APP_NAME: affine
|
||||
MACOSX_DEPLOYMENT_TARGET: '11.6'
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
|
||||
jobs:
|
||||
before-make:
|
||||
if: ${{ inputs.desktop_macos || inputs.desktop_windows || inputs.desktop_linux }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
steps:
|
||||
@@ -74,8 +58,7 @@ jobs:
|
||||
name: desktop-web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
make-distribution-macos:
|
||||
if: ${{ inputs.desktop_macos }}
|
||||
make-distribution:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -88,90 +71,223 @@ jobs:
|
||||
platform: darwin
|
||||
arch: arm64
|
||||
target: aarch64-apple-darwin
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: ${{ matrix.spec.runner }}
|
||||
platform: ${{ matrix.spec.platform }}
|
||||
arch: ${{ matrix.spec.arch }}
|
||||
target: ${{ matrix.spec.target }}
|
||||
apple_codesign: true
|
||||
|
||||
make-distribution-linux:
|
||||
if: ${{ inputs.desktop_linux }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
spec:
|
||||
- runner: ubuntu-latest
|
||||
platform: linux
|
||||
arch: x64
|
||||
target: x86_64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: ${{ matrix.spec.runner }}
|
||||
platform: ${{ matrix.spec.platform }}
|
||||
arch: ${{ matrix.spec.arch }}
|
||||
target: ${{ matrix.spec.target }}
|
||||
install_linux_deps: true
|
||||
environment: ${{ inputs.build-type }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app-version }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
|
||||
hard-link-nm: false
|
||||
nmHoistingLimits: workspaces
|
||||
enableScripts: false
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
package: '@affine/native'
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
package-distribution-windows-x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
enable_scripts: true
|
||||
- name: Build Desktop Layers
|
||||
run: yarn affine @affine/electron build
|
||||
|
||||
package-distribution-windows-arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
- name: Signing By Apple Developer ID
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||
|
||||
- name: Install additional dependencies on Linux
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
run: |
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak update
|
||||
# some flatpak deps need git protocol.file.allow
|
||||
git config --global protocol.file.allow always
|
||||
|
||||
- name: Remove nbstore node_modules
|
||||
shell: bash
|
||||
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
|
||||
run: |
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: make
|
||||
run: yarn affine @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: signing DMG
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
run: |
|
||||
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
|
||||
|
||||
- name: Save artifacts (mac)
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
- name: Save artifacts (linux)
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.zip
|
||||
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.appimage
|
||||
mv packages/frontend/apps/electron/out/*/make/deb/${{ matrix.spec.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.deb
|
||||
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.flatpak
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.appimage
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.deb
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
|
||||
path: builds
|
||||
|
||||
package-distribution-windows:
|
||||
environment: ${{ inputs.build-type }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
spec:
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: windows-latest
|
||||
platform: win32
|
||||
arch: arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
enable_scripts: true
|
||||
outputs:
|
||||
FILES_TO_BE_SIGNED_x64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_x64 }}
|
||||
FILES_TO_BE_SIGNED_arm64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_arm64 }}
|
||||
env:
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app-version }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
|
||||
hard-link-nm: false
|
||||
nmHoistingLimits: workspaces
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
package: '@affine/native'
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn affine @affine/electron build
|
||||
|
||||
- name: Remove nbstore node_modules
|
||||
shell: bash
|
||||
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
|
||||
run: |
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: package
|
||||
run: |
|
||||
yarn affine @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED_${{ matrix.spec.arch }}=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
|
||||
|
||||
- name: Save packaged artifacts for signing
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: |
|
||||
archive.zip
|
||||
!**/*.map
|
||||
|
||||
sign-packaged-artifacts-windows_x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: package-distribution-windows-x64
|
||||
needs: package-distribution-windows
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-x64.outputs.files_to_be_signed }}
|
||||
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_x64 }}
|
||||
artifact-name: packaged-win32-x64
|
||||
|
||||
sign-packaged-artifacts-windows_arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: package-distribution-windows-arm64
|
||||
needs: package-distribution-windows
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-arm64.outputs.files_to_be_signed }}
|
||||
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_arm64 }}
|
||||
artifact-name: packaged-win32-arm64
|
||||
|
||||
make-windows-installer:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs:
|
||||
- sign-packaged-artifacts-windows_x64
|
||||
- sign-packaged-artifacts-windows_arm64
|
||||
@@ -233,7 +349,6 @@ jobs:
|
||||
path: archive.zip
|
||||
|
||||
sign-installer-artifacts-windows-x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
@@ -241,7 +356,6 @@ jobs:
|
||||
artifact-name: installer-win32-x64
|
||||
|
||||
sign-installer-artifacts-windows-arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
@@ -249,7 +363,6 @@ jobs:
|
||||
artifact-name: installer-win32-arm64
|
||||
|
||||
finalize-installer-windows:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs:
|
||||
[
|
||||
sign-installer-artifacts-windows-x64,
|
||||
@@ -297,18 +410,17 @@ jobs:
|
||||
path: builds
|
||||
|
||||
release:
|
||||
if: ${{ inputs.desktop_macos && inputs.desktop_linux && inputs.desktop_windows }}
|
||||
needs:
|
||||
[
|
||||
before-make,
|
||||
make-distribution-macos,
|
||||
make-distribution-linux,
|
||||
finalize-installer-windows,
|
||||
]
|
||||
needs: [before-make, make-distribution, finalize-installer-windows]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-web
|
||||
path: web-static
|
||||
- name: Zip web-static
|
||||
run: zip -r web-static.zip web-static
|
||||
- name: Download Artifacts (macos-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -354,4 +466,6 @@ jobs:
|
||||
draft: ${{ inputs.build-type == 'stable' }}
|
||||
prerelease: ${{ inputs.build-type != 'stable' }}
|
||||
tag_name: v${{ env.RELEASE_VERSION}}
|
||||
files: ./release/*
|
||||
files: |
|
||||
./release/*
|
||||
./release/.env.example
|
||||
|
||||
92
.github/workflows/release.yml
vendored
92
.github/workflows/release.yml
vendored
@@ -11,18 +11,8 @@ on:
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
desktop_macos:
|
||||
description: 'Desktop - macOS'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
desktop_windows:
|
||||
description: 'Desktop - Windows'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
desktop_linux:
|
||||
description: 'Desktop - Linux'
|
||||
desktop:
|
||||
description: 'Release Desktop?'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -60,68 +50,6 @@ jobs:
|
||||
id: prepare
|
||||
uses: ./.github/actions/prepare-release
|
||||
|
||||
canary-gate:
|
||||
name: Canary Gate
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
outputs:
|
||||
SHOULD_RELEASE: ${{ steps.decide.outputs.SHOULD_RELEASE }}
|
||||
LAST_CANARY_TAG: ${{ steps.decide.outputs.LAST_CANARY_TAG }}
|
||||
LAST_CANARY_SHA: ${{ steps.decide.outputs.LAST_CANARY_SHA }}
|
||||
steps:
|
||||
- name: Decide whether to release
|
||||
id: decide
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}'
|
||||
if (buildType !== 'canary') {
|
||||
core.setOutput('SHOULD_RELEASE', 'true')
|
||||
return
|
||||
}
|
||||
|
||||
const owner = context.repo.owner
|
||||
const repo = context.repo.repo
|
||||
const currentSha = context.sha
|
||||
const canaryTagRe = /^v\d+\.\d+\.\d+-canary\.[0-9a-f]+$/i
|
||||
|
||||
let page = 1
|
||||
const perPage = 100
|
||||
let lastCanary = null
|
||||
|
||||
while (!lastCanary && page <= 10) {
|
||||
const { data } = await github.rest.repos.listTags({
|
||||
owner,
|
||||
repo,
|
||||
per_page: perPage,
|
||||
page,
|
||||
})
|
||||
|
||||
for (const tag of data) {
|
||||
if (canaryTagRe.test(tag.name)) {
|
||||
lastCanary = tag
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length < perPage) break
|
||||
page++
|
||||
}
|
||||
|
||||
if (!lastCanary) {
|
||||
core.warning('No canary tags found; proceeding with canary release.')
|
||||
core.setOutput('SHOULD_RELEASE', 'true')
|
||||
return
|
||||
}
|
||||
|
||||
core.setOutput('LAST_CANARY_TAG', lastCanary.name)
|
||||
core.setOutput('LAST_CANARY_SHA', lastCanary.commit.sha)
|
||||
|
||||
const shouldRelease = lastCanary.commit.sha !== currentSha
|
||||
core.info(`Latest canary tag ${lastCanary.name} -> ${lastCanary.commit.sha}; current ${currentSha}; should_release=${shouldRelease}`)
|
||||
core.setOutput('SHOULD_RELEASE', shouldRelease ? 'true' : 'false')
|
||||
|
||||
cloud:
|
||||
name: Release Cloud
|
||||
if: ${{ inputs.web || github.event_name != 'workflow_dispatch' }}
|
||||
@@ -136,11 +64,9 @@ jobs:
|
||||
|
||||
image:
|
||||
name: Release Docker Image
|
||||
if: ${{ needs.canary-gate.outputs.SHOULD_RELEASE == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
- canary-gate
|
||||
- cloud
|
||||
steps:
|
||||
- uses: trstringer/manual-approval@v1
|
||||
@@ -148,7 +74,7 @@ jobs:
|
||||
name: Wait for approval
|
||||
with:
|
||||
secret: ${{ secrets.GITHUB_TOKEN }}
|
||||
approvers: darkskygit,pengx17,L-Sun,EYHN
|
||||
approvers: forehalo,fengmk2,darkskygit
|
||||
minimum-approvals: 1
|
||||
fail-on-denial: true
|
||||
issue-title: Please confirm to release docker image
|
||||
@@ -176,25 +102,15 @@ jobs:
|
||||
|
||||
desktop:
|
||||
name: Release Desktop
|
||||
if: >-
|
||||
${{
|
||||
(github.event_name != 'workflow_dispatch' && needs.canary-gate.outputs.SHOULD_RELEASE == 'true') ||
|
||||
inputs.desktop_macos ||
|
||||
inputs.desktop_windows ||
|
||||
inputs.desktop_linux
|
||||
}}
|
||||
if: ${{ inputs.desktop || github.event_name != 'workflow_dispatch' }}
|
||||
needs:
|
||||
- prepare
|
||||
- canary-gate
|
||||
uses: ./.github/workflows/release-desktop.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
|
||||
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
|
||||
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
|
||||
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 }}
|
||||
|
||||
mobile:
|
||||
name: Release Mobile
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -33,9 +33,6 @@ node_modules
|
||||
!.vscode/launch.template.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Kiro
|
||||
.kiro
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
@@ -47,8 +44,6 @@ testem.log
|
||||
.pnpm-debug.log
|
||||
/typings
|
||||
tsconfig.tsbuildinfo
|
||||
rfc*.md
|
||||
todo.md
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
|
||||
10
.taplo.toml
10
.taplo.toml
@@ -1,7 +1,7 @@
|
||||
exclude = ["node_modules/**/*.toml", "target/**/*.toml"]
|
||||
include = ["./*.toml", "./packages/**/*.toml"]
|
||||
|
||||
# https://taplo.tamasfe.dev/configuration/formatter-options.html
|
||||
[formatting]
|
||||
align_entries = true
|
||||
indent_tables = true
|
||||
reorder_keys = true
|
||||
align_entries = true
|
||||
column_width = 180
|
||||
reorder_arrays = true
|
||||
reorder_keys = true
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,4 +12,4 @@ npmPublishAccess: public
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
|
||||
843
Cargo.lock
generated
843
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
224
Cargo.toml
224
Cargo.toml
@@ -13,124 +13,108 @@ members = [
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
affine_common = { path = "./packages/common/native" }
|
||||
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
|
||||
ahash = "0.8"
|
||||
anyhow = "1"
|
||||
arbitrary = { version = "1.3", features = ["derive"] }
|
||||
assert-json-diff = "2.0"
|
||||
async-lock = { version = "3.4.0", features = ["loom"] }
|
||||
base64-simd = "0.8"
|
||||
bitvec = "1.0"
|
||||
block2 = "0.6"
|
||||
byteorder = "1.5"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
core-foundation = "0.10"
|
||||
coreaudio-rs = "0.12"
|
||||
cpal = "0.15"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
criterion2 = { version = "3", default-features = false }
|
||||
crossbeam-channel = "0.5"
|
||||
dispatch2 = "0.3"
|
||||
docx-parser = { git = "https://github.com/toeverything/docx-parser" }
|
||||
dotenvy = "0.15"
|
||||
file-format = { version = "0.28", features = ["reader"] }
|
||||
homedir = "0.3"
|
||||
infer = { version = "0.19.0" }
|
||||
lasso = { version = "0.7", features = ["multi-threaded"] }
|
||||
lib0 = { version = "0.16", features = ["lib0-serde"] }
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
loom = { version = "0.7", features = ["checkpoint"] }
|
||||
memory-indexer = "0.2.1"
|
||||
mimalloc = "0.1"
|
||||
mp4parse = "0.17"
|
||||
nanoid = "0.4"
|
||||
napi = { version = "3.7.0", features = [
|
||||
"async",
|
||||
"chrono_date",
|
||||
"error_anyhow",
|
||||
"napi9",
|
||||
"serde",
|
||||
] }
|
||||
napi-build = { version = "2" }
|
||||
napi-derive = { version = "3.4" }
|
||||
nom = "8"
|
||||
notify = { version = "8", features = ["serde"] }
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = "0.3"
|
||||
ogg = "0.9"
|
||||
once_cell = "1"
|
||||
ordered-float = "5"
|
||||
parking_lot = "0.12"
|
||||
path-ext = "0.1.2"
|
||||
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
proptest = "1.3"
|
||||
proptest-derive = "0.5"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
rand_distr = "0.5"
|
||||
rayon = "1.10"
|
||||
readability = { version = "0.3.0", default-features = false }
|
||||
regex = "1.10"
|
||||
rubato = "0.16"
|
||||
screencapturekit = "0.3"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
sha3 = "0.10"
|
||||
smol_str = "0.3"
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
"chrono",
|
||||
"macros",
|
||||
"migrate",
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
"tls-rustls",
|
||||
] }
|
||||
strum_macros = "0.27.0"
|
||||
symphonia = { version = "0.5", features = ["all", "opt-simd"] }
|
||||
text-splitter = "0.27"
|
||||
thiserror = "2"
|
||||
tiktoken-rs = "0.7"
|
||||
tokio = "1.45"
|
||||
tree-sitter = { version = "0.25" }
|
||||
tree-sitter-c = { version = "0.24" }
|
||||
tree-sitter-c-sharp = { version = "0.23" }
|
||||
tree-sitter-cpp = { version = "0.23" }
|
||||
tree-sitter-go = { version = "0.23" }
|
||||
tree-sitter-java = { version = "0.23" }
|
||||
tree-sitter-javascript = { version = "0.23" }
|
||||
tree-sitter-kotlin-ng = { version = "1.1" }
|
||||
tree-sitter-python = { version = "0.23" }
|
||||
tree-sitter-rust = { version = "0.24" }
|
||||
tree-sitter-scala = { version = "0.24" }
|
||||
tree-sitter-typescript = { version = "0.23" }
|
||||
uniffi = "0.29"
|
||||
url = { version = "2.5" }
|
||||
uuid = "1.8"
|
||||
v_htmlescape = "0.15"
|
||||
windows = { version = "0.61", features = [
|
||||
"Win32_Devices_FunctionDiscovery",
|
||||
"Win32_Foundation",
|
||||
"Win32_Media_Audio",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Diagnostics_ToolHelp",
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_Variant",
|
||||
"Win32_UI_Shell_PropertiesSystem",
|
||||
] }
|
||||
windows-core = { version = "0.61" }
|
||||
y-octo = { path = "./packages/common/y-octo/core" }
|
||||
y-sync = { version = "0.4" }
|
||||
yrs = "0.23.0"
|
||||
[workspace.dependencies]
|
||||
affine_common = { path = "./packages/common/native" }
|
||||
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
|
||||
ahash = "0.8"
|
||||
anyhow = "1"
|
||||
arbitrary = { version = "1.3", features = ["derive"] }
|
||||
assert-json-diff = "2.0"
|
||||
async-lock = { version = "3.4.0", features = ["loom"] }
|
||||
base64-simd = "0.8"
|
||||
bitvec = "1.0"
|
||||
block2 = "0.6"
|
||||
byteorder = "1.5"
|
||||
cpal = "0.15"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
core-foundation = "0.10"
|
||||
coreaudio-rs = "0.12"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
criterion2 = { version = "3", default-features = false }
|
||||
crossbeam-channel = "0.5"
|
||||
dispatch2 = "0.3"
|
||||
docx-parser = { git = "https://github.com/toeverything/docx-parser" }
|
||||
dotenvy = "0.15"
|
||||
file-format = { version = "0.26", features = ["reader"] }
|
||||
homedir = "0.3"
|
||||
infer = { version = "0.19.0" }
|
||||
lasso = { version = "0.7", features = ["multi-threaded"] }
|
||||
lib0 = { version = "0.16", features = ["lib0-serde"] }
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
loom = { version = "0.7", features = ["checkpoint"] }
|
||||
mimalloc = "0.1"
|
||||
nanoid = "0.4"
|
||||
napi = { version = "3.0.0-beta.3", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
|
||||
napi-build = { version = "2" }
|
||||
napi-derive = { version = "3.0.0-beta.3" }
|
||||
nom = "8"
|
||||
notify = { version = "8", features = ["serde"] }
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = "0.3"
|
||||
once_cell = "1"
|
||||
ordered-float = "5"
|
||||
parking_lot = "0.12"
|
||||
path-ext = "0.1.2"
|
||||
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
proptest = "1.3"
|
||||
proptest-derive = "0.5"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
rand_distr = "0.5"
|
||||
rayon = "1.10"
|
||||
readability = { version = "0.3.0", default-features = false }
|
||||
regex = "1.10"
|
||||
rubato = "0.16"
|
||||
screencapturekit = "0.3"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
sha3 = "0.10"
|
||||
smol_str = "0.3"
|
||||
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "tls-rustls"] }
|
||||
strum_macros = "0.27.0"
|
||||
symphonia = { version = "0.5", features = ["all", "opt-simd"] }
|
||||
text-splitter = "0.27"
|
||||
thiserror = "2"
|
||||
tiktoken-rs = "0.7"
|
||||
tokio = "1.45"
|
||||
tree-sitter = { version = "0.25" }
|
||||
tree-sitter-c = { version = "0.24" }
|
||||
tree-sitter-c-sharp = { version = "0.23" }
|
||||
tree-sitter-cpp = { version = "0.23" }
|
||||
tree-sitter-go = { version = "0.23" }
|
||||
tree-sitter-java = { version = "0.23" }
|
||||
tree-sitter-javascript = { version = "0.23" }
|
||||
tree-sitter-kotlin-ng = { version = "1.1" }
|
||||
tree-sitter-python = { version = "0.23" }
|
||||
tree-sitter-rust = { version = "0.24" }
|
||||
tree-sitter-scala = { version = "0.24" }
|
||||
tree-sitter-typescript = { version = "0.23" }
|
||||
uniffi = "0.29"
|
||||
url = { version = "2.5" }
|
||||
uuid = "1.8"
|
||||
v_htmlescape = "0.15"
|
||||
windows = { version = "0.61", features = [
|
||||
"Win32_Devices_FunctionDiscovery",
|
||||
"Win32_UI_Shell_PropertiesSystem",
|
||||
"Win32_Media_Audio",
|
||||
"Win32_System_Variant",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_Diagnostics_ToolHelp",
|
||||
] }
|
||||
windows-core = { version = "0.61" }
|
||||
y-octo = { path = "./packages/common/y-octo/core" }
|
||||
y-sync = { version = "0.4" }
|
||||
yrs = "0.23.0"
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
@@ -141,6 +125,6 @@ lto = true
|
||||
opt-level = 3
|
||||
strip = "symbols"
|
||||
|
||||
# android uniffi bindgen requires symbols
|
||||
[profile.release.package.affine_mobile_native]
|
||||
strip = "none"
|
||||
# android uniffi bindgen requires symbols
|
||||
[profile.release.package.affine_mobile_native]
|
||||
strip = "none"
|
||||
2
LICENSE
2
LICENSE
@@ -2,7 +2,7 @@ Copyright (c) 2022-present TOEVERYTHING PTE. LTD. and its affiliates.
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/backend" and "packages/common/native" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
|
||||
- All content that resides under the "packages/backend/server" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
|
||||
- All third party components incorporated into the AFFiNE Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "MIT" license as defined in "LICENSE-MIT".
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<br>
|
||||
</h1>
|
||||
<a href="https://affine.pro/download">
|
||||
<img alt="affine logo" src="https://cdn.affine.pro/Github_hero_image2.png" style="width: 100%">
|
||||
<img alt="affine logo" src="https://cdn.affine.pro/Github_hero_image1.png" style="width: 100%">
|
||||
</a>
|
||||
<br/>
|
||||
<p align="center">
|
||||
@@ -81,7 +81,7 @@ Star us, and you will receive all release notifications from GitHub without any
|
||||
|
||||
**Multimodal AI partner ready to kick in any work**
|
||||
|
||||
- Write up professional work report? Turn an outline into expressive and presentable slides? Summary an article into a well-structured mindmap? Sorting your job plan and backlog for tasks? Or... draw and code prototype apps and web pages directly all with one prompt? With you, [AFFiNE AI](https://affine.pro/ai) pushes your creativity to the edge of your imagination, just like [Canvas AI](https://affine.pro/blog/best-canvas-ai) to generate mind map for brainstorming.
|
||||
- Write up professional work report? Turn an outline into expressive and presentable slides? Summary an article into a well-structured mindmap? Sorting your job plan and backlog for tasks? Or... draw and code prototype apps and web pages directly all with one prompt? With you, [AFFiNE AI](https://affine.pro/ai) pushes your creativity to the edge of your imagination,just like [Canvas AI](https://affine.pro/blog/best-canvas-ai) to generate mind map for brainstorming.
|
||||
|
||||
**Local-first & Real-time collaborative**
|
||||
|
||||
@@ -193,8 +193,6 @@ We would like to express our gratitude to all the individuals who have already c
|
||||
|
||||
Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiNE. Our team is diligently updating to the latest version. For more information on how to self-host AFFiNE, please refer to our [documentation](https://docs.affine.pro/self-host-affine).
|
||||
|
||||
[](https://sealos.io/products/app-store/affine)
|
||||
|
||||
[](https://template.run.claw.cloud/?openapp=system-fastdeploy%3FtemplateName%3Daffine)
|
||||
|
||||
## Hiring
|
||||
|
||||
11
SECURITY.md
11
SECURITY.md
@@ -6,14 +6,15 @@ We recommend users to always use the latest major version. Security updates will
|
||||
|
||||
| Version | Supported |
|
||||
| --------------- | ------------------ |
|
||||
| 0.25.x (stable) | :white_check_mark: |
|
||||
| < 0.25.x | :x: |
|
||||
| 0.17.x (stable) | :white_check_mark: |
|
||||
| < 0.17.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We welcome you to provide us with bug reports via and email at [security@toeverything.info](mailto:security@toeverything.info) or submit directly on [GitHub](https://github.com/toeverything/AFFiNE/security), **we encourage you to submit the relevant information directly via GitHub**. We expect your report to contain at least the following for us to evaluate and reproduce:
|
||||
We welcome you to provide us with bug reports via and email at [security@toeverything.info](mailto:security@toeverything.info). We expect your report to contain at least the following for us to evaluate and reproduce:
|
||||
|
||||
1. Using platform and version, for example:
|
||||
|
||||
- macos arm64 0.12.0-canary-202402220729-0868ac6
|
||||
- app.affine.pro 0.12.0-canary-202402220729-0868ac6
|
||||
|
||||
@@ -21,6 +22,8 @@ We welcome you to provide us with bug reports via and email at [security@toevery
|
||||
|
||||
3. Your classification or analysis of the vulnerability (optional)
|
||||
|
||||
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.
|
||||
Since we are an open source project, we also welcome you to provide corresponding fix PRs.
|
||||
|
||||
We will provide bounties for vulnerabilities involving user information leakage, permission leakage, and unauthorized code execution. For other types of vulnerabilities, we will determine specific rewards based on the evaluation results.
|
||||
|
||||
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.
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@blocksuite/sync": "workspace:*",
|
||||
"rxjs": "^7.8.2"
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -296,10 +296,10 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7",
|
||||
"version": "0.22.4",
|
||||
"devDependencies": {
|
||||
"@vanilla-extract/vite-plugin": "^5.0.0",
|
||||
"msw": "^2.12.4",
|
||||
"vitest": "^3.2.4"
|
||||
"msw": "^2.8.4",
|
||||
"vitest": "3.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2214,7 +2214,7 @@ describe('html to snapshot', () => {
|
||||
|
||||
test('iframe', async () => {
|
||||
const html = template(
|
||||
`<iframe width="560" height="315" src="https://www.youtube.com/embed/QDsd0nyzwz0?start=&end=" title="YouTube video player" frameborder="0" allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>`
|
||||
`<iframe width="560" height="315" src="https://www.youtube.com/embed/QDsd0nyzwz0?start=&end=" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>`
|
||||
);
|
||||
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||
import {
|
||||
DefaultTheme,
|
||||
NoteDisplayMode,
|
||||
@@ -17,15 +16,12 @@ import type {
|
||||
SliceSnapshot,
|
||||
TransformerMiddleware,
|
||||
} from '@blocksuite/store';
|
||||
import { AssetsManager, MemoryBlobCRUD, Schema } from '@blocksuite/store';
|
||||
import { TestWorkspace } from '@blocksuite/store/test';
|
||||
import { AssetsManager, MemoryBlobCRUD } from '@blocksuite/store';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { AffineSchemas } from '../../schemas.js';
|
||||
import { createJob } from '../utils/create-job.js';
|
||||
import { getProvider } from '../utils/get-provider.js';
|
||||
import { nanoidReplacement } from '../utils/nanoid-replacement.js';
|
||||
import { testStoreExtensions } from '../utils/store.js';
|
||||
|
||||
const provider = getProvider();
|
||||
|
||||
@@ -94,39 +90,6 @@ describe('snapshot to markdown', () => {
|
||||
expect(target.file).toBe(markdown);
|
||||
});
|
||||
|
||||
test('imports frontmatter metadata into doc meta', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const markdown = `---
|
||||
title: Web developer
|
||||
created: 2018-04-12T09:51:00
|
||||
updated: 2018-04-12T10:00:00
|
||||
tags: [a, b]
|
||||
favorite: true
|
||||
---
|
||||
Hello world
|
||||
`;
|
||||
|
||||
const docId = await MarkdownTransformer.importMarkdownToDoc({
|
||||
collection,
|
||||
schema,
|
||||
markdown,
|
||||
fileName: 'fallback-title',
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
|
||||
expect(docId).toBeTruthy();
|
||||
const meta = collection.meta.getDocMeta(docId!);
|
||||
expect(meta?.title).toBe('Web developer');
|
||||
expect(meta?.createDate).toBe(Date.parse('2018-04-12T09:51:00'));
|
||||
expect(meta?.updatedDate).toBe(Date.parse('2018-04-12T10:00:00'));
|
||||
expect(meta?.favorite).toBe(true);
|
||||
expect(meta?.tags).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
test('paragraph', async () => {
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
@@ -3033,50 +2996,6 @@ describe('markdown to snapshot', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('html inline color span imports to nearest supported text color', async () => {
|
||||
const markdown = `<span style="color: #00afde;">Hello</span>`;
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
type: 'text',
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'Hello',
|
||||
attributes: {
|
||||
color: 'var(--affine-v2-text-highlight-fg-blue)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('paragraph', async () => {
|
||||
const markdown = `aaa
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -26,9 +26,9 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"file-type": "^21.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.23",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -45,5 +45,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@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",
|
||||
@@ -30,9 +29,9 @@
|
||||
"@types/mdast": "^4.0.4",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -45,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
export const calloutHostStyles = css({
|
||||
display: 'block',
|
||||
margin: '8px 0',
|
||||
});
|
||||
|
||||
export const calloutBlockContainerStyles = css({
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
padding: '5px 10px',
|
||||
borderRadius: '8px',
|
||||
});
|
||||
|
||||
export const calloutEmojiContainerStyles = css({
|
||||
userSelect: 'none',
|
||||
fontSize: '1.2em',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
// marginTop is dynamically set by JavaScript based on first child's height
|
||||
marginBottom: '10px',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const calloutEmojiStyles = css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
opacity: 0.7,
|
||||
},
|
||||
});
|
||||
|
||||
export const calloutChildrenStyles = css({
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
paddingLeft: '10px',
|
||||
});
|
||||
|
||||
export const iconPickerContainerStyles = css({
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
left: 0,
|
||||
zIndex: 1000,
|
||||
background: 'white',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
width: '390px',
|
||||
height: '400px',
|
||||
});
|
||||
@@ -1,191 +1,84 @@
|
||||
import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
|
||||
import {
|
||||
createPopup,
|
||||
popupTargetFromElement,
|
||||
} from '@blocksuite/affine-components/context-menu';
|
||||
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
||||
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
|
||||
import {
|
||||
type CalloutBlockModel,
|
||||
type ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { type CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
DocModeProvider,
|
||||
type IconData,
|
||||
IconPickerServiceIdentifier,
|
||||
IconType,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
import * as icons from '@blocksuite/icons/lit';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import type { BlockComponent } from '@blocksuite/std';
|
||||
import { type Signal } from '@preact/signals-core';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import type { TemplateResult } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import {
|
||||
calloutBlockContainerStyles,
|
||||
calloutChildrenStyles,
|
||||
calloutEmojiContainerStyles,
|
||||
calloutEmojiStyles,
|
||||
calloutHostStyles,
|
||||
} from './callout-block-styles.js';
|
||||
import { IconPickerWrapper } from './icon-picker-wrapper.js';
|
||||
// Copy of renderUniLit and UniLit from affine-data-view
|
||||
export const renderUniLit = <Props, Expose extends NonNullable<unknown>>(
|
||||
uni: UniComponent<Props, Expose> | undefined,
|
||||
props?: Props,
|
||||
options?: {
|
||||
ref?: Signal<Expose | undefined>;
|
||||
style?: Readonly<StyleInfo>;
|
||||
class?: string;
|
||||
}
|
||||
): TemplateResult => {
|
||||
return html` <uni-lit
|
||||
.uni="${uni}"
|
||||
.props="${props}"
|
||||
.ref="${options?.ref}"
|
||||
style=${options?.style ? styleMap(options?.style) : ''}
|
||||
></uni-lit>`;
|
||||
};
|
||||
const getIcon = (icon?: IconData) => {
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
if (icon.type === IconType.Emoji) {
|
||||
return icon.unicode;
|
||||
}
|
||||
if (icon.type === IconType.AffineIcon) {
|
||||
return (
|
||||
icons as Record<string, (props: { style: string }) => TemplateResult>
|
||||
)[`${icon.name}Icon`]?.({ style: `color:${icon.color}` });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { css, html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockModel> {
|
||||
private _popupCloseHandler: (() => void) | null = null;
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.classList.add(calloutHostStyles);
|
||||
}
|
||||
|
||||
private _getEmojiMarginTop(): string {
|
||||
if (this.model.children.length === 0) {
|
||||
return '10px';
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
const firstChild = this.model.children[0];
|
||||
const flavour = firstChild.flavour;
|
||||
|
||||
const marginTopMap: Record<string, string> = {
|
||||
'affine:paragraph:h1': '23px',
|
||||
'affine:paragraph:h2': '20px',
|
||||
'affine:paragraph:h3': '16px',
|
||||
'affine:paragraph:h4': '15px',
|
||||
'affine:paragraph:h5': '14px',
|
||||
'affine:paragraph:h6': '13px',
|
||||
};
|
||||
|
||||
// For heading blocks, use the type to determine margin
|
||||
if (flavour === 'affine:paragraph') {
|
||||
const paragraph = firstChild as ParagraphBlockModel;
|
||||
const type = paragraph.props.type$.value;
|
||||
const key = `${flavour}:${type}`;
|
||||
return marginTopMap[key] || '10px';
|
||||
.affine-callout-block-container {
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
border-radius: 8px;
|
||||
background-color: ${unsafeCSSVarV2('block/callout/background/grey')};
|
||||
}
|
||||
|
||||
// Default for all other block types
|
||||
return '10px';
|
||||
}
|
||||
|
||||
private _closeIconPicker() {
|
||||
if (this._popupCloseHandler) {
|
||||
this._popupCloseHandler();
|
||||
this._popupCloseHandler = null;
|
||||
.affine-callout-emoji-container {
|
||||
margin-right: 10px;
|
||||
margin-top: 14px;
|
||||
user-select: none;
|
||||
font-size: 1.2em;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleIconPicker(event: MouseEvent) {
|
||||
// If popup is already open, close it
|
||||
if (this._popupCloseHandler) {
|
||||
this._closeIconPicker();
|
||||
return;
|
||||
.affine-callout-emoji:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
// Get IconPickerService from the framework
|
||||
const iconPickerService = this.std.getOptional(IconPickerServiceIdentifier);
|
||||
if (!iconPickerService) {
|
||||
console.warn('IconPickerService not found');
|
||||
return;
|
||||
.affine-callout-children {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
// Get the uni-component from the service
|
||||
const iconPickerComponent = iconPickerService.iconPickerComponent;
|
||||
private _emojiMenuAbortController: AbortController | null = null;
|
||||
private readonly _toggleEmojiMenu = () => {
|
||||
if (this._emojiMenuAbortController) {
|
||||
this._emojiMenuAbortController.abort();
|
||||
}
|
||||
this._emojiMenuAbortController = new AbortController();
|
||||
|
||||
// Create props for the icon picker
|
||||
const props = {
|
||||
onSelect: (iconData?: IconData) => {
|
||||
this.model.props.icon$.value = iconData;
|
||||
this._closeIconPicker(); // Close the picker after selection
|
||||
const theme = this.std.get(ThemeProvider).theme$.value;
|
||||
|
||||
createLitPortal({
|
||||
template: html`<affine-emoji-menu
|
||||
.theme=${theme}
|
||||
.onEmojiSelect=${(data: any) => {
|
||||
this.model.props.emoji = data.native;
|
||||
}}
|
||||
></affine-emoji-menu>`,
|
||||
portalStyles: {
|
||||
zIndex: 'var(--affine-z-index-popover)',
|
||||
},
|
||||
onClose: () => {
|
||||
this._closeIconPicker();
|
||||
},
|
||||
};
|
||||
|
||||
// Create IconPickerWrapper instance
|
||||
const wrapper = new IconPickerWrapper();
|
||||
wrapper.iconPickerComponent = iconPickerComponent;
|
||||
wrapper.props = props;
|
||||
wrapper.style.position = 'absolute';
|
||||
wrapper.style.backgroundColor = cssVarV2.layer.background.overlayPanel;
|
||||
wrapper.style.boxShadow = 'var(--affine-menu-shadow)';
|
||||
wrapper.style.borderRadius = '8px';
|
||||
|
||||
// Create popup target from the clicked element
|
||||
const target = popupTargetFromElement(event.currentTarget as HTMLElement);
|
||||
|
||||
// Create popup
|
||||
this._popupCloseHandler = createPopup(target, wrapper, {
|
||||
onClose: () => {
|
||||
this._popupCloseHandler = null;
|
||||
container: this.host,
|
||||
computePosition: {
|
||||
referenceElement: this._emojiButton,
|
||||
placement: 'bottom-start',
|
||||
middleware: [flip(), offset(4)],
|
||||
autoUpdate: { animationFrame: true },
|
||||
},
|
||||
abortController: this._emojiMenuAbortController,
|
||||
closeOnClickAway: true,
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _handleBlockClick = (event: MouseEvent) => {
|
||||
// Check if the click target is emoji related element
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.closest('.affine-callout-emoji-container') ||
|
||||
target.classList.contains('affine-callout-emoji')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's no icon, open icon picker on click
|
||||
const icon = this.model.props.icon$.value;
|
||||
if (!icon) {
|
||||
this._toggleIconPicker(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle clicks when there are no children
|
||||
if (this.model.children.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent event bubbling
|
||||
event.stopPropagation();
|
||||
|
||||
// Create a new paragraph block
|
||||
const paragraphId = this.store.addBlock('affine:paragraph', {}, this.model);
|
||||
|
||||
// Focus the new paragraph
|
||||
focusTextModel(this.std, paragraphId);
|
||||
};
|
||||
|
||||
get attributeRenderer() {
|
||||
@@ -204,6 +97,9 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
||||
return this.std.get(DefaultInlineManagerExtension.identifier);
|
||||
}
|
||||
|
||||
@query('.affine-callout-emoji')
|
||||
private accessor _emojiButton!: HTMLElement;
|
||||
|
||||
override get topContenteditableElement() {
|
||||
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
||||
return this.closest<BlockComponent>(
|
||||
@@ -214,39 +110,20 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
||||
}
|
||||
|
||||
override renderBlock() {
|
||||
const icon = this.model.props.icon$.value;
|
||||
const backgroundColorName = this.model.props.backgroundColorName$.value;
|
||||
const backgroundColor = (
|
||||
cssVarV2.block.callout.background as Record<string, string>
|
||||
)[backgroundColorName ?? ''];
|
||||
|
||||
const iconContent = getIcon(icon);
|
||||
|
||||
const emoji = this.model.props.emoji$.value;
|
||||
return html`
|
||||
<div
|
||||
class="${calloutBlockContainerStyles}"
|
||||
@click=${this._handleBlockClick}
|
||||
style=${styleMap({
|
||||
backgroundColor: backgroundColor ?? 'transparent',
|
||||
})}
|
||||
>
|
||||
${iconContent
|
||||
? html`
|
||||
<div
|
||||
@click=${this._toggleIconPicker}
|
||||
contenteditable="false"
|
||||
class="${calloutEmojiContainerStyles}"
|
||||
style=${styleMap({
|
||||
marginTop: this._getEmojiMarginTop(),
|
||||
})}
|
||||
>
|
||||
<span class="${calloutEmojiStyles}" data-testid="callout-emoji"
|
||||
>${iconContent}</span
|
||||
>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
<div class="${calloutChildrenStyles}">
|
||||
<div class="affine-callout-block-container">
|
||||
<div
|
||||
@click=${this._toggleEmojiMenu}
|
||||
contenteditable="false"
|
||||
class="affine-callout-emoji-container"
|
||||
style=${styleMap({
|
||||
display: emoji.length === 0 ? 'none' : undefined,
|
||||
})}
|
||||
>
|
||||
<span class="affine-callout-emoji">${emoji}</span>
|
||||
</div>
|
||||
<div class="affine-callout-children">
|
||||
${this.renderChildren(this.model)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
CalloutBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
BlockSelection,
|
||||
@@ -9,46 +6,13 @@ import {
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
|
||||
import { calloutToParagraphCommand } from './commands/callout-to-paragraph.js';
|
||||
import { splitCalloutCommand } from './commands/split-callout.js';
|
||||
|
||||
export const CalloutKeymapExtension = KeymapExtension(std => {
|
||||
return {
|
||||
Enter: ctx => {
|
||||
const text = std.selection.find(TextSelection);
|
||||
if (!text) return false;
|
||||
|
||||
const currentBlock = std.store.getBlock(text.from.blockId);
|
||||
if (!currentBlock) return false;
|
||||
|
||||
// Check if current block is a callout block
|
||||
let calloutBlock = currentBlock;
|
||||
if (!matchModels(currentBlock.model, [CalloutBlockModel])) {
|
||||
// If not, check if the parent is a callout block
|
||||
const parent = std.store.getParent(currentBlock.model);
|
||||
if (!parent || !matchModels(parent, [CalloutBlockModel])) {
|
||||
return false;
|
||||
}
|
||||
const parentBlock = std.store.getBlock(parent.id);
|
||||
if (!parentBlock) return false;
|
||||
calloutBlock = parentBlock;
|
||||
}
|
||||
|
||||
ctx.get('keyboardState').raw.preventDefault();
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(splitCalloutCommand, {
|
||||
blockId: calloutBlock.model.id,
|
||||
inlineIndex: text.from.index,
|
||||
currentBlockId: text.from.blockId,
|
||||
})
|
||||
.run();
|
||||
return true;
|
||||
},
|
||||
Backspace: ctx => {
|
||||
const text = std.selection.find(TextSelection);
|
||||
if (text && text.isCollapsed() && text.from.index === 0) {
|
||||
const event = ctx.get('defaultState').event;
|
||||
event.preventDefault();
|
||||
|
||||
const block = std.store.getBlock(text.from.blockId);
|
||||
if (!block) return false;
|
||||
@@ -56,22 +20,6 @@ export const CalloutKeymapExtension = KeymapExtension(std => {
|
||||
if (!parent) return false;
|
||||
if (!matchModels(parent, [CalloutBlockModel])) return false;
|
||||
|
||||
// Check if current block is a paragraph inside callout
|
||||
if (matchModels(block.model, [ParagraphBlockModel])) {
|
||||
event.preventDefault();
|
||||
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(calloutToParagraphCommand, {
|
||||
id: block.model.id,
|
||||
})
|
||||
.run();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to selecting the callout block
|
||||
event.preventDefault();
|
||||
std.selection.setGroup('note', [
|
||||
std.selection.create(BlockSelection, {
|
||||
blockId: parent.id,
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import {
|
||||
CalloutBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import type { Command } from '@blocksuite/std';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { Text } from '@blocksuite/store';
|
||||
|
||||
export const calloutToParagraphCommand: Command<
|
||||
{
|
||||
id: string;
|
||||
stopCapturing?: boolean;
|
||||
},
|
||||
{
|
||||
success: boolean;
|
||||
}
|
||||
> = (ctx, next) => {
|
||||
const { id, stopCapturing = true } = ctx;
|
||||
const std = ctx.std;
|
||||
const doc = std.store;
|
||||
const model = doc.getBlock(id)?.model;
|
||||
|
||||
if (!model || !matchModels(model, [ParagraphBlockModel])) return false;
|
||||
|
||||
const parent = doc.getParent(model);
|
||||
if (!parent || !matchModels(parent, [CalloutBlockModel])) return false;
|
||||
|
||||
if (stopCapturing) std.store.captureSync();
|
||||
|
||||
// Get current block index in callout
|
||||
const currentIndex = parent.children.indexOf(model);
|
||||
const hasText = model.text && model.text.length > 0;
|
||||
|
||||
// Find previous paragraph block in callout
|
||||
let previousBlock = null;
|
||||
for (let i = currentIndex - 1; i >= 0; i--) {
|
||||
const sibling = parent.children[i];
|
||||
if (matchModels(sibling, [ParagraphBlockModel])) {
|
||||
previousBlock = sibling;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousBlock && hasText) {
|
||||
// Clone current text content before any operations to prevent data loss
|
||||
const currentText = model.text || new Text();
|
||||
|
||||
// Get previous block text and merge index
|
||||
const previousText = previousBlock.text || new Text();
|
||||
const mergeIndex = previousText.length;
|
||||
|
||||
// Apply each delta from cloned current text to previous block to preserve formatting
|
||||
previousText.join(currentText);
|
||||
|
||||
// Remove current block after text has been merged
|
||||
doc.deleteBlock(model, {
|
||||
deleteChildren: false,
|
||||
});
|
||||
|
||||
// Focus at merge point in previous block
|
||||
focusTextModel(std, previousBlock.id, mergeIndex);
|
||||
} else if (previousBlock && !hasText) {
|
||||
// Move cursor to end of previous block
|
||||
doc.deleteBlock(model, {
|
||||
deleteChildren: false,
|
||||
});
|
||||
|
||||
const previousText = previousBlock.text || new Text();
|
||||
focusTextModel(std, previousBlock.id, previousText.length);
|
||||
} else {
|
||||
// No previous block, select the entire callout
|
||||
doc.deleteBlock(model, {
|
||||
deleteChildren: false,
|
||||
});
|
||||
|
||||
std.selection.setGroup('note', [
|
||||
std.selection.create(BlockSelection, {
|
||||
blockId: parent.id,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
return next({ success: true });
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
import {
|
||||
CalloutBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import type { Command, EditorHost } from '@blocksuite/std';
|
||||
|
||||
export const splitCalloutCommand: Command<{
|
||||
blockId: string;
|
||||
inlineIndex: number;
|
||||
currentBlockId: string;
|
||||
}> = (ctx, next) => {
|
||||
const { blockId, inlineIndex, currentBlockId, std } = ctx;
|
||||
const host = std.host as EditorHost;
|
||||
const doc = host.store;
|
||||
|
||||
const calloutModel = doc.getBlock(blockId)?.model;
|
||||
if (!calloutModel || !matchModels(calloutModel, [CalloutBlockModel])) {
|
||||
console.error(`block ${blockId} is not a callout block`);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentModel = doc.getBlock(currentBlockId)?.model;
|
||||
if (!currentModel) {
|
||||
console.error(`current block ${currentBlockId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
doc.captureSync();
|
||||
|
||||
if (matchModels(currentModel, [ParagraphBlockModel])) {
|
||||
// User is in a paragraph within the callout's children
|
||||
const afterText = currentModel.props.text.split(inlineIndex);
|
||||
|
||||
// Update the current paragraph's text to keep only the part before cursor
|
||||
doc.transact(() => {
|
||||
currentModel.props.text.delete(
|
||||
inlineIndex,
|
||||
currentModel.props.text.length - inlineIndex
|
||||
);
|
||||
});
|
||||
|
||||
// Create a new paragraph block after the current one
|
||||
const parent = doc.getParent(currentModel);
|
||||
if (parent) {
|
||||
const currentIndex = parent.children.indexOf(currentModel);
|
||||
const newParagraphId = doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: afterText,
|
||||
},
|
||||
parent,
|
||||
currentIndex + 1
|
||||
);
|
||||
|
||||
if (newParagraphId) {
|
||||
host.updateComplete
|
||||
.then(() => {
|
||||
focusTextModel(std, newParagraphId);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If current block is not a paragraph, create a new paragraph in callout
|
||||
const newParagraphId = doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new Text(),
|
||||
},
|
||||
calloutModel
|
||||
);
|
||||
|
||||
if (newParagraphId) {
|
||||
host.updateComplete
|
||||
.then(() => {
|
||||
focusTextModel(std, newParagraphId);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
@@ -1,11 +1,24 @@
|
||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import { focusBlockEnd } from '@blocksuite/affine-shared/commands';
|
||||
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
findAncestorModel,
|
||||
isInsideBlockByFlavour,
|
||||
matchModels,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu';
|
||||
import { FontIcon } from '@blocksuite/icons/lit';
|
||||
|
||||
import { calloutTooltip } from './tooltips';
|
||||
|
||||
export const calloutSlashMenuConfig: SlashMenuConfig = {
|
||||
disableWhen: ({ model }) => {
|
||||
return (
|
||||
findAncestorModel(model, ancestor =>
|
||||
matchModels(ancestor, [CalloutBlockModel])
|
||||
) !== null
|
||||
);
|
||||
},
|
||||
items: [
|
||||
{
|
||||
name: 'Callout',
|
||||
@@ -17,11 +30,10 @@ export const calloutSlashMenuConfig: SlashMenuConfig = {
|
||||
},
|
||||
searchAlias: ['callout'],
|
||||
group: '0_Basic@9',
|
||||
when: ({ model }) => {
|
||||
return !isInsideBlockByFlavour(
|
||||
model.store,
|
||||
model,
|
||||
'affine:edgeless-text'
|
||||
when: ({ std, model }) => {
|
||||
return (
|
||||
std.get(FeatureFlagService).getFlag('enable_callout') &&
|
||||
!isInsideBlockByFlavour(model.store, model, 'affine:edgeless-text')
|
||||
);
|
||||
},
|
||||
action: ({ model, std }) => {
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
import {
|
||||
createPopup,
|
||||
popupTargetFromElement,
|
||||
} from '@blocksuite/affine-components/context-menu';
|
||||
import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
|
||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
ActionPlacement,
|
||||
type IconData,
|
||||
IconPickerServiceIdentifier,
|
||||
type ToolbarAction,
|
||||
type ToolbarActionGroup,
|
||||
type ToolbarModuleConfig,
|
||||
ToolbarModuleExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { DeleteIcon, PaletteIcon, SmileIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { html } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { IconPickerWrapper } from '../icon-picker-wrapper.js';
|
||||
|
||||
const colors = [
|
||||
'default',
|
||||
'red',
|
||||
'orange',
|
||||
'yellow',
|
||||
'green',
|
||||
'teal',
|
||||
'blue',
|
||||
'purple',
|
||||
'grey',
|
||||
] as const;
|
||||
|
||||
const backgroundColorAction = {
|
||||
id: 'background-color',
|
||||
label: 'Background Color',
|
||||
tooltip: 'Change background color',
|
||||
icon: PaletteIcon(),
|
||||
run() {
|
||||
// This will be handled by the content function
|
||||
},
|
||||
content(ctx) {
|
||||
const model = ctx.getCurrentModelByType(CalloutBlockModel);
|
||||
if (!model) return null;
|
||||
|
||||
const updateBackground = (color: string) => {
|
||||
ctx.store.updateBlock(model, { backgroundColorName: color });
|
||||
};
|
||||
|
||||
return html`
|
||||
<editor-menu-button
|
||||
.contentPadding=${'8px'}
|
||||
.button=${html`
|
||||
<editor-icon-button
|
||||
aria-label="background"
|
||||
.tooltip=${'Background Color'}
|
||||
>
|
||||
${PaletteIcon()} ${EditorChevronDown}
|
||||
</editor-icon-button>
|
||||
`}
|
||||
>
|
||||
<div data-size="large" data-orientation="vertical">
|
||||
<div class="highlight-heading">Background</div>
|
||||
${repeat(colors, color => {
|
||||
const isDefault = color === 'default';
|
||||
const value = isDefault
|
||||
? null
|
||||
: `var(--affine-text-highlight-${color})`;
|
||||
const displayName = `${color} Background`;
|
||||
|
||||
return html`
|
||||
<editor-menu-action
|
||||
data-testid="background-${color}"
|
||||
@click=${() => updateBackground(color)}
|
||||
>
|
||||
<affine-text-duotone-icon
|
||||
style=${styleMap({
|
||||
'--color': 'var(--affine-text-primary-color)',
|
||||
'--background': value ?? 'transparent',
|
||||
})}
|
||||
></affine-text-duotone-icon>
|
||||
<span class="label capitalize">${displayName}</span>
|
||||
</editor-menu-action>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</editor-menu-button>
|
||||
`;
|
||||
},
|
||||
} satisfies ToolbarAction;
|
||||
|
||||
const iconPickerAction = {
|
||||
id: 'icon-picker',
|
||||
label: 'Icon Picker',
|
||||
tooltip: 'Change icon',
|
||||
icon: SmileIcon(),
|
||||
run() {
|
||||
// This will be handled by the content function
|
||||
},
|
||||
content(ctx) {
|
||||
const model = ctx.getCurrentModelByType(CalloutBlockModel);
|
||||
if (!model) return null;
|
||||
|
||||
const handleIconPickerClick = (event: MouseEvent) => {
|
||||
// Get IconPickerService from the framework
|
||||
const iconPickerService = ctx.std.getOptional(
|
||||
IconPickerServiceIdentifier
|
||||
);
|
||||
if (!iconPickerService) {
|
||||
console.warn('IconPickerService not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the uni-component from the service
|
||||
const iconPickerComponent = iconPickerService.iconPickerComponent;
|
||||
|
||||
// Create props for the icon picker
|
||||
const props = {
|
||||
onSelect: (iconData?: IconData) => {
|
||||
// When iconData is undefined (delete icon), set icon to undefined
|
||||
ctx.store.updateBlock(model, { icon: iconData });
|
||||
closeHandler(); // Close the picker after selection
|
||||
},
|
||||
onClose: () => {
|
||||
closeHandler();
|
||||
},
|
||||
};
|
||||
|
||||
// Create IconPickerWrapper instance
|
||||
const wrapper = new IconPickerWrapper();
|
||||
wrapper.iconPickerComponent = iconPickerComponent;
|
||||
wrapper.props = props;
|
||||
wrapper.style.position = 'absolute';
|
||||
wrapper.style.backgroundColor = cssVarV2.layer.background.overlayPanel;
|
||||
wrapper.style.boxShadow = 'var(--affine-menu-shadow)';
|
||||
wrapper.style.borderRadius = '8px';
|
||||
|
||||
// Create popup target from the clicked element
|
||||
const target = popupTargetFromElement(event.currentTarget as HTMLElement);
|
||||
|
||||
// Create popup
|
||||
const closeHandler = createPopup(target, wrapper, {
|
||||
onClose: () => {
|
||||
// Cleanup if needed
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return html`
|
||||
<editor-icon-button
|
||||
aria-label="icon-picker"
|
||||
.tooltip=${'Change Icon'}
|
||||
@click=${handleIconPickerClick}
|
||||
>
|
||||
${SmileIcon()} ${EditorChevronDown}
|
||||
</editor-icon-button>
|
||||
`;
|
||||
},
|
||||
} satisfies ToolbarAction;
|
||||
|
||||
const builtinToolbarConfig = {
|
||||
actions: [
|
||||
{
|
||||
id: 'style',
|
||||
actions: [backgroundColorAction],
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
{
|
||||
id: 'icon',
|
||||
actions: [iconPickerAction],
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'c.delete',
|
||||
label: 'Delete',
|
||||
icon: DeleteIcon(),
|
||||
variant: 'destructive',
|
||||
run(ctx) {
|
||||
const model = ctx.getCurrentModelByType(CalloutBlockModel);
|
||||
if (!model) return;
|
||||
|
||||
ctx.store.deleteBlock(model);
|
||||
|
||||
// Clears
|
||||
ctx.select('note');
|
||||
ctx.reset();
|
||||
},
|
||||
} satisfies ToolbarAction,
|
||||
],
|
||||
} as const satisfies ToolbarModuleConfig;
|
||||
|
||||
export const createBuiltinToolbarConfigExtension = (
|
||||
flavour: string
|
||||
): ExtensionType[] => {
|
||||
return [
|
||||
ToolbarModuleExtension({
|
||||
id: BlockFlavourIdentifier(flavour),
|
||||
config: builtinToolbarConfig,
|
||||
}),
|
||||
];
|
||||
};
|
||||
@@ -1,14 +1,14 @@
|
||||
import { CalloutBlockComponent } from './callout-block';
|
||||
import { IconPickerWrapper } from './icon-picker-wrapper';
|
||||
import { EmojiMenu } from './emoji-menu';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('affine-callout', CalloutBlockComponent);
|
||||
customElements.define('icon-picker-wrapper', IconPickerWrapper);
|
||||
customElements.define('affine-emoji-menu', EmojiMenu);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-callout': CalloutBlockComponent;
|
||||
'icon-picker-wrapper': IconPickerWrapper;
|
||||
'affine-emoji-menu': EmojiMenu;
|
||||
}
|
||||
}
|
||||
|
||||
34
blocksuite/affine/blocks/callout/src/emoji-menu.ts
Normal file
34
blocksuite/affine/blocks/callout/src/emoji-menu.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import data from '@emoji-mart/data';
|
||||
import { Picker } from 'emoji-mart';
|
||||
import { html, LitElement, type PropertyValues } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
|
||||
export class EmojiMenu extends WithDisposable(LitElement) {
|
||||
override firstUpdated(props: PropertyValues) {
|
||||
const result = super.firstUpdated(props);
|
||||
|
||||
const picker = new Picker({
|
||||
data,
|
||||
onEmojiSelect: this.onEmojiSelect,
|
||||
autoFocus: true,
|
||||
theme: this.theme,
|
||||
});
|
||||
this.emojiMenu.append(picker as unknown as Node);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onEmojiSelect: (data: any) => void = () => {};
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor theme: 'light' | 'dark' = 'light';
|
||||
|
||||
@query('.affine-emoji-menu')
|
||||
accessor emojiMenu!: HTMLElement;
|
||||
|
||||
override render() {
|
||||
return html`<div class="affine-emoji-menu"></div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { IconData } from '@blocksuite/affine-shared/services';
|
||||
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { type Signal } from '@preact/signals-core';
|
||||
import { html, type TemplateResult } from 'lit';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
// Copy of renderUniLit from callout-block.ts
|
||||
const renderUniLit = <Props, Expose extends NonNullable<unknown>>(
|
||||
uni: UniComponent<Props, Expose> | undefined,
|
||||
props?: Props,
|
||||
options?: {
|
||||
ref?: Signal<Expose | undefined>;
|
||||
style?: Readonly<StyleInfo>;
|
||||
class?: string;
|
||||
}
|
||||
): TemplateResult => {
|
||||
return html` <uni-lit
|
||||
.uni="${uni}"
|
||||
.props="${props}"
|
||||
.ref="${options?.ref}"
|
||||
style=${options?.style ? styleMap(options?.style) : ''}
|
||||
></uni-lit>`;
|
||||
};
|
||||
|
||||
export interface IconPickerWrapperProps {
|
||||
onSelect?: (iconData?: IconData) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export class IconPickerWrapper extends ShadowlessElement {
|
||||
iconPickerComponent?: UniComponent<IconPickerWrapperProps, any>;
|
||||
props?: IconPickerWrapperProps;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.iconPickerComponent) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return renderUniLit(this.iconPickerComponent, this.props);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'icon-picker-wrapper': IconPickerWrapper;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { literal } from 'lit/static-html.js';
|
||||
|
||||
import { CalloutKeymapExtension } from './callout-keymap';
|
||||
import { calloutSlashMenuConfig } from './configs/slash-menu';
|
||||
import { createBuiltinToolbarConfigExtension } from './configs/toolbar';
|
||||
import { effects } from './effects';
|
||||
|
||||
export class CalloutViewExtension extends ViewExtensionProvider {
|
||||
@@ -26,7 +25,6 @@ export class CalloutViewExtension extends ViewExtensionProvider {
|
||||
BlockViewExtension('affine:callout', literal`affine-callout`),
|
||||
CalloutKeymapExtension,
|
||||
SlashMenuConfigExtension('affine:callout', calloutSlashMenuConfig),
|
||||
...createBuiltinToolbarConfigExtension('affine:callout'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -31,10 +31,10 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"shiki": "^3.19.0",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"shiki": "^3.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -48,5 +48,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export const CODE_BLOCK_DEFAULT_DARK_THEME =
|
||||
import('shiki/themes/dark-plus.mjs');
|
||||
export const CODE_BLOCK_DEFAULT_LIGHT_THEME =
|
||||
import('shiki/themes/light-plus.mjs');
|
||||
export const CODE_BLOCK_DEFAULT_DARK_THEME = import(
|
||||
'shiki/themes/dark-plus.mjs'
|
||||
);
|
||||
export const CODE_BLOCK_DEFAULT_LIGHT_THEME = import(
|
||||
'shiki/themes/light-plus.mjs'
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -27,9 +27,9 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@emotion/css": "^11.13.5",
|
||||
@@ -32,10 +32,10 @@
|
||||
"@types/mdast": "^4.0.4",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -48,5 +48,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ const ColumnClassMap: Record<string, string> = {
|
||||
typesCheckbox: 'checkbox',
|
||||
typesText: 'rich-text',
|
||||
typesTitle: 'title',
|
||||
typesDate: 'date',
|
||||
};
|
||||
|
||||
const NotionDatabaseToken = '.collection-content';
|
||||
@@ -166,36 +165,7 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
||||
if (!column) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for <time> element to find date field from Notion.
|
||||
if (HastUtils.querySelector(child, 'time')) {
|
||||
const timeElement = HastUtils.querySelector(child, 'time');
|
||||
let rawColumnData =
|
||||
HastUtils.getTextContent(timeElement).trim();
|
||||
|
||||
if (rawColumnData.startsWith('@')) {
|
||||
rawColumnData = rawColumnData.slice(1);
|
||||
}
|
||||
|
||||
const columnDate = new Date(rawColumnData);
|
||||
const timestamp = columnDate.getTime();
|
||||
|
||||
if (!Number.isNaN(timestamp)) {
|
||||
column.data = {};
|
||||
if (column.type !== 'date') {
|
||||
column.type = 'date';
|
||||
}
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: timestamp,
|
||||
};
|
||||
} else {
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: HastUtils.getTextContent(child),
|
||||
};
|
||||
}
|
||||
} else if (HastUtils.querySelector(child, '.selected-value')) {
|
||||
if (HastUtils.querySelector(child, '.selected-value')) {
|
||||
if (!('options' in column.data)) {
|
||||
column.data.options = [];
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ export class DatabaseTitle extends SignalWatcher(
|
||||
private readonly isFocus$ = signal(false);
|
||||
|
||||
private onPressEnterKey() {
|
||||
this.input.blur();
|
||||
this.dataViewLogic.addRow?.('start');
|
||||
}
|
||||
|
||||
get readonly$() {
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -39,5 +39,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -28,9 +28,9 @@
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -43,5 +43,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -30,13 +30,13 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -49,5 +49,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -323,8 +323,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
||||
|
||||
private readonly _renderEmbedView = () => {
|
||||
const linkedDoc = this.linkedDoc;
|
||||
const trash = linkedDoc?.meta?.trash;
|
||||
const isDeleted = trash || !linkedDoc;
|
||||
const isDeleted = !linkedDoc;
|
||||
const isLoading = this._loading;
|
||||
const isError = this.isError;
|
||||
const isEmpty = this._isDocEmpty() && this.isBannerEmpty;
|
||||
@@ -522,6 +521,11 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
||||
);
|
||||
|
||||
this._setDocUpdatedAt();
|
||||
this.disposables.add(
|
||||
this.store.workspace.slots.docListUpdated.subscribe(() => {
|
||||
this._setDocUpdatedAt();
|
||||
})
|
||||
);
|
||||
|
||||
if (this._referenceToNode) {
|
||||
this._linkedDocMode = this.model.props.params?.mode ?? 'page';
|
||||
@@ -550,13 +554,6 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
this.store.workspace.slots.docListUpdated.subscribe(() => {
|
||||
this._setDocUpdatedAt();
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
|
||||
this._trackCitationDeleteEvent();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,9 +56,6 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
// Caches total bounds, includes all blocks and elements.
|
||||
private _cachedBounds: Bound | null = null;
|
||||
|
||||
private _hasRenderedSyncedView = false;
|
||||
private _hasInitedFitEffect = false;
|
||||
|
||||
private readonly _initEdgelessFitEffect = () => {
|
||||
const fitToContent = () => {
|
||||
if (this.isPageMode) return;
|
||||
@@ -360,14 +357,10 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
};
|
||||
|
||||
refreshData = () => {
|
||||
this._load()
|
||||
.then(() => {
|
||||
this._isEmptySyncedDoc = isEmptyDoc(this.syncedDoc, this.editorMode);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
this._error = true;
|
||||
});
|
||||
this._load().catch(e => {
|
||||
console.error(e);
|
||||
this._error = true;
|
||||
});
|
||||
};
|
||||
|
||||
title$ = computed(() => {
|
||||
@@ -452,8 +445,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
this._cycle = false;
|
||||
|
||||
const syncedDoc = this.syncedDoc;
|
||||
const trash = syncedDoc?.meta?.trash;
|
||||
if (trash || !syncedDoc) {
|
||||
if (!syncedDoc) {
|
||||
this._deleted = true;
|
||||
this._loading = false;
|
||||
return;
|
||||
@@ -529,7 +521,6 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
this.disposables.add(
|
||||
this.store.workspace.slots.docListUpdated.subscribe(() => {
|
||||
this._setDocUpdatedAt();
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -561,6 +552,8 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
this._selectBlock();
|
||||
}
|
||||
});
|
||||
|
||||
this._initEdgelessFitEffect();
|
||||
}
|
||||
|
||||
override renderBlock() {
|
||||
@@ -588,21 +581,12 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
);
|
||||
}
|
||||
|
||||
!this._hasRenderedSyncedView && (this._hasRenderedSyncedView = true);
|
||||
|
||||
return this._renderSyncedView();
|
||||
}
|
||||
|
||||
override updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
this.syncedDocCard?.requestUpdate();
|
||||
|
||||
if (!this._hasInitedFitEffect && this._hasRenderedSyncedView) {
|
||||
/* Register the resizeObserver AFTER syncdView viewport's own resizeObserver
|
||||
* so that viewport.onResize() use up-to-date boundingClientRect values */
|
||||
this._hasInitedFitEffect = true;
|
||||
this._initEdgelessFitEffect();
|
||||
}
|
||||
}
|
||||
|
||||
@state()
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -30,13 +30,13 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -49,5 +49,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -82,8 +82,7 @@ export class EmbedFigmaBlockComponent extends EmbedBlockComponent<EmbedFigmaMode
|
||||
<div class="affine-embed-figma-iframe-container">
|
||||
<iframe
|
||||
src=${`https://www.figma.com/embed?embed_host=blocksuite&url=${url}`}
|
||||
sandbox="allow-same-origin allow-scripts allow-presentation"
|
||||
allow="fullscreen"
|
||||
allowfullscreen
|
||||
loading="lazy"
|
||||
credentialless
|
||||
></iframe>
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { EmbedIframeConfigExtension } from '@blocksuite/affine-shared/services';
|
||||
|
||||
import {
|
||||
type EmbedIframeUrlValidationOptions,
|
||||
validateEmbedIframeUrl,
|
||||
} from '../../utils';
|
||||
|
||||
const BILIBILI_DEFAULT_WIDTH_IN_SURFACE = 800;
|
||||
const BILIBILI_DEFAULT_HEIGHT_IN_SURFACE = 450;
|
||||
const BILIBILI_DEFAULT_HEIGHT_IN_NOTE = 450;
|
||||
const BILIBILI_DEFAULT_WIDTH_PERCENT = 100;
|
||||
|
||||
const bilibiliValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
protocols: ['https:'],
|
||||
hostnames: ['player.bilibili.com', 'www.bilibili.com', 'bilibili.com'],
|
||||
};
|
||||
|
||||
const biliPlayerValidationOptions: EmbedIframeUrlValidationOptions = {
|
||||
protocols: ['https:'],
|
||||
hostnames: ['player.bilibili.com'],
|
||||
};
|
||||
|
||||
const AV_REGEX = /av([0-9]+)/i;
|
||||
const BV_REGEX = /(BV[0-9A-Za-z]{10})/;
|
||||
|
||||
const extractAvid = (url: string) => {
|
||||
const match = url.match(AV_REGEX);
|
||||
return match ? match[1] : undefined;
|
||||
};
|
||||
|
||||
const extractBvid = (url: string) => {
|
||||
const match = url.match(BV_REGEX);
|
||||
return match ? match[1] : undefined;
|
||||
};
|
||||
|
||||
const buildBiliPlayerEmbedUrl = (url: string) => {
|
||||
// If the user pasted the embed URL directly, keep it
|
||||
if (validateEmbedIframeUrl(url, biliPlayerValidationOptions)) {
|
||||
return url;
|
||||
}
|
||||
const avid = extractAvid(url);
|
||||
if (avid) {
|
||||
const params = new URLSearchParams({
|
||||
aid: avid,
|
||||
autoplay: '0',
|
||||
});
|
||||
return `https://player.bilibili.com/player.html?${params.toString()}`;
|
||||
}
|
||||
const bvid = extractBvid(url);
|
||||
if (bvid) {
|
||||
const params = new URLSearchParams({
|
||||
bvid,
|
||||
autoplay: '0',
|
||||
});
|
||||
return `https://player.bilibili.com/player.html?${params.toString()}`;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const bilibiliConfig = {
|
||||
name: 'bilibili',
|
||||
match: (url: string) =>
|
||||
validateEmbedIframeUrl(url, bilibiliValidationOptions) &&
|
||||
(!!extractAvid(url) || !!extractBvid(url)),
|
||||
buildOEmbedUrl: buildBiliPlayerEmbedUrl,
|
||||
useOEmbedUrlDirectly: true,
|
||||
options: {
|
||||
widthInSurface: BILIBILI_DEFAULT_WIDTH_IN_SURFACE,
|
||||
heightInSurface: BILIBILI_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
heightInNote: BILIBILI_DEFAULT_HEIGHT_IN_NOTE,
|
||||
widthPercent: BILIBILI_DEFAULT_WIDTH_PERCENT,
|
||||
allow: 'clipboard-write; encrypted-media; picture-in-picture',
|
||||
sandbox: 'allow-same-origin allow-scripts',
|
||||
style: 'border: none; border-radius: 8px;',
|
||||
allowFullscreen: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const BilibiliEmbedConfig = EmbedIframeConfigExtension(bilibiliConfig);
|
||||
@@ -67,9 +67,8 @@ const genericConfig = {
|
||||
heightInNote: GENERIC_DEFAULT_HEIGHT_IN_NOTE,
|
||||
allowFullscreen: true,
|
||||
style: 'border: none; border-radius: 8px;',
|
||||
allow: '',
|
||||
allow: 'clipboard-read; clipboard-write; picture-in-picture;',
|
||||
referrerpolicy: 'no-referrer-when-downgrade',
|
||||
sandbox: 'allow-scripts',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BilibiliEmbedConfig } from './bilibili';
|
||||
import { ExcalidrawEmbedConfig } from './excalidraw';
|
||||
import { GenericEmbedConfig } from './generic';
|
||||
import { GoogleDocsEmbedConfig } from './google-docs';
|
||||
@@ -12,6 +11,5 @@ export const EmbedIframeConfigExtensions = [
|
||||
MiroEmbedConfig,
|
||||
ExcalidrawEmbedConfig,
|
||||
GoogleDocsEmbedConfig,
|
||||
BilibiliEmbedConfig,
|
||||
GenericEmbedConfig,
|
||||
];
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
type ReadonlySignal,
|
||||
signal,
|
||||
} from '@preact/signals-core';
|
||||
import { html, nothing } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
@@ -45,10 +45,6 @@ import { safeGetIframeSrc } from './utils.js';
|
||||
|
||||
export type EmbedIframeStatus = 'idle' | 'loading' | 'success' | 'error';
|
||||
|
||||
const TRUSTED_SANDBOX =
|
||||
'allow-same-origin allow-scripts allow-forms allow-presentation';
|
||||
const UNTRUSTED_SANDBOX = 'allow-scripts';
|
||||
|
||||
export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIframeBlockModel> {
|
||||
selectedStyle$: ReadonlySignal<ClassInfo> | null = computed<ClassInfo>(
|
||||
() => ({
|
||||
@@ -93,7 +89,6 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
});
|
||||
|
||||
protected iframeOptions: IframeOptions | undefined = undefined;
|
||||
private currentConfigName: string | undefined;
|
||||
|
||||
get embedIframeService() {
|
||||
return this.std.get(EmbedIframeService);
|
||||
@@ -284,10 +279,6 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
const config = this.embedIframeService?.getConfig(url);
|
||||
if (config) {
|
||||
this.iframeOptions = config.options;
|
||||
this.currentConfigName = config.name;
|
||||
} else {
|
||||
this.iframeOptions = undefined;
|
||||
this.currentConfigName = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -337,46 +328,26 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
referrerpolicy,
|
||||
scrolling,
|
||||
allowFullscreen,
|
||||
sandbox,
|
||||
} = this.iframeOptions ?? {};
|
||||
const width = `${widthPercent}%`;
|
||||
// if the block is in the surface, use 100% as the height
|
||||
// otherwise, use the heightInNote
|
||||
const height = this.inSurface ? '100%' : heightInNote;
|
||||
const sandboxValue =
|
||||
sandbox ??
|
||||
(this.currentConfigName === 'generic'
|
||||
? UNTRUSTED_SANDBOX
|
||||
: TRUSTED_SANDBOX);
|
||||
const sourceHost = this._getSourceHost();
|
||||
return html`<iframe
|
||||
return html`
|
||||
<iframe
|
||||
width=${width ?? DEFAULT_IFRAME_WIDTH}
|
||||
height=${height ?? DEFAULT_IFRAME_HEIGHT}
|
||||
?allowfullscreen=${allowFullscreen}
|
||||
loading="lazy"
|
||||
frameborder="0"
|
||||
credentialless
|
||||
sandbox=${sandboxValue}
|
||||
src=${ifDefined(iframeUrl)}
|
||||
allow=${ifDefined(allow)}
|
||||
referrerpolicy=${ifDefined(referrerpolicy)}
|
||||
scrolling=${ifDefined(scrolling)}
|
||||
style=${ifDefined(style)}
|
||||
></iframe>
|
||||
${sourceHost
|
||||
? html`<div class="affine-embed-iframe-source">${sourceHost}</div>`
|
||||
: nothing}`;
|
||||
};
|
||||
|
||||
private readonly _getSourceHost = () => {
|
||||
const url = this.model.props.url ?? this.model.props.iframeUrl;
|
||||
if (!url) return null;
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.hostname;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
private readonly _renderContent = () => {
|
||||
|
||||
@@ -23,19 +23,6 @@ export const embedIframeBlockStyles = css`
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.affine-embed-iframe-source {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
padding: 2px 6px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.affine-embed-iframe-block-overlay.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -124,8 +124,7 @@ export class EmbedLoomBlockComponent extends EmbedBlockComponent<
|
||||
<iframe
|
||||
src=${`https://www.loom.com/embed/${videoId}?hide_title=true`}
|
||||
frameborder="0"
|
||||
allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
|
||||
sandbox="allow-scripts allow-same-origin allow-presentation"
|
||||
allow="fullscreen; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
loading="lazy"
|
||||
credentialless
|
||||
></iframe>
|
||||
|
||||
@@ -148,8 +148,8 @@ export class EmbedYoutubeBlockComponent extends EmbedBlockComponent<
|
||||
type="text/html"
|
||||
src=${`https://www.youtube.com/embed/${videoId}`}
|
||||
frameborder="0"
|
||||
allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
|
||||
sandbox="allow-scripts allow-same-origin allow-presentation"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
loading="lazy"
|
||||
credentialless
|
||||
></iframe>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-frame-title": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -28,10 +28,10 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -28,9 +28,9 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"file-type": "^21.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { updateBlockAlign } from '@blocksuite/affine-block-note';
|
||||
import { ImageBlockModel, TextAlign } from '@blocksuite/affine-model';
|
||||
import { ImageBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
@@ -13,9 +12,6 @@ import {
|
||||
DeleteIcon,
|
||||
DownloadIcon,
|
||||
DuplicateIcon,
|
||||
TextAlignCenterIcon,
|
||||
TextAlignLeftIcon,
|
||||
TextAlignRightIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
@@ -55,55 +51,7 @@ const builtinToolbarConfig = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'c.1.align-left',
|
||||
tooltip: 'Align left',
|
||||
icon: TextAlignLeftIcon(),
|
||||
run(ctx) {
|
||||
const block = ctx.getCurrentBlockByType(ImageBlockComponent);
|
||||
if (block) {
|
||||
ctx.chain
|
||||
.pipe(updateBlockAlign, {
|
||||
textAlign: TextAlign.Left,
|
||||
selectedBlocks: [block],
|
||||
})
|
||||
.run();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'c.2.align-center',
|
||||
tooltip: 'Align center',
|
||||
icon: TextAlignCenterIcon(),
|
||||
run(ctx) {
|
||||
const block = ctx.getCurrentBlockByType(ImageBlockComponent);
|
||||
if (block) {
|
||||
ctx.chain
|
||||
.pipe(updateBlockAlign, {
|
||||
textAlign: TextAlign.Center,
|
||||
selectedBlocks: [block],
|
||||
})
|
||||
.run();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'c.3.align-right',
|
||||
tooltip: 'Align right',
|
||||
icon: TextAlignRightIcon(),
|
||||
run(ctx) {
|
||||
const block = ctx.getCurrentBlockByType(ImageBlockComponent);
|
||||
if (block) {
|
||||
ctx.chain
|
||||
.pipe(updateBlockAlign, {
|
||||
textAlign: TextAlign.Right,
|
||||
selectedBlocks: [block],
|
||||
})
|
||||
.run();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'd.comment',
|
||||
id: 'c.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -143,15 +143,6 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
const alignItemsStyleMap = styleMap({
|
||||
alignItems:
|
||||
this.model.props.textAlign$.value === 'left'
|
||||
? 'flex-start'
|
||||
: this.model.props.textAlign$.value === 'right'
|
||||
? 'flex-end'
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const resovledState = this.resourceController.resolveStateWith({
|
||||
loadingIcon: LoadingIcon({
|
||||
strokeColor: cssVarV2('button/pureWhiteText'),
|
||||
@@ -171,7 +162,6 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
|
||||
html`<affine-page-image
|
||||
.block=${this}
|
||||
.state=${resovledState}
|
||||
style="${alignItemsStyleMap}"
|
||||
></affine-page-image>`,
|
||||
() =>
|
||||
html`<affine-image-fallback-card
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -28,12 +28,12 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/katex": "^0.16.7",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"katex": "^0.16.27",
|
||||
"katex": "^0.16.11",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"minimatch": "^10.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -46,5 +46,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -27,12 +27,12 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -46,5 +46,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -150,10 +150,6 @@ export class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel>
|
||||
|
||||
const listIcon = getListIcon(model, !collapsed, _onClickIcon);
|
||||
|
||||
const textAlignStyle = styleMap({
|
||||
textAlign: this.model.props.textAlign$?.value,
|
||||
});
|
||||
|
||||
const children = html`<div
|
||||
class="affine-block-children-container"
|
||||
style=${styleMap({
|
||||
@@ -165,7 +161,7 @@ export class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel>
|
||||
</div>`;
|
||||
|
||||
return html`
|
||||
<div class=${'affine-list-block-container'} style="${textAlignStyle}">
|
||||
<div class=${'affine-list-block-container'}>
|
||||
<div
|
||||
class=${classMap({
|
||||
'affine-list-rich-text-wrapper': true,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ListBlockModel } from '@blocksuite/affine-model';
|
||||
import { getNumberPrefix } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
BulletedList01Icon,
|
||||
BulletedList02Icon,
|
||||
@@ -12,6 +11,8 @@ import {
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { html } from 'lit';
|
||||
|
||||
import { getNumberPrefix } from './get-number-prefix.js';
|
||||
|
||||
const getListDeep = (model: ListBlockModel): number => {
|
||||
let deep = 0;
|
||||
let parent = model.store.getParent(model);
|
||||
|
||||
@@ -11,7 +11,7 @@ function number2letter(n: number) {
|
||||
}
|
||||
|
||||
// Derive from https://gist.github.com/imilu/00f32c61e50b7ca296f91e9d96d8e976
|
||||
function number2roman(num: number) {
|
||||
export function number2roman(num: number) {
|
||||
const lookup: Record<string, number> = {
|
||||
M: 1000,
|
||||
CM: 900,
|
||||
@@ -28,13 +28,12 @@ function number2roman(num: number) {
|
||||
I: 1,
|
||||
};
|
||||
let romanStr = '';
|
||||
for (const [key, value] of Object.entries(lookup)) {
|
||||
while (num >= value) {
|
||||
romanStr += key;
|
||||
num -= value;
|
||||
for (const i in lookup) {
|
||||
while (num >= lookup[i]) {
|
||||
romanStr += i;
|
||||
num -= lookup[i];
|
||||
}
|
||||
}
|
||||
|
||||
return romanStr;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
@@ -33,9 +33,9 @@
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -49,5 +49,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -8,4 +8,3 @@ export { indentBlock } from './indent-block.js';
|
||||
export { indentBlocks } from './indent-blocks.js';
|
||||
export { selectBlock } from './select-block.js';
|
||||
export { selectBlocksBetween } from './select-blocks-between.js';
|
||||
export { updateBlockAlign } from './update-block-align.js';
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { TextAlign } from '@blocksuite/affine-model';
|
||||
import {
|
||||
getBlockSelectionsCommand,
|
||||
getImageSelectionsCommand,
|
||||
getSelectedBlocksCommand,
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import {
|
||||
type BlockComponent,
|
||||
type Command,
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
|
||||
type UpdateBlockAlignConfig = {
|
||||
textAlign: TextAlign;
|
||||
selectedBlocks?: BlockComponent[];
|
||||
};
|
||||
|
||||
export const updateBlockAlign: Command<UpdateBlockAlignConfig> = (
|
||||
ctx,
|
||||
next
|
||||
) => {
|
||||
let { std, textAlign, selectedBlocks } = ctx;
|
||||
|
||||
if (!selectedBlocks) {
|
||||
const [result, ctx] = std.command
|
||||
.chain()
|
||||
.tryAll(chain => [
|
||||
chain.pipe(getTextSelectionCommand),
|
||||
chain.pipe(getBlockSelectionsCommand),
|
||||
chain.pipe(getImageSelectionsCommand),
|
||||
])
|
||||
.pipe(getSelectedBlocksCommand, { types: ['text', 'block', 'image'] })
|
||||
.run();
|
||||
if (result) {
|
||||
selectedBlocks = ctx.selectedBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedBlocks || selectedBlocks.length === 0) return false;
|
||||
|
||||
selectedBlocks.forEach(block => {
|
||||
std.store.updateBlock(block.model, { textAlign });
|
||||
});
|
||||
|
||||
const selectionManager = std.host.selection;
|
||||
const textSelection = selectionManager.find(TextSelection);
|
||||
if (!textSelection) {
|
||||
return false;
|
||||
}
|
||||
selectionManager.setGroup('note', [textSelection]);
|
||||
return next();
|
||||
};
|
||||
@@ -4,15 +4,9 @@ import {
|
||||
textFormatConfigs,
|
||||
} from '@blocksuite/affine-inline-preset';
|
||||
import {
|
||||
type TextAlignConfig,
|
||||
textAlignConfigs,
|
||||
type TextConversionConfig,
|
||||
textConversionConfigs,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
getSelectedModelsCommand,
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
type SlashMenuActionItem,
|
||||
@@ -23,7 +17,7 @@ import {
|
||||
import { HeadingsIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
|
||||
import { updateBlockAlign, updateBlockType } from '../commands';
|
||||
import { updateBlockType } from '../commands';
|
||||
import { tooltips } from './tooltips';
|
||||
|
||||
let basicIndex = 0;
|
||||
@@ -66,10 +60,6 @@ const noteSlashMenuConfig: SlashMenuConfig = {
|
||||
createConversionItem(config, `1_List@${index++}`)
|
||||
),
|
||||
|
||||
...textAlignConfigs.map((config, index) =>
|
||||
createAlignItem(config, `2_Align@${index++}`)
|
||||
),
|
||||
|
||||
...textFormatConfigs
|
||||
.filter(i => !['Code', 'Link'].includes(i.name))
|
||||
.map((config, index) =>
|
||||
@@ -82,13 +72,12 @@ function createConversionItem(
|
||||
config: TextConversionConfig,
|
||||
group?: SlashMenuItem['group']
|
||||
): SlashMenuActionItem {
|
||||
const { name, description, icon, flavour, type, searchAlias = [] } = config;
|
||||
const { name, description, icon, flavour, type } = config;
|
||||
return {
|
||||
name,
|
||||
group,
|
||||
description,
|
||||
icon,
|
||||
searchAlias,
|
||||
tooltip: tooltips[name],
|
||||
when: ({ model }) => model.store.schema.flavourSchemaMap.has(flavour),
|
||||
action: ({ std }) => {
|
||||
@@ -100,26 +89,6 @@ function createConversionItem(
|
||||
};
|
||||
}
|
||||
|
||||
function createAlignItem(
|
||||
config: TextAlignConfig,
|
||||
group?: SlashMenuItem['group']
|
||||
): SlashMenuActionItem {
|
||||
const { textAlign, name, icon } = config;
|
||||
return {
|
||||
name,
|
||||
group,
|
||||
icon,
|
||||
action: ({ std }) => {
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(getTextSelectionCommand)
|
||||
.pipe(getSelectedModelsCommand, { types: ['text'] })
|
||||
.pipe(updateBlockAlign, { textAlign })
|
||||
.run();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createTextFormatItem(
|
||||
config: TextFormatConfig,
|
||||
group?: SlashMenuItem['group']
|
||||
|
||||
@@ -5,10 +5,7 @@ import {
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
textAlignConfigs,
|
||||
textConversionConfigs,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { textConversionConfigs } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
focusBlockEnd,
|
||||
focusBlockStart,
|
||||
@@ -39,7 +36,6 @@ import {
|
||||
indentBlocks,
|
||||
selectBlock,
|
||||
selectBlocksBetween,
|
||||
updateBlockAlign,
|
||||
updateBlockType,
|
||||
} from './commands';
|
||||
import { moveBlockConfigs } from './move-block';
|
||||
@@ -161,36 +157,6 @@ class NoteKeymap {
|
||||
);
|
||||
};
|
||||
|
||||
private readonly _bindTextAlignHotKey = () => {
|
||||
return textAlignConfigs.reduce(
|
||||
(acc, item) => {
|
||||
const keymap = item.hotkey!.reduce(
|
||||
(acc, key) => {
|
||||
return {
|
||||
...acc,
|
||||
[key]: ctx => {
|
||||
ctx.get('defaultState').event.preventDefault();
|
||||
const [result] = this._std.command
|
||||
.chain()
|
||||
.pipe(updateBlockAlign, { textAlign: item.textAlign })
|
||||
.run();
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
},
|
||||
{} as Record<string, UIEventHandler>
|
||||
);
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...keymap,
|
||||
};
|
||||
},
|
||||
{} as Record<string, UIEventHandler>
|
||||
);
|
||||
};
|
||||
|
||||
private _focusBlock: BlockComponent | null = null;
|
||||
|
||||
private readonly _getClosestNoteByBlockId = (blockId: string) => {
|
||||
@@ -602,7 +568,6 @@ class NoteKeymap {
|
||||
...this._bindMoveBlockHotKey(),
|
||||
...this._bindQuickActionHotKey(),
|
||||
...this._bindTextConversionHotKey(),
|
||||
...this._bindTextAlignHotKey(),
|
||||
Tab: ctx => {
|
||||
const [success] = this.std.command.exec(indentBlocks);
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -264,10 +264,6 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
|
||||
`;
|
||||
}
|
||||
|
||||
const textAlignStyle = styleMap({
|
||||
textAlign: this.model.props.textAlign$?.value,
|
||||
});
|
||||
|
||||
const children = html`<div
|
||||
class="affine-block-children-container"
|
||||
style=${styleMap({
|
||||
@@ -292,7 +288,6 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
|
||||
'affine-paragraph-block-container': true,
|
||||
'highlight-comment': this.isCommentHighlighted,
|
||||
})}
|
||||
style="${textAlignStyle}"
|
||||
data-has-collapsed-siblings="${collapsedSiblings.length > 0}"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -46,14 +46,14 @@
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.16",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"dompurify": "^3.3.0",
|
||||
"dompurify": "^3.2.4",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -67,5 +67,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@ import {
|
||||
notifyDocCreated,
|
||||
promptDocTitle,
|
||||
} from '@blocksuite/affine-block-embed';
|
||||
import {
|
||||
updateBlockAlign,
|
||||
updateBlockType,
|
||||
} from '@blocksuite/affine-block-note';
|
||||
import { updateBlockType } from '@blocksuite/affine-block-note';
|
||||
import type { HighlightType } from '@blocksuite/affine-components/highlight-dropdown-menu';
|
||||
import { toast } from '@blocksuite/affine-components/toast';
|
||||
import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
|
||||
@@ -26,12 +23,8 @@ import {
|
||||
import {
|
||||
EmbedLinkedDocBlockSchema,
|
||||
EmbedSyncedDocBlockSchema,
|
||||
type TextAlign,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
textAlignConfigs,
|
||||
textConversionConfigs,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { textConversionConfigs } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
copySelectedModelsCommand,
|
||||
deleteSelectedModelsCommand,
|
||||
@@ -53,7 +46,6 @@ import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { getMostCommonValue } from '@blocksuite/affine-shared/utils';
|
||||
import { tableViewMeta } from '@blocksuite/data-view/view-presets';
|
||||
import {
|
||||
CopyIcon,
|
||||
@@ -138,64 +130,6 @@ const conversionsActionGroup = {
|
||||
},
|
||||
} as const satisfies ToolbarActionGenerator;
|
||||
|
||||
const alignActionGroup = {
|
||||
id: 'b.align',
|
||||
when: ({ chain }) => isFormatSupported(chain).run()[0],
|
||||
generate({ chain }) {
|
||||
const [ok, { selectedModels = [] }] = chain
|
||||
.tryAll(chain => [
|
||||
chain.pipe(getTextSelectionCommand),
|
||||
chain.pipe(getBlockSelectionsCommand),
|
||||
])
|
||||
.pipe(getSelectedModelsCommand, { types: ['text', 'block'] })
|
||||
.run();
|
||||
if (!ok) return null;
|
||||
|
||||
const alignment =
|
||||
textAlignConfigs.find(
|
||||
({ textAlign }) =>
|
||||
textAlign ===
|
||||
getMostCommonValue(
|
||||
selectedModels.map(
|
||||
({ props }) => props as { textAlign?: TextAlign }
|
||||
),
|
||||
'textAlign'
|
||||
)
|
||||
) ?? textAlignConfigs[0];
|
||||
const update = (textAlign: TextAlign) => {
|
||||
chain.pipe(updateBlockAlign, { textAlign }).run();
|
||||
};
|
||||
|
||||
return {
|
||||
content: html`
|
||||
<editor-menu-button
|
||||
.contentPadding="${'8px'}"
|
||||
.button=${html`
|
||||
<editor-icon-button aria-label="Align" .tooltip="${'Align'}">
|
||||
${alignment.icon} ${EditorChevronDown}
|
||||
</editor-icon-button>
|
||||
`}
|
||||
>
|
||||
<div data-size="large" data-orientation="vertical">
|
||||
${repeat(
|
||||
textAlignConfigs,
|
||||
item => item.name,
|
||||
({ textAlign, name, icon }) => html`
|
||||
<editor-menu-action
|
||||
aria-label=${name}
|
||||
@click=${() => update(textAlign)}
|
||||
>
|
||||
${icon}<span class="label">${name}</span>
|
||||
</editor-menu-action>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</editor-menu-button>
|
||||
`,
|
||||
};
|
||||
},
|
||||
} as const satisfies ToolbarActionGenerator;
|
||||
|
||||
const inlineTextActionGroup = {
|
||||
id: 'b.inline-text',
|
||||
when: ({ chain }) => isFormatSupported(chain).run()[0],
|
||||
@@ -357,7 +291,6 @@ const turnIntoLinkedDoc = {
|
||||
export const builtinToolbarConfig = {
|
||||
actions: [
|
||||
conversionsActionGroup,
|
||||
alignActionGroup,
|
||||
inlineTextActionGroup,
|
||||
highlightActionGroup,
|
||||
turnIntoDatabase,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
@@ -30,9 +30,9 @@
|
||||
"fractional-indexing": "^3.2.0",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^5.1.6",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"nanoid": "^5.0.7",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -45,5 +45,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
"html2canvas": "^1.4.1",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^5.1.6",
|
||||
"nanoid": "^5.0.7",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -46,5 +46,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export class CanvasRenderer {
|
||||
* It is not recommended to set width and height to 100%.
|
||||
*/
|
||||
private _canvasSizeUpdater(dpr = window.devicePixelRatio) {
|
||||
const { width, height, viewScale } = this.viewport;
|
||||
const { width, height } = this.viewport;
|
||||
const actualWidth = Math.ceil(width * dpr);
|
||||
const actualHeight = Math.ceil(height * dpr);
|
||||
|
||||
@@ -124,8 +124,6 @@ export class CanvasRenderer {
|
||||
update(canvas: HTMLCanvasElement) {
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
canvas.style.transform = `scale(${1 / viewScale})`;
|
||||
canvas.style.transformOrigin = `top left`;
|
||||
canvas.width = actualWidth;
|
||||
canvas.height = actualHeight;
|
||||
},
|
||||
|
||||
@@ -8,8 +8,9 @@ import type { RoughCanvas } from '../../index.js';
|
||||
import type { CanvasRenderer } from '../canvas-renderer.js';
|
||||
|
||||
export type ElementRenderer<
|
||||
T extends GfxPrimitiveElementModel | GfxLocalElementModel =
|
||||
GfxPrimitiveElementModel,
|
||||
T extends
|
||||
| GfxPrimitiveElementModel
|
||||
| GfxLocalElementModel = GfxPrimitiveElementModel,
|
||||
> = (
|
||||
model: T,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"author": "toeverything",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-inline-preset": "workspace:*",
|
||||
@@ -20,16 +20,16 @@
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"lit": "^3.2.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -144,16 +144,6 @@ export class TableBlockComponent extends CaptionedBlockComponent<TableBlockModel
|
||||
style=${styleMap({
|
||||
paddingLeft: `${virtualPadding}px`,
|
||||
paddingRight: `${virtualPadding}px`,
|
||||
marginLeft:
|
||||
!this.model.props.textAlign$.value ||
|
||||
this.model.props.textAlign$?.value === 'left'
|
||||
? undefined
|
||||
: 'auto',
|
||||
marginRight:
|
||||
!this.model.props.textAlign$.value ||
|
||||
this.model.props.textAlign$?.value === 'right'
|
||||
? undefined
|
||||
: 'auto',
|
||||
width: 'max-content',
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -418,7 +418,6 @@ export class TableCell extends SignalWatcher(
|
||||
name: 'Paste',
|
||||
prefix: PasteIcon(),
|
||||
select: () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
navigator.clipboard.readText().then(text => {
|
||||
this.selectionController.doPaste(text, selected);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@blocksuite/sync": "workspace:*",
|
||||
@@ -28,15 +28,15 @@
|
||||
"@types/mdast": "^4.0.4",
|
||||
"collapse-white-space": "^2.1.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"katex": "^0.16.27",
|
||||
"katex": "^0.16.11",
|
||||
"lit": "^3.2.0",
|
||||
"lit-html": "^3.2.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"remark-math": "^6.0.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"shiki": "^3.19.0",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"rxjs": "^7.8.1",
|
||||
"shiki": "^3.0.0",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -82,5 +82,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ export type MenuButtonData = {
|
||||
select: (ele: HTMLElement) => void | false;
|
||||
onHover?: (hover: boolean) => void;
|
||||
testId?: string;
|
||||
closeOnSelect?: boolean;
|
||||
};
|
||||
|
||||
export class MenuButton extends MenuFocusable {
|
||||
@@ -86,9 +85,7 @@ export class MenuButton extends MenuFocusable {
|
||||
onClick() {
|
||||
if (this.data.select(this) !== false) {
|
||||
this.menu.options.onComplete?.();
|
||||
if (this.data.closeOnSelect !== false) {
|
||||
this.menu.close();
|
||||
}
|
||||
this.menu.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +150,7 @@ export class MobileMenuButton extends MenuFocusable {
|
||||
onClick() {
|
||||
if (this.data.select(this) !== false) {
|
||||
this.menu.options.onComplete?.();
|
||||
if (this.data.closeOnSelect !== false) {
|
||||
this.menu.close();
|
||||
}
|
||||
this.menu.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,14 +193,12 @@ export const menuButtonItems = {
|
||||
(config: {
|
||||
name: string;
|
||||
label?: () => TemplateResult;
|
||||
info?: TemplateResult;
|
||||
prefix?: TemplateResult;
|
||||
postfix?: TemplateResult;
|
||||
isSelected?: boolean;
|
||||
select: (ele: HTMLElement) => void | false;
|
||||
onHover?: (hover: boolean) => void;
|
||||
class?: MenuClass;
|
||||
closeOnSelect?: boolean;
|
||||
hide?: () => boolean;
|
||||
testId?: string;
|
||||
}) =>
|
||||
@@ -218,14 +211,13 @@ export const menuButtonItems = {
|
||||
return html`
|
||||
${config.prefix}
|
||||
<div class="affine-menu-action-text">
|
||||
${config.label?.() ?? config.name} ${config.info}
|
||||
${config.label?.() ?? config.name}
|
||||
</div>
|
||||
${config.postfix ?? (config.isSelected ? DoneIcon() : undefined)}
|
||||
`;
|
||||
},
|
||||
onHover: config.onHover,
|
||||
select: config.select,
|
||||
closeOnSelect: config.closeOnSelect,
|
||||
class: {
|
||||
'selected-item': config.isSelected ?? false,
|
||||
...config.class,
|
||||
|
||||
@@ -17,7 +17,6 @@ export type MenuInputData = {
|
||||
class?: string;
|
||||
onComplete?: (value: string) => void;
|
||||
onChange?: (value: string) => void;
|
||||
onBlur?: (value: string) => void;
|
||||
disableAutoFocus?: boolean;
|
||||
};
|
||||
|
||||
@@ -50,10 +49,6 @@ export class MenuInput extends MenuFocusable {
|
||||
this.data.onChange?.(this.inputRef.value);
|
||||
};
|
||||
|
||||
private readonly onBlur = () => {
|
||||
this.data.onBlur?.(this.inputRef.value);
|
||||
};
|
||||
|
||||
private readonly onInput = (e: InputEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.isComposing) return;
|
||||
@@ -114,7 +109,6 @@ export class MenuInput extends MenuFocusable {
|
||||
@focus="${() => {
|
||||
this.menu.setFocusOnly(this);
|
||||
}}"
|
||||
@blur="${this.onBlur}"
|
||||
@input="${this.onInput}"
|
||||
placeholder="${this.data.placeholder ?? ''}"
|
||||
@keypress="${this.stopPropagation}"
|
||||
@@ -221,7 +215,6 @@ export const menuInputItems = {
|
||||
prefix?: TemplateResult;
|
||||
onComplete?: (value: string) => void;
|
||||
onChange?: (value: string) => void;
|
||||
onBlur?: (value: string) => void;
|
||||
class?: string;
|
||||
style?: Readonly<StyleInfo>;
|
||||
}) =>
|
||||
@@ -235,7 +228,6 @@ export const menuInputItems = {
|
||||
class: config.class,
|
||||
onComplete: config.onComplete,
|
||||
onChange: config.onChange,
|
||||
onBlur: config.onBlur,
|
||||
};
|
||||
const style = styleMap({
|
||||
display: 'flex',
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
computePosition,
|
||||
type Middleware,
|
||||
offset,
|
||||
type Placement,
|
||||
type ReferenceElement,
|
||||
shift,
|
||||
} from '@floating-ui/dom';
|
||||
@@ -38,9 +37,7 @@ export class MenuComponent
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
min-width: 320px;
|
||||
max-width: 320px;
|
||||
max-height: 700px;
|
||||
min-width: 180px;
|
||||
box-shadow: ${unsafeCSSVar('overlayPanelShadow')};
|
||||
border-radius: 4px;
|
||||
background-color: ${unsafeCSSVarV2('layer/background/overlayPanel')};
|
||||
@@ -442,7 +439,6 @@ export const createPopup = (
|
||||
onClose?: () => void;
|
||||
middleware?: Array<Middleware | null | undefined | false>;
|
||||
container?: HTMLElement;
|
||||
placement?: Placement;
|
||||
}
|
||||
) => {
|
||||
const close = () => {
|
||||
@@ -452,7 +448,6 @@ export const createPopup = (
|
||||
const modal = createModal(target.root);
|
||||
autoUpdate(target.targetRect, content, () => {
|
||||
computePosition(target.targetRect, content, {
|
||||
placement: options?.placement,
|
||||
middleware: options?.middleware ?? [shift({ crossAxis: true })],
|
||||
})
|
||||
.then(({ x, y }) => {
|
||||
@@ -525,7 +520,6 @@ export const popMenu = (
|
||||
options: MenuOptions;
|
||||
middleware?: Array<Middleware | null | undefined | false>;
|
||||
container?: HTMLElement;
|
||||
placement?: Placement;
|
||||
}
|
||||
): MenuHandler => {
|
||||
if (IS_MOBILE) {
|
||||
@@ -557,7 +551,6 @@ export const popMenu = (
|
||||
offset(4),
|
||||
],
|
||||
container: props.container,
|
||||
placement: props.placement,
|
||||
});
|
||||
return {
|
||||
close: closePopup,
|
||||
@@ -570,14 +563,12 @@ export const popMenu = (
|
||||
export const popFilterableSimpleMenu = (
|
||||
target: PopupTarget,
|
||||
options: MenuConfig[],
|
||||
onClose?: () => void,
|
||||
placement: Placement = 'bottom-start'
|
||||
onClose?: () => void
|
||||
) => {
|
||||
popMenu(target, {
|
||||
options: {
|
||||
items: options,
|
||||
onClose,
|
||||
},
|
||||
placement,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,15 +4,12 @@ import {
|
||||
autoPlacement,
|
||||
autoUpdate,
|
||||
computePosition,
|
||||
type Middleware,
|
||||
offset,
|
||||
shift,
|
||||
} from '@floating-ui/dom';
|
||||
import { css, html, nothing, type TemplateResult } from 'lit';
|
||||
import { html, nothing, type TemplateResult } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
import { MenuButton } from './button.js';
|
||||
import { MenuFocusable } from './focusable.js';
|
||||
import { Menu, type MenuOptions } from './menu.js';
|
||||
import { popMenu, popupTargetFromElement } from './menu-renderer.js';
|
||||
@@ -23,55 +20,29 @@ export type MenuSubMenuData = {
|
||||
options: MenuOptions;
|
||||
select?: () => void;
|
||||
class?: string;
|
||||
openOnHover?: boolean;
|
||||
middleware?: Middleware[];
|
||||
autoHeight?: boolean;
|
||||
closeOnSelect?: boolean;
|
||||
};
|
||||
export const subMenuOffset = offset({
|
||||
mainAxis: 16,
|
||||
crossAxis: 0,
|
||||
crossAxis: -8.5,
|
||||
});
|
||||
export const subMenuPlacements = autoPlacement({
|
||||
allowedPlacements: ['bottom-end'],
|
||||
allowedPlacements: ['right-start', 'left-start', 'right-end', 'left-end'],
|
||||
});
|
||||
export const subMenuMiddleware = [subMenuOffset, subMenuPlacements];
|
||||
|
||||
export const dropdownSubMenuMiddleware = [
|
||||
autoPlacement({ allowedPlacements: ['bottom-end'] }),
|
||||
offset({ mainAxis: 8, crossAxis: 0 }),
|
||||
shift({ crossAxis: true }),
|
||||
];
|
||||
|
||||
export class MenuSubMenu extends MenuFocusable {
|
||||
static override styles = [
|
||||
MenuButton.styles,
|
||||
css`
|
||||
.affine-menu-button svg:last-child {
|
||||
transition: transform 150ms cubic-bezier(0.42, 0, 1, 1);
|
||||
}
|
||||
affine-menu-sub-menu.active .affine-menu-button svg:last-child {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
createTime = 0;
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.createTime = Date.now();
|
||||
if (this.data.openOnHover !== false) {
|
||||
this.disposables.addFromEvent(this, 'mouseenter', this.onMouseEnter);
|
||||
}
|
||||
this.disposables.addFromEvent(this, 'mouseenter', this.onMouseEnter);
|
||||
this.disposables.addFromEvent(this, 'click', e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this.data.select) {
|
||||
this.data.select();
|
||||
if (this.data.closeOnSelect !== false) {
|
||||
this.menu.close();
|
||||
}
|
||||
this.menu.close();
|
||||
} else {
|
||||
this.openSubMenu();
|
||||
}
|
||||
@@ -89,38 +60,11 @@ export class MenuSubMenu extends MenuFocusable {
|
||||
}
|
||||
|
||||
openSubMenu() {
|
||||
if (this.data.openOnHover === false) {
|
||||
const { menu } = popMenu(popupTargetFromElement(this), {
|
||||
options: {
|
||||
...this.data.options,
|
||||
onComplete: () => {
|
||||
if (this.data.closeOnSelect !== false) {
|
||||
this.menu.close();
|
||||
}
|
||||
},
|
||||
onClose: () => {
|
||||
menu.menuElement.remove();
|
||||
this.data.options.onClose?.();
|
||||
},
|
||||
},
|
||||
middleware: this.data.middleware,
|
||||
});
|
||||
if (this.data.autoHeight) {
|
||||
menu.menuElement.style.minHeight = 'fit-content';
|
||||
menu.menuElement.style.maxHeight = 'fit-content';
|
||||
}
|
||||
menu.menuElement.style.minWidth = '200px';
|
||||
this.menu.openSubMenu(menu);
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = this.menu.currentFocused$.value;
|
||||
const menu = new Menu({
|
||||
...this.data.options,
|
||||
onComplete: () => {
|
||||
if (this.data.closeOnSelect !== false) {
|
||||
this.menu.close();
|
||||
}
|
||||
this.menu.close();
|
||||
},
|
||||
onClose: () => {
|
||||
menu.menuElement.remove();
|
||||
@@ -130,14 +74,9 @@ export class MenuSubMenu extends MenuFocusable {
|
||||
},
|
||||
});
|
||||
this.menu.menuElement.parentElement?.append(menu.menuElement);
|
||||
if (this.data.autoHeight) {
|
||||
menu.menuElement.style.minHeight = 'fit-content';
|
||||
menu.menuElement.style.maxHeight = 'fit-content';
|
||||
}
|
||||
menu.menuElement.style.minWidth = '200px';
|
||||
const unsub = autoUpdate(this, menu.menuElement, () => {
|
||||
computePosition(this, menu.menuElement, {
|
||||
middleware: this.data.middleware ?? subMenuMiddleware,
|
||||
middleware: subMenuMiddleware,
|
||||
})
|
||||
.then(({ x, y }) => {
|
||||
menu.menuElement.style.left = `${x}px`;
|
||||
@@ -186,22 +125,14 @@ export class MobileSubMenu extends MenuFocusable {
|
||||
options: {
|
||||
...this.data.options,
|
||||
onComplete: () => {
|
||||
if (this.data.closeOnSelect !== false) {
|
||||
this.menu.close();
|
||||
}
|
||||
this.menu.close();
|
||||
},
|
||||
onClose: () => {
|
||||
menu.menuElement.remove();
|
||||
this.data.options.onClose?.();
|
||||
},
|
||||
},
|
||||
middleware: this.data.middleware,
|
||||
});
|
||||
if (this.data.autoHeight) {
|
||||
menu.menuElement.style.minHeight = 'fit-content';
|
||||
menu.menuElement.style.maxHeight = 'fit-content';
|
||||
}
|
||||
menu.menuElement.style.minWidth = '200px';
|
||||
this.menu.openSubMenu(menu);
|
||||
}
|
||||
|
||||
@@ -244,10 +175,6 @@ export const subMenuItems = {
|
||||
options: MenuOptions;
|
||||
disableArrow?: boolean;
|
||||
hide?: () => boolean;
|
||||
openOnHover?: boolean;
|
||||
middleware?: Middleware[];
|
||||
autoHeight?: boolean;
|
||||
closeOnSelect?: boolean;
|
||||
}) =>
|
||||
menu => {
|
||||
if (config.hide?.() || !menu.search(config.name)) {
|
||||
@@ -263,10 +190,6 @@ export const subMenuItems = {
|
||||
${config.disableArrow ? nothing : ArrowRightSmallIcon()} `,
|
||||
class: config.class,
|
||||
options: config.options,
|
||||
openOnHover: config.openOnHover,
|
||||
middleware: config.middleware,
|
||||
autoHeight: config.autoHeight,
|
||||
closeOnSelect: config.closeOnSelect,
|
||||
};
|
||||
return renderSubMenu(data, menu);
|
||||
},
|
||||
|
||||
@@ -229,9 +229,9 @@ export class DatePicker extends WithDisposable(LitElement) {
|
||||
|
||||
private _modeDecade(offset: number) {
|
||||
this._yearCursor = clamp(
|
||||
this._yearCursor + offset,
|
||||
this._minYear,
|
||||
this._maxYear
|
||||
this._maxYear,
|
||||
this._yearCursor + offset
|
||||
);
|
||||
this._getYearMatrix();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@emotion/css": "^11.13.5",
|
||||
@@ -27,12 +27,10 @@
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"rxjs": "^7.8.1",
|
||||
"vitest": "^3.2.3",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -48,5 +46,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.25.7"
|
||||
"version": "0.22.4"
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { compareDateKeys } from '../core/group-by/compare-date-keys.js';
|
||||
|
||||
describe('compareDateKeys', () => {
|
||||
it('sorts relative keys ascending', () => {
|
||||
const cmp = compareDateKeys('date-relative', true);
|
||||
const keys = ['today', 'last7', 'yesterday', 'last30'];
|
||||
const sorted = [...keys].sort(cmp);
|
||||
expect(sorted).toEqual(['last30', 'last7', 'yesterday', 'today']);
|
||||
});
|
||||
|
||||
it('sorts relative keys descending', () => {
|
||||
const cmp = compareDateKeys('date-relative', false);
|
||||
const keys = ['today', 'last7', 'yesterday', 'last30'];
|
||||
const sorted = [...keys].sort(cmp);
|
||||
expect(sorted).toEqual(['today', 'yesterday', 'last7', 'last30']);
|
||||
});
|
||||
|
||||
it('sorts numeric keys correctly', () => {
|
||||
const asc = compareDateKeys('date-day', true);
|
||||
const desc = compareDateKeys('date-day', false);
|
||||
const keys = ['3', '1', '2'];
|
||||
expect([...keys].sort(asc)).toEqual(['1', '2', '3']);
|
||||
expect([...keys].sort(desc)).toEqual(['3', '2', '1']);
|
||||
});
|
||||
|
||||
it('handles mixed relative and numeric keys', () => {
|
||||
const cmp = compareDateKeys('date-relative', true);
|
||||
const keys = ['today', '1', 'yesterday', '2'];
|
||||
const sorted = [...keys].sort(cmp);
|
||||
expect(sorted[0]).toBe('1');
|
||||
expect(sorted[sorted.length - 1]).toBe('today');
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { mobileEffects } from '../view-presets/table/mobile/effect.js';
|
||||
import type { MobileTableGroup } from '../view-presets/table/mobile/group.js';
|
||||
import { pcEffects } from '../view-presets/table/pc/effect.js';
|
||||
import type { TableGroup } from '../view-presets/table/pc/group.js';
|
||||
|
||||
/** @vitest-environment happy-dom */
|
||||
|
||||
describe('TableGroup', () => {
|
||||
test('toggle collapse on pc', () => {
|
||||
pcEffects();
|
||||
const group = document.createElement(
|
||||
'affine-data-view-table-group'
|
||||
) as TableGroup;
|
||||
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(true);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
});
|
||||
|
||||
test('toggle collapse on mobile', () => {
|
||||
mobileEffects();
|
||||
const group = document.createElement(
|
||||
'mobile-table-group'
|
||||
) as MobileTableGroup;
|
||||
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(true);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user