Compare commits

..

1 Commits

Author SHA1 Message Date
fengmk2 b1d7011047 chore(server): use jemalloc to reduce RSS 2025-07-10 11:22:37 +08:00
894 changed files with 5489 additions and 60962 deletions
+2 -11
View File
@@ -18,19 +18,11 @@ services:
ports:
- 6379:6379
# https://mailpit.axllent.org/docs/install/docker/
mailpit:
image: axllent/mailpit:latest
mailhog:
image: mailhog/mailhog:latest
ports:
- 1025:1025
- 8025:8025
environment:
MP_MAX_MESSAGES: 5000
MP_DATABASE: /data/mailpit.db
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
volumes:
- mailpit_data:/data
# https://manual.manticoresearch.com/Starting_the_server/Docker
manticoresearch:
@@ -95,5 +87,4 @@ networks:
volumes:
postgres_data:
manticoresearch_data:
mailpit_data:
elasticsearch_data:
+11 -98
View File
@@ -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`",
@@ -229,46 +219,6 @@
"type": "boolean",
"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": {
"type": "array",
"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 \"\"",
"default": ""
},
"fallbackSMTP.port": {
"type": "number",
"description": "Port of the email server (they commonly are 25, 465 or 587)\n@default 465",
"default": 465
},
"fallbackSMTP.username": {
"type": "string",
"description": "Username used to authenticate the email server\n@default \"\"",
"default": ""
},
"fallbackSMTP.password": {
"type": "string",
"description": "Password used to authenticate the email server\n@default \"\"",
"default": ""
},
"fallbackSMTP.sender": {
"type": "string",
"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 TSL certification verification. Enable it for self-signed certificates.\n@default false",
"default": false
}
}
},
@@ -679,34 +629,14 @@
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether to enable the copilot plugin. <br> Document: <a href=\"https://docs.affine.pro/self-host-affine/administer/ai\" target=\"_blank\">https://docs.affine.pro/self-host-affine/administer/ai</a>\n@default false",
"description": "Whether to enable the copilot plugin.\n@default false",
"default": false
},
"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@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",
"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"
}
}
},
"providers.openai": {
"type": "object",
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.openai.com/v1\"}\n@link https://github.com/openai/openai-node",
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\"}\n@link https://github.com/openai/openai-node",
"default": {
"apiKey": "",
"baseURL": "https://api.openai.com/v1"
"apiKey": ""
}
},
"providers.fal": {
@@ -718,10 +648,9 @@
},
"providers.gemini": {
"type": "object",
"description": "The config for the gemini provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://generativelanguage.googleapis.com/v1beta\"}",
"description": "The config for the gemini provider.\n@default {\"apiKey\":\"\"}",
"default": {
"apiKey": "",
"baseURL": "https://generativelanguage.googleapis.com/v1beta"
"apiKey": ""
}
},
"providers.geminiVertex": {
@@ -768,10 +697,9 @@
},
"providers.anthropic": {
"type": "object",
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.anthropic.com/v1\"}",
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\"}",
"default": {
"apiKey": "",
"baseURL": "https://api.anthropic.com/v1"
"apiKey": ""
}
},
"providers.anthropicVertex": {
@@ -1108,33 +1036,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": {}
}
}
},
+15 -15
View File
@@ -29,25 +29,25 @@ const isInternal = buildType === 'internal';
const replicaConfig = {
stable: {
web: 2,
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 2,
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 2,
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 2,
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 2,
web: 3,
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 3,
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 3,
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 3,
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 3,
},
beta: {
web: 1,
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 1,
sync: Number(process.env.BETA_SYNC_REPLICA) || 1,
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 1,
doc: Number(process.env.BETA_DOC_REPLICA) || 1,
web: 2,
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 2,
sync: Number(process.env.BETA_SYNC_REPLICA) || 2,
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 2,
doc: Number(process.env.BETA_DOC_REPLICA) || 2,
},
canary: {
web: 1,
graphql: 1,
sync: 1,
renderer: 1,
doc: 1,
web: 2,
graphql: 2,
sync: 2,
renderer: 2,
doc: 2,
},
};
-6
View File
@@ -4,15 +4,9 @@ inputs:
app-version:
description: 'App Version'
required: true
ios-app-version:
description: 'iOS App Store Version (Optional, use App version if empty)'
required: false
type: string
runs:
using: 'composite'
steps:
- name: 'Write Version'
shell: bash
env:
IOS_APP_VERSION: ${{ inputs.ios-app-version }}
run: ./scripts/set-version.sh ${{ inputs.app-version }}
@@ -1,4 +1,4 @@
replicaCount: 2
replicaCount: 3
enabled: false
database:
connectionName: ""
@@ -33,11 +33,8 @@ service:
resources:
limits:
memory: "1Gi"
cpu: "1"
requests:
memory: "512Mi"
cpu: "100m"
memory: "4Gi"
cpu: "2"
volumes: []
volumeMounts: []
+1 -1
View File
@@ -465,7 +465,7 @@ jobs:
name: ${{ env.RELEASE_VERSION }}
draft: ${{ inputs.build-type == 'stable' }}
prerelease: ${{ inputs.build-type != 'stable' }}
tag_name: v${{ env.RELEASE_VERSION}}
tag_name: ${{ env.RELEASE_VERSION}}
files: |
./release/*
./release/.env.example
+2 -6
View File
@@ -12,9 +12,6 @@ on:
build-type:
type: string
required: true
ios-app-version:
type: string
required: false
env:
BUILD_TYPE: ${{ inputs.build-type }}
@@ -81,7 +78,7 @@ jobs:
path: packages/frontend/apps/android/dist
ios:
runs-on: 'macos-15'
runs-on: ${{ github.ref_name == 'canary' && 'macos-latest' || 'blaze/macos-14' }}
needs:
- build-ios-web
steps:
@@ -90,7 +87,6 @@ jobs:
uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
ios-app-version: ${{ inputs.ios-app-version }}
- name: 'Update Code Sign Identity'
shell: bash
run: ./packages/frontend/apps/ios/update_code_sign_identity.sh
@@ -110,7 +106,7 @@ jobs:
enableScripts: false
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 16.4
xcode-version: 16.2
- name: Install Swiftformat
run: brew install swiftformat
- name: Cap sync
+2 -9
View File
@@ -21,10 +21,6 @@ on:
required: true
type: boolean
default: false
ios-app-version:
description: 'iOS App Store Version (Optional, use tag version if empty)'
required: false
type: string
permissions:
contents: write
@@ -34,7 +30,6 @@ permissions:
packages: write
security-events: write
attestations: write
issues: write
jobs:
prepare:
@@ -74,8 +69,7 @@ jobs:
name: Wait for approval
with:
secret: ${{ secrets.GITHUB_TOKEN }}
approvers: forehalo,fengmk2,darkskygit
minimum-approvals: 1
approvers: forehalo,fengmk2
fail-on-denial: true
issue-title: Please confirm to release docker image
issue-body: |
@@ -84,7 +78,7 @@ jobs:
Tag: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }}
> comment with "approve", "approved", "lgtm", "yes" to approve
> comment with "deny", "denied", "no" to deny
> comment with "deny", "deny", "no" to deny
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -123,4 +117,3 @@ jobs:
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
ios-app-version: ${{ inputs.ios-app-version }}
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
shell: cmd
run: |
cd ${{ env.ARCHIVE_DIR }}/out
signtool sign /tr http://timestamp.globalsign.com/tsa/r6advanced1 /td sha256 /fd sha256 /a ${{ inputs.files }}
signtool sign /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a ${{ inputs.files }}
- name: zip file
shell: cmd
run: |
-1
View File
@@ -2,7 +2,6 @@
**/node_modules
.yarn
.github/helm
.git
.vscode
.yarnrc.yml
.docker
Generated
+12 -12
View File
@@ -93,7 +93,7 @@ dependencies = [
"symphonia",
"thiserror 2.0.12",
"uuid",
"windows 0.61.3",
"windows 0.61.1",
"windows-core 0.61.2",
]
@@ -1691,7 +1691,7 @@ dependencies = [
"libc",
"log",
"rustversion",
"windows 0.61.3",
"windows 0.61.1",
]
[[package]]
@@ -2284,7 +2284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@@ -4732,9 +4732,9 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.25.8"
version = "0.25.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7b8994f367f16e6fa14b5aebbcb350de5d7cbea82dc5b00ae997dd71680dd2"
checksum = "ac5fff5c47490dfdf473b5228039bfacad9d765d9b6939d26bf7cc064c1c7822"
dependencies = [
"cc",
"regex",
@@ -4842,9 +4842,9 @@ dependencies = [
[[package]]
name = "tree-sitter-scala"
version = "0.24.0"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7516aeb3d1f40ede8e3045b163e86993b3434514dd06c34c0b75e782d9a0b251"
checksum = "efde5e68b4736e9eac17bfa296c6f104a26bffab363b365eb898c40a63c15d2f"
dependencies = [
"cc",
"tree-sitter-language",
@@ -5334,7 +5334,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -5365,9 +5365,9 @@ dependencies = [
[[package]]
name = "windows"
version = "0.61.3"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
dependencies = [
"windows-collections",
"windows-core 0.61.2",
@@ -5477,9 +5477,9 @@ dependencies = [
[[package]]
name = "windows-link"
version = "0.1.3"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-numerics"
+1 -1
View File
@@ -93,7 +93,7 @@ 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-scala = { version = "0.23" }
tree-sitter-typescript = { version = "0.23" }
uniffi = "0.29"
url = { version = "2.5" }
+1 -1
View File
@@ -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**
-1
View File
@@ -266,7 +266,6 @@
"./components/toggle-button": "./src/components/toggle-button.ts",
"./components/toggle-switch": "./src/components/toggle-switch.ts",
"./components/toolbar": "./src/components/toolbar.ts",
"./components/tooltip": "./src/components/tooltip.ts",
"./components/view-dropdown-menu": "./src/components/view-dropdown-menu.ts",
"./components/tooltip-content-with-shortcut": "./src/components/tooltip-content-with-shortcut.ts",
"./components/resource": "./src/components/resource.ts",
@@ -1 +0,0 @@
export * from '@blocksuite/affine-components/tooltip';
@@ -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",
@@ -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:*",
"@lit/context": "^1.1.2",
@@ -10,7 +10,6 @@
"author": "toeverything",
"license": "MIT",
"dependencies": {
"@affine/component": "workspace:*",
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
@@ -19,7 +18,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:*",
"@emoji-mart/data": "^1.2.1",
@@ -1,54 +1,18 @@
import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
import { createLitPortal } from '@blocksuite/affine-components/portal';
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
import { type CalloutBlockModel, DefaultTheme } 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, signal } from '@preact/signals-core';
import type { TemplateResult } from 'lit';
import { flip, offset } from '@floating-ui/dom';
import { css, html } from 'lit';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.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) => {
console.log(icon);
if (!icon) {
return '💡';
}
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 '💡';
};
import { query } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockModel> {
static override styles = css`
:host {
@@ -58,12 +22,14 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
.affine-callout-block-container {
display: flex;
align-items: flex-start;
padding: 5px 10px;
border-radius: 8px;
background-color: ${unsafeCSSVarV2('block/callout/background/grey')};
}
.affine-callout-emoji-container {
margin-right: 10px;
margin-top: 14px;
user-select: none;
font-size: 1.2em;
width: 24px;
@@ -71,15 +37,6 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
display: flex;
align-items: center;
justify-content: center;
margin-top: 10px;
margin-bottom: 10px;
flex-shrink: 0;
position: relative;
}
.affine-callout-emoji {
display: flex;
align-items: center;
justify-content: center;
}
.affine-callout-emoji:hover {
cursor: pointer;
@@ -91,92 +48,37 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
min-width: 0;
padding-left: 10px;
}
.icon-picker-container {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
background: white;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
width: 300px;
height: 400px;
}
`;
private readonly showIconPicker$ = signal(false);
private _closeEmojiMenu() {
this.showIconPicker$.value = false;
}
private _toggleIconPicker() {
this.showIconPicker$.value = !this.showIconPicker$.value;
}
private _renderIconPicker() {
if (!this.showIconPicker$.value) {
return html``;
private _emojiMenuAbortController: AbortController | null = null;
private readonly _toggleEmojiMenu = () => {
if (this._emojiMenuAbortController) {
this._emojiMenuAbortController.abort();
}
this._emojiMenuAbortController = new AbortController();
// Get IconPickerService from the framework
const iconPickerService = this.std.getOptional(IconPickerServiceIdentifier);
if (!iconPickerService) {
console.warn('IconPickerService not found');
return html``;
}
const theme = this.std.get(ThemeProvider).theme$.value;
// Get the uni-component from the service
const iconPickerComponent = iconPickerService.iconPickerComponent;
// Create props for the icon picker
const props = {
onSelect: (iconData?: IconData) => {
this.model.props.icon$.value = iconData;
this._closeEmojiMenu(); // Close the picker after selection
},
onClose: () => {
this._closeEmojiMenu();
},
};
return html`
<div
@click=${(e: MouseEvent) => {
e.stopPropagation();
createLitPortal({
template: html`<affine-emoji-menu
.theme=${theme}
.onEmojiSelect=${(data: any) => {
this.model.props.emoji = data.native;
}}
class="icon-picker-container"
>
${renderUniLit(iconPickerComponent, props)}
</div>
`;
}
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;
}
// 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);
></affine-emoji-menu>`,
portalStyles: {
zIndex: 'var(--affine-z-index-popover)',
},
container: this.host,
computePosition: {
referenceElement: this._emojiButton,
placement: 'bottom-start',
middleware: [flip(), offset(4)],
autoUpdate: { animationFrame: true },
},
abortController: this._emojiMenuAbortController,
closeOnClickAway: true,
});
};
get attributeRenderer() {
@@ -195,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>(
@@ -205,32 +110,18 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
}
override renderBlock() {
const icon = this.model.props.icon$.value;
const background = this.model.props.background$.value;
const themeProvider = this.std.get(ThemeProvider);
const theme = themeProvider.theme$.value;
const backgroundColor = themeProvider.generateColorProperty(
background || DefaultTheme.NoteBackgroundColorMap.White,
DefaultTheme.NoteBackgroundColorMap.White,
theme
);
const emoji = this.model.props.emoji$.value;
return html`
<div
class="affine-callout-block-container"
@click=${this._handleBlockClick}
style=${styleMap({
backgroundColor: backgroundColor,
})}
>
<div class="affine-callout-block-container">
<div
@click=${this._toggleIconPicker}
@click=${this._toggleEmojiMenu}
contenteditable="false"
class="affine-callout-emoji-container"
style=${styleMap({
display: emoji.length === 0 ? 'none' : undefined,
})}
>
<span class="affine-callout-emoji">${getIcon(icon)}</span>
${this._renderIconPicker()}
<span class="affine-callout-emoji">${emoji}</span>
</div>
<div class="affine-callout-children">
${this.renderChildren(this.model)}
@@ -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,12 +1,24 @@
import { CalloutBlockModel } from '@blocksuite/affine-model';
import { focusBlockEnd } from '@blocksuite/affine-shared/commands';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
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',
@@ -1,124 +0,0 @@
import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
import { CalloutBlockModel, DefaultTheme } from '@blocksuite/affine-model';
import {
type ToolbarAction,
type ToolbarActionGroup,
type ToolbarModuleConfig,
ToolbarModuleExtension,
} from '@blocksuite/affine-shared/services';
import { PaletteIcon } from '@blocksuite/icons/lit';
import { BlockFlavourIdentifier } from '@blocksuite/std';
import type { ExtensionType } from '@blocksuite/store';
import { html } from 'lit';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.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) => {
// Map text highlight colors to note background colors
const colorMap: Record<
string,
keyof typeof DefaultTheme.NoteBackgroundColorMap | null
> = {
default: null,
red: 'Red',
orange: 'Orange',
yellow: 'Yellow',
green: 'Green',
teal: 'Green', // Map teal to green as it's not available in NoteBackgroundColorMap
blue: 'Blue',
purple: 'Purple',
grey: 'White', // Map grey to white as it's the closest available
};
const mappedColor = colorMap[color];
const backgroundValue = mappedColor
? DefaultTheme.NoteBackgroundColorMap[mappedColor]
: null;
ctx.store.updateBlock(model, { background: backgroundValue });
};
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 builtinToolbarConfig = {
actions: [
{
id: 'style',
actions: [backgroundColorAction],
} satisfies ToolbarActionGroup<ToolbarAction>,
],
} as const satisfies ToolbarModuleConfig;
export const createBuiltinToolbarConfigExtension = (
flavour: string
): ExtensionType[] => {
return [
ToolbarModuleExtension({
id: BlockFlavourIdentifier(flavour),
config: builtinToolbarConfig,
}),
];
};
@@ -1,11 +1,14 @@
import { CalloutBlockComponent } from './callout-block';
import { EmojiMenu } from './emoji-menu';
export function effects() {
customElements.define('affine-callout', CalloutBlockComponent);
customElements.define('affine-emoji-menu', EmojiMenu);
}
declare global {
interface HTMLElementTagNameMap {
'affine-callout': CalloutBlockComponent;
'affine-emoji-menu': EmojiMenu;
}
}
@@ -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>`;
}
}
@@ -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'),
]);
}
}
+1 -1
View File
@@ -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",
@@ -39,13 +39,6 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
private readonly _loadTheme = async (
highlighter: HighlighterCore
): Promise<void> => {
// It is possible that by the time the highlighter is ready all instances
// have already been unmounted. In that case there is no need to load
// themes or update state.
if (CodeBlockHighlighter._refCount === 0) {
return;
}
const config = this.std.getOptional(CodeBlockConfigExtension.identifier);
const darkTheme = config?.theme?.dark ?? CODE_BLOCK_DEFAULT_DARK_THEME;
const lightTheme = config?.theme?.light ?? CODE_BLOCK_DEFAULT_LIGHT_THEME;
@@ -85,27 +78,14 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
override unmounted(): void {
CodeBlockHighlighter._refCount--;
// Dispose the shared highlighter **after** any in-flight creation finishes.
if (CodeBlockHighlighter._refCount !== 0) {
return;
}
const doDispose = (highlighter: HighlighterCore | null) => {
if (highlighter) {
highlighter.dispose();
}
// Only dispose the shared highlighter when no instances are using it
if (
CodeBlockHighlighter._refCount === 0 &&
CodeBlockHighlighter._sharedHighlighter
) {
CodeBlockHighlighter._sharedHighlighter.dispose();
CodeBlockHighlighter._sharedHighlighter = null;
CodeBlockHighlighter._highlighterPromise = null;
};
if (CodeBlockHighlighter._sharedHighlighter) {
// Highlighter already created dispose immediately.
doDispose(CodeBlockHighlighter._sharedHighlighter);
} else if (CodeBlockHighlighter._highlighterPromise) {
// Highlighter still being created wait for it, then dispose.
CodeBlockHighlighter._highlighterPromise
.then(doDispose)
.catch(console.error);
}
}
}
@@ -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",
@@ -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",
@@ -164,10 +164,8 @@ export class DatabaseBlockDataSource extends DataSourceBase {
readonly$: ReadonlySignal<boolean> = computed(() => {
return (
this._model.store.readonly ||
(IS_MOBILE &&
!this._model.store.provider
.get(FeatureFlagService)
.getFlag('enable_mobile_database_editing'))
// TODO(@L-Sun): use block level readonly
IS_MOBILE
);
});
@@ -13,7 +13,6 @@ import {
BlockElementCommentManager,
CommentProviderIdentifier,
DocModeProvider,
FeatureFlagService,
NotificationProvider,
type TelemetryEventMap,
TelemetryProvider,
@@ -35,7 +34,6 @@ import {
uniMap,
} from '@blocksuite/data-view';
import { widgetPresets } from '@blocksuite/data-view/widget-presets';
import { IS_MOBILE } from '@blocksuite/global/env';
import { Rect } from '@blocksuite/global/gfx';
import {
CommentIcon,
@@ -50,7 +48,6 @@ import { autoUpdate } from '@floating-ui/dom';
import { computed, signal } from '@preact/signals-core';
import { html, nothing } from 'lit';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import { popSideDetail } from './components/layout.js';
import { DatabaseConfigExtension } from './config.js';
@@ -352,7 +349,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
this.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true');
this.classList.add(databaseBlockStyles);
this.listenFullWidthChange();
this.handleMobileEditing();
}
listenFullWidthChange() {
@@ -368,41 +364,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
})
);
}
handleMobileEditing() {
if (!IS_MOBILE) return;
let notifyClosed = true;
const handler = () => {
if (
!this.std
.get(FeatureFlagService)
.getFlag('enable_mobile_database_editing')
) {
const notification = this.std.getOptional(NotificationProvider);
if (notification && notifyClosed) {
notifyClosed = false;
notification.notify({
title: html`<div
style=${styleMap({
whiteSpace: 'wrap',
})}
>
Mobile database editing is not supported yet. You can open it in
experimental features, or edit it in desktop mode.
</div>`,
accent: 'warning',
onClose: () => {
notifyClosed = true;
},
});
}
}
};
this.disposables.addFromEvent(this, 'click', handler);
}
private readonly dataViewRootLogic = lazy(
() =>
new DataViewRootUILogic({
@@ -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",
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
@@ -1,7 +1,6 @@
import { ImageBlockModel } from '@blocksuite/affine-model';
import {
ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig,
ToolbarModuleExtension,
} from '@blocksuite/affine-shared/services';
@@ -50,10 +49,6 @@ const builtinToolbarConfig = {
});
},
},
{
id: 'c.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
@@ -24,7 +24,6 @@ import {
getPrevContentBlock,
matchModels,
} from '@blocksuite/affine-shared/utils';
import { IS_ANDROID, IS_MOBILE } from '@blocksuite/global/env';
import { BlockSelection, type EditorHost } from '@blocksuite/std';
import type { BlockModel, Text } from '@blocksuite/store';
@@ -79,28 +78,6 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
index: lengthBeforeJoin,
length: 0,
}).catch(console.error);
// due to some IME like Microsoft Swift IME on Android will reset range after join text,
// for example:
//
// $ZERO_WIDTH_FOR_EMPTY_LINE <--- p1
// |aaa <--- p2
//
// after pressing backspace, during beforeinput event, the native range is (p1, 1) -> (p2, 0)
// and after browser and IME handle the event, the native range is (p1, 1) -> (p1, 1)
//
// a|aa <--- p1
//
// so we need to set range again after join text.
if (IS_ANDROID) {
setTimeout(() => {
asyncSetInlineRange(editorHost.std, prevBlock, {
index: lengthBeforeJoin,
length: 0,
}).catch(console.error);
});
}
return true;
}
@@ -114,17 +91,10 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
...EMBED_BLOCK_MODEL_LIST,
])
) {
// due to create a block selection will clear text selection, which lead
// the virtual keyboard to be auto closed on mobile. This behavior breaks
// the user experience.
if (!IS_MOBILE) {
const selection = editorHost.selection.create(BlockSelection, {
blockId: prevBlock.id,
});
editorHost.selection.setGroup('note', [selection]);
} else {
doc.deleteBlock(prevBlock);
}
const selection = editorHost.selection.create(BlockSelection, {
blockId: prevBlock.id,
});
editorHost.selection.setGroup('note', [selection]);
if (model.text?.length === 0) {
doc.deleteBlock(model, {
+1 -1
View File
@@ -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",
@@ -634,9 +634,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
const movedElements = new Set([
...selectedElements,
...selectedElements.flatMap(el =>
isGfxGroupCompatibleModel(el) ? el.descendantElements : []
),
...selectedElements
.map(el => (isGfxGroupCompatibleModel(el) ? el.descendantElements : []))
.flat(),
]);
movedElements.forEach(element => {
@@ -4,6 +4,6 @@ export * from './clipboard/command';
export * from './edgeless-root-block.js';
export { EdgelessRootService } from './edgeless-root-service.js';
export * from './utils/clipboard-utils.js';
export { getElementProps, sortEdgelessElements } from './utils/clone-utils.js';
export { sortEdgelessElements } from './utils/clone-utils.js';
export { isCanvasElement } from './utils/query.js';
export { EDGELESS_BLOCK_CHILD_PADDING } from '@blocksuite/affine-shared/consts';
@@ -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",
@@ -5,7 +5,6 @@ import {
} from '@blocksuite/affine-shared/commands';
import {
ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig,
} from '@blocksuite/affine-shared/services';
import { CaptionIcon, CopyIcon, DeleteIcon } from '@blocksuite/icons/lit';
@@ -62,10 +61,6 @@ export const surfaceRefToolbarModuleConfig: ToolbarModuleConfig = {
surfaceRefBlock.captionElement.show();
},
},
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{
id: 'a.clipboard',
placement: ActionPlacement.More,
@@ -11,7 +11,7 @@ import {
getBoundWithRotation,
intersects,
} from '@blocksuite/global/gfx';
import { type BlockStdScope, SurfaceSelection } from '@blocksuite/std';
import type { BlockStdScope } from '@blocksuite/std';
import type {
GfxCompatibleInterface,
GridManager,
@@ -298,10 +298,7 @@ export class DomRenderer {
viewportBounds,
zoom
);
const zIndexStyle = {
'z-index': this.layerManager.getZIndex(elementModel),
};
Object.assign(domElement.style, geometricStyles, zIndexStyle);
Object.assign(domElement.style, geometricStyles);
Object.assign(domElement.style, PLACEHOLDER_RESET_STYLES);
// Clear classes specific to shapes, if applicable
@@ -338,10 +335,7 @@ export class DomRenderer {
zoom
);
const opacityStyle = getOpacity(elementModel);
const zIndexStyle = {
'z-index': this.layerManager.getZIndex(elementModel),
};
Object.assign(domElement.style, geometricStyles, opacityStyle, zIndexStyle);
Object.assign(domElement.style, geometricStyles, opacityStyle);
this._renderElement(elementModel, domElement);
}
@@ -390,36 +384,6 @@ export class DomRenderer {
this.refresh();
})
);
// Workaround for the group rendering reactive update when selection changed
let lastSet = new Set<string>();
this._disposables.add(
this.std.selection.filter$(SurfaceSelection).subscribe(selections => {
const groupRelatedSelection = new Set(
selections.flatMap(s =>
s.elements.flatMap(e => {
const element = surfaceModel.getElementById(e);
if (
element &&
(element.type === 'group' || element.groups.length !== 0)
) {
return [element.id, ...element.groups.map(g => g.id)];
}
return [];
})
)
);
if (lastSet.symmetricDifference(groupRelatedSelection).size !== 0) {
lastSet.union(groupRelatedSelection).forEach(g => {
this._markElementDirty(g, UpdateType.ELEMENT_UPDATED);
});
this.refresh();
}
lastSet = groupRelatedSelection;
})
);
}
addOverlay = (overlay: Overlay) => {
+1 -1
View File
@@ -20,7 +20,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",
+2 -3
View File
@@ -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:*",
@@ -73,8 +73,7 @@
"./edgeless-line-styles-panel": "./src/edgeless-line-styles-panel/index.ts",
"./edgeless-shape-color-picker": "./src/edgeless-shape-color-picker/index.ts",
"./open-doc-dropdown-menu": "./src/open-doc-dropdown-menu/index.ts",
"./slider": "./src/slider/index.ts",
"./tooltip": "./src/tooltip/index.ts"
"./slider": "./src/slider/index.ts"
},
"files": [
"src",
@@ -193,7 +193,6 @@ export const menuButtonItems = {
(config: {
name: string;
label?: () => TemplateResult;
info?: TemplateResult;
prefix?: TemplateResult;
postfix?: TemplateResult;
isSelected?: boolean;
@@ -212,7 +211,7 @@ 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)}
`;
@@ -85,8 +85,6 @@ export class MenuSubMenu extends MenuFocusable {
.catch(err => console.error(err));
});
this.menu.openSubMenu(menu);
// in case that the menu is not closed, but the component is removed,
this.disposables.add(unsub);
}
protected override render(): unknown {
@@ -18,7 +18,6 @@ export const LoadingIcon = ({
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="none"
style="fill: none;"
>
<style>
.spinner {
@@ -1,4 +1,3 @@
import { effects as tooltipEffects } from '../tooltip/effect.js';
import { EditorIconButton } from './icon-button.js';
import {
EditorMenuAction,
@@ -7,6 +6,7 @@ import {
} from './menu-button.js';
import { EditorToolbarSeparator } from './separator.js';
import { EditorToolbar } from './toolbar.js';
import { Tooltip } from './tooltip.js';
export { EditorChevronDown } from './chevron-down.js';
export { ToolbarMoreMenuConfigExtension } from './config.js';
@@ -20,6 +20,7 @@ export { MenuContext } from './menu-context.js';
export { EditorToolbarSeparator } from './separator.js';
export { darkToolbarStyles, lightToolbarStyles } from './styles.js';
export { EditorToolbar } from './toolbar.js';
export { Tooltip } from './tooltip.js';
export type {
AdvancedMenuItem,
FatMenuItems,
@@ -37,12 +38,11 @@ export {
} from './utils.js';
export function effects() {
tooltipEffects();
customElements.define('editor-toolbar-separator', EditorToolbarSeparator);
customElements.define('editor-toolbar', EditorToolbar);
customElements.define('editor-icon-button', EditorIconButton);
customElements.define('editor-menu-button', EditorMenuButton);
customElements.define('editor-menu-content', EditorMenuContent);
customElements.define('editor-menu-action', EditorMenuAction);
customElements.define('affine-tooltip', Tooltip);
}
@@ -1,7 +0,0 @@
import { Tooltip } from './tooltip.js';
export function effects() {
if (!customElements.get('affine-tooltip')) {
customElements.define('affine-tooltip', Tooltip);
}
}
@@ -1,2 +0,0 @@
export { effects } from './effect.js';
export { Tooltip } from './tooltip.js';
+1 -1
View File
@@ -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",
@@ -65,7 +65,7 @@ export abstract class DataViewUILogicBase<
return handler(context);
});
}
setSelection(selection?: Selection) {
setSelection(selection?: Selection): void {
this.root.setSelection(selection);
}
@@ -73,9 +73,7 @@ export class MobileKanbanCell extends SignalWatcher(
if (this.view.readonly$.value) {
return;
}
const setSelection = this.kanbanViewLogic.setSelection.bind(
this.kanbanViewLogic
);
const setSelection = this.kanbanViewLogic.setSelection;
const viewId = this.kanbanViewLogic.view.id;
if (setSelection && viewId) {
if (editing && this.cell?.beforeEnterEditMode() === false) {
@@ -103,12 +101,12 @@ export class MobileKanbanCell extends SignalWatcher(
this.disposables.add(
effect(() => {
const isEditing = this.isSelectionEditing$.value;
if (isEditing && !this.isEditing$.peek()) {
if (isEditing) {
this.isEditing$.value = true;
requestAnimationFrame(() => {
this._cell.value?.afterEnterEditingMode();
});
} else if (!isEditing && this.isEditing$.peek()) {
} else {
this._cell.value?.beforeExitEditingMode();
this.isEditing$.value = false;
}
@@ -86,9 +86,6 @@ export class MobileKanbanViewUILogic extends DataViewUILogicBase<
}
renderAddGroup = () => {
if (this.readonly) {
return;
}
const addGroup = this.groupManager.addGroup;
if (!addGroup) {
return;
@@ -68,9 +68,7 @@ export class MobileTableCell extends SignalWatcher(
if (this.view.readonly$.value) {
return;
}
const setSelection = this.tableViewLogic.setSelection.bind(
this.tableViewLogic
);
const setSelection = this.tableViewLogic.setSelection;
const viewId = this.tableViewLogic.view.id;
if (setSelection && viewId) {
if (editing && this.cell?.beforeEnterEditMode() === false) {
@@ -105,13 +103,13 @@ export class MobileTableCell extends SignalWatcher(
this.disposables.add(
effect(() => {
const isEditing = this.isSelectionEditing$.value;
if (isEditing && !this.isEditing$.peek()) {
if (isEditing) {
this.isEditing$.value = true;
const cell = this._cell.value;
requestAnimationFrame(() => {
cell?.afterEnterEditingMode();
});
} else if (!isEditing && this.isEditing$.peek()) {
} else {
this._cell.value?.beforeExitEditingMode();
this.isEditing$.value = false;
}
@@ -5,6 +5,12 @@ export const mobileTableViewWrapper = css({
position: 'relative',
width: '100%',
paddingBottom: '4px',
/**
* Disable horizontal scrolling to prevent crashes on iOS Safari
* See https://github.com/toeverything/AFFiNE/pull/12203
* and https://github.com/toeverything/blocksuite/pull/8784
*/
overflowX: 'hidden',
overflowY: 'hidden',
});
@@ -88,9 +88,6 @@ export class FilterBar extends SignalWatcher(ShadowlessElement) {
};
private readonly addFilter = (e: MouseEvent) => {
if (this.dataViewLogic.root.config.dataSource.readonly$.peek()) {
return;
}
const element = popupTargetFromElement(e.target as HTMLElement);
popCreateFilter(element, {
vars: this.vars,
+1 -1
View File
@@ -16,7 +16,7 @@
"@blocksuite/affine-shared": "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",
@@ -15,7 +15,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:*",
"@floating-ui/dom": "^1.6.13",
@@ -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",
@@ -19,7 +19,6 @@ const DOC_BLOCK_CHILD_PADDING = 24;
export class DocTitle extends WithDisposable(ShadowlessElement) {
static override styles = css`
.doc-icon-container,
.doc-title-container {
box-sizing: border-box;
font-family: var(--affine-font-family);
@@ -50,7 +49,6 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
/* Extra small devices (phones, 640px and down) */
@container viewport (width <= 640px) {
.doc-icon-container,
.doc-title-container {
padding-left: ${DOC_BLOCK_CHILD_PADDING}px;
padding-right: ${DOC_BLOCK_CHILD_PADDING}px;
@@ -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",
@@ -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",
@@ -68,5 +68,5 @@ export function getHeadingBlocksFromDoc(
ignoreEmpty = false
) {
const notes = getNotesFromStore(store, modes);
return notes.flatMap(note => getHeadingBlocksFromNote(note, ignoreEmpty));
return notes.map(note => getHeadingBlocksFromNote(note, ignoreEmpty)).flat();
}
+1 -1
View File
@@ -18,7 +18,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:*",
"@lit/context": "^1.1.2",
+1 -1
View File
@@ -1,7 +1,7 @@
export * from './adapter';
export * from './brush-tool';
export * from './element-renderer';
export * from './eraser-tool';
export * from './highlighter-tool';
export * from './renderer';
export * from './toolbar/configs';
export * from './toolbar/senior-tool';
@@ -1,69 +0,0 @@
import {
DomElementRendererExtension,
type DomRenderer,
} from '@blocksuite/affine-block-surface';
import type { BrushElementModel } from '@blocksuite/affine-model';
import { DefaultTheme } from '@blocksuite/affine-model';
export const BrushDomRendererExtension = DomElementRendererExtension(
'brush',
(
model: BrushElementModel,
domElement: HTMLElement,
renderer: DomRenderer
) => {
const { zoom } = renderer.viewport;
const [, , w, h] = model.deserializedXYWH;
// Early return if invalid dimensions
if (w <= 0 || h <= 0) {
return;
}
// Early return if no commands
if (!model.commands) {
return;
}
// Clear previous content
domElement.innerHTML = '';
// Get color value
const color = renderer.getColorValue(model.color, DefaultTheme.black, true);
// Create SVG element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute';
svg.style.left = '0';
svg.style.top = '0';
svg.style.width = `${w * zoom}px`;
svg.style.height = `${h * zoom}px`;
svg.style.overflow = 'visible';
svg.style.pointerEvents = 'none';
svg.setAttribute('viewBox', `0 0 ${w} ${h}`);
// Apply rotation transform
if (model.rotate !== 0) {
svg.style.transform = `rotate(${model.rotate}deg)`;
svg.style.transformOrigin = 'center';
}
// Create path element for the brush stroke
const pathElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
pathElement.setAttribute('d', model.commands);
pathElement.setAttribute('fill', color);
pathElement.setAttribute('stroke', 'none');
svg.append(pathElement);
domElement.replaceChildren(svg);
// Set element size and position
domElement.style.width = `${w * zoom}px`;
domElement.style.height = `${h * zoom}px`;
domElement.style.overflow = 'visible';
domElement.style.pointerEvents = 'none';
}
);
@@ -1,73 +0,0 @@
import {
DomElementRendererExtension,
type DomRenderer,
} from '@blocksuite/affine-block-surface';
import type { HighlighterElementModel } from '@blocksuite/affine-model';
import { DefaultTheme } from '@blocksuite/affine-model';
export const HighlighterDomRendererExtension = DomElementRendererExtension(
'highlighter',
(
model: HighlighterElementModel,
domElement: HTMLElement,
renderer: DomRenderer
) => {
const { zoom } = renderer.viewport;
const [, , w, h] = model.deserializedXYWH;
// Early return if invalid dimensions
if (w <= 0 || h <= 0) {
return;
}
// Early return if no commands
if (!model.commands) {
return;
}
// Clear previous content
domElement.innerHTML = '';
// Get color value
const color = renderer.getColorValue(
model.color,
DefaultTheme.hightlighterColor,
true
);
// Create SVG element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute';
svg.style.left = '0';
svg.style.top = '0';
svg.style.width = `${w * zoom}px`;
svg.style.height = `${h * zoom}px`;
svg.style.overflow = 'visible';
svg.style.pointerEvents = 'none';
svg.setAttribute('viewBox', `0 0 ${w} ${h}`);
// Apply rotation transform
if (model.rotate !== 0) {
svg.style.transform = `rotate(${model.rotate}deg)`;
svg.style.transformOrigin = 'center';
}
// Create path element for the highlighter stroke
const pathElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
pathElement.setAttribute('d', model.commands);
pathElement.setAttribute('fill', color);
pathElement.setAttribute('stroke', 'none');
svg.append(pathElement);
domElement.replaceChildren(svg);
// Set element size and position
domElement.style.width = `${w * zoom}px`;
domElement.style.height = `${h * zoom}px`;
domElement.style.overflow = 'visible';
domElement.style.pointerEvents = 'none';
}
);
@@ -1,2 +0,0 @@
export { BrushDomRendererExtension } from './brush';
export { HighlighterDomRendererExtension } from './highlighter';
@@ -1,2 +0,0 @@
export { BrushElementRendererExtension } from './brush';
export { HighlighterElementRendererExtension } from './highlighter';
@@ -1,2 +0,0 @@
export * from './dom';
export * from './element';
+1 -9
View File
@@ -5,14 +5,9 @@ import {
import { BrushTool } from './brush-tool';
import { effects } from './effects';
import { BrushElementRendererExtension } from './element-renderer';
import { EraserTool } from './eraser-tool';
import { HighlighterTool } from './highlighter-tool';
import {
BrushDomRendererExtension,
BrushElementRendererExtension,
HighlighterDomRendererExtension,
HighlighterElementRendererExtension,
} from './renderer';
import {
brushToolbarExtension,
highlighterToolbarExtension,
@@ -35,9 +30,6 @@ export class BrushViewExtension extends ViewExtensionProvider {
context.register(HighlighterTool);
context.register(BrushElementRendererExtension);
context.register(BrushDomRendererExtension);
context.register(HighlighterElementRendererExtension);
context.register(HighlighterDomRendererExtension);
context.register(brushToolbarExtension);
context.register(highlighterToolbarExtension);
+1 -1
View File
@@ -19,7 +19,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:*",
"@lit/context": "^1.1.2",
@@ -0,0 +1,11 @@
import { DomElementRendererExtension } from '@blocksuite/affine-block-surface';
import { connectorDomRenderer } from './connector-dom/index.js';
/**
* Extension to register the DOM-based renderer for 'connector' elements.
*/
export const ConnectorDomRendererExtension = DomElementRendererExtension(
'connector',
connectorDomRenderer
);
@@ -1,18 +1,14 @@
import {
DomElementRendererExtension,
type DomRenderer,
} from '@blocksuite/affine-block-surface';
import type { DomRenderer } from '@blocksuite/affine-block-surface';
import {
type ConnectorElementModel,
ConnectorMode,
DefaultTheme,
type LocalConnectorElementModel,
type PointStyle,
} from '@blocksuite/affine-model';
import { PointLocation, SVGPathBuilder } from '@blocksuite/global/gfx';
import { isConnectorWithLabel } from '../connector-manager';
import { DEFAULT_ARROW_SIZE } from './utils';
import { isConnectorWithLabel } from '../../connector-manager.js';
import { DEFAULT_ARROW_SIZE } from '../utils.js';
interface PathBounds {
minX: number;
@@ -225,8 +221,8 @@ function renderConnectorLabel(
* @param element - The HTMLElement to apply the connector's styles to.
* @param renderer - The main DOMRenderer instance, providing access to viewport and color utilities.
*/
export const connectorBaseDomRenderer = (
model: ConnectorElementModel | LocalConnectorElementModel,
export const connectorDomRenderer = (
model: ConnectorElementModel,
element: HTMLElement,
renderer: DomRenderer
): void => {
@@ -362,21 +358,10 @@ export const connectorBaseDomRenderer = (
element.style.height = `${model.h * zoom}px`;
element.style.overflow = 'visible';
element.style.pointerEvents = 'none';
};
export const connectorDomRenderer = (
model: ConnectorElementModel,
element: HTMLElement,
renderer: DomRenderer
): void => {
connectorBaseDomRenderer(model, element, renderer);
renderConnectorLabel(model, element, renderer, renderer.viewport.zoom);
};
// Set z-index for layering
element.style.zIndex = renderer.layerManager.getZIndex(model).toString();
/**
* Extension to register the DOM-based renderer for 'connector' elements.
*/
export const ConnectorDomRendererExtension = DomElementRendererExtension(
'connector',
connectorDomRenderer
);
// Render label if present
renderConnectorLabel(model, element, renderer, zoom);
};
@@ -25,7 +25,7 @@ import {
} from '@blocksuite/global/gfx';
import { deltaInsertsToChunks } from '@blocksuite/std/inline';
import { isConnectorWithLabel } from '../connector-manager';
import { isConnectorWithLabel } from '../connector-manager.js';
import {
DEFAULT_ARROW_SIZE,
getArrowOptions,
@@ -33,7 +33,7 @@ import {
renderCircle,
renderDiamond,
renderTriangle,
} from './utils';
} from './utils.js';
export const connector: ElementRenderer<
ConnectorElementModel | LocalConnectorElementModel
+2 -1
View File
@@ -1,8 +1,9 @@
export * from './adapter';
export * from './connector-manager';
export * from './connector-tool';
export * from './element-renderer';
export { ConnectorDomRendererExtension } from './element-renderer/connector-dom';
export * from './element-transform';
export * from './renderer';
export * from './text';
export * from './toolbar/config';
export * from './toolbar/quick-tool';
@@ -1,2 +0,0 @@
export * from './dom-renderer';
export * from './element-renderer';
+2 -4
View File
@@ -6,11 +6,9 @@ import {
import { ConnectionOverlay } from './connector-manager';
import { ConnectorTool } from './connector-tool';
import { effects } from './effects';
import { ConnectorElementRendererExtension } from './element-renderer';
import { ConnectorDomRendererExtension } from './element-renderer/connector-dom';
import { ConnectorFilter } from './element-transform';
import {
ConnectorDomRendererExtension,
ConnectorElementRendererExtension,
} from './renderer';
import { connectorToolbarExtension } from './toolbar/config';
import { connectorQuickTool } from './toolbar/quick-tool';
import { ConnectorElementView, ConnectorInteraction } from './view/view';
+1 -1
View File
@@ -19,7 +19,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:*",
"@lit/context": "^1.1.2",
@@ -6,7 +6,7 @@ import {
import type { GroupElementModel } from '@blocksuite/affine-model';
import { Bound } from '@blocksuite/global/gfx';
import { titleRenderParams } from './utils';
import { titleRenderParams } from './utils.js';
export const group: ElementRenderer<GroupElementModel> = (
model,
@@ -13,7 +13,7 @@ import {
GROUP_TITLE_FONT_SIZE,
GROUP_TITLE_OFFSET,
GROUP_TITLE_PADDING,
} from './consts';
} from './consts.js';
export function titleRenderParams(group: GroupElementModel, zoom: number) {
let text = group.title.toString().trim();
+1 -1
View File
@@ -1,6 +1,6 @@
export * from './adapter';
export * from './command';
export * from './element-renderer';
export * from './element-view';
export * from './renderer';
export * from './text/text';
export * from './toolbar/config';
@@ -1,62 +0,0 @@
import { DomElementRendererExtension } from '@blocksuite/affine-block-surface';
import { FontWeight, type GroupElementModel } from '@blocksuite/affine-model';
import {
GROUP_TITLE_FONT,
GROUP_TITLE_FONT_SIZE,
GROUP_TITLE_PADDING,
} from './consts';
import { titleRenderParams } from './utils';
export const GroupDomRendererExtension = DomElementRendererExtension(
'group',
(model: GroupElementModel, domElement, renderer) => {
const { zoom } = renderer.viewport;
const [, , w, h] = model.deserializedXYWH;
const renderParams = titleRenderParams(model, zoom);
model.externalXYWH = renderParams.titleBound.serialize();
domElement.innerHTML = '';
domElement.style.outlineColor = '';
domElement.style.outlineWidth = '';
domElement.style.outlineStyle = '';
const elements = renderer.provider.selectedElements?.() || [];
const renderTitle = () => {
const { text } = renderParams;
const titleElement = document.createElement('div');
titleElement.style.transform = `translate(0, -100%)`;
titleElement.style.fontFamily = GROUP_TITLE_FONT;
titleElement.style.fontWeight = `${FontWeight.Regular}`;
titleElement.style.fontStyle = 'normal';
titleElement.style.fontSize = `${GROUP_TITLE_FONT_SIZE}px`;
titleElement.style.color = renderer.getPropertyValue('--affine-blue');
titleElement.style.textAlign = 'left';
titleElement.style.padding = `${GROUP_TITLE_PADDING[0]}px ${GROUP_TITLE_PADDING[1]}px`;
titleElement.textContent = text;
domElement.replaceChildren(titleElement);
};
if (elements.includes(model.id)) {
if (model.showTitle) {
renderTitle();
} else {
domElement.style.outlineColor =
renderer.getPropertyValue('--affine-blue');
domElement.style.outlineWidth = '2px';
domElement.style.outlineStyle = 'solid';
}
} else if (model.childElements.some(child => elements.includes(child.id))) {
domElement.style.outlineColor = '#8FD1FF';
domElement.style.outlineWidth = '2px';
domElement.style.outlineStyle = 'solid';
}
domElement.style.width = `${w * zoom}px`;
domElement.style.height = `${h * zoom}px`;
domElement.style.overflow = 'visible';
domElement.style.pointerEvents = 'none';
}
);
@@ -1,2 +0,0 @@
export * from './dom-renderer';
export * from './element-renderer';
@@ -21,7 +21,7 @@ import {
GROUP_TITLE_FONT_SIZE,
GROUP_TITLE_OFFSET,
GROUP_TITLE_PADDING,
} from '../renderer/consts';
} from '../element-renderer/consts';
export function mountGroupTitleEditor(
group: GroupElementModel,
+1 -5
View File
@@ -4,12 +4,9 @@ import {
} from '@blocksuite/affine-ext-loader';
import { effects } from './effects';
import { GroupElementRendererExtension } from './element-renderer';
import { GroupElementView, GroupInteraction } from './element-view';
import { GroupInteractionExtension } from './interaction-ext';
import {
GroupDomRendererExtension,
GroupElementRendererExtension,
} from './renderer';
import { groupToolbarExtension } from './toolbar/config';
export class GroupViewExtension extends ViewExtensionProvider {
@@ -23,7 +20,6 @@ export class GroupViewExtension extends ViewExtensionProvider {
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register(GroupElementRendererExtension);
context.register(GroupDomRendererExtension);
context.register(GroupElementView);
if (this.isEdgeless(context.scope)) {
context.register(groupToolbarExtension);
+1 -1
View File
@@ -21,7 +21,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:*",
"@lit/context": "^1.1.2",
+1 -1
View File
@@ -25,7 +25,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:*",
"@lit/context": "^1.1.2",

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