From cac05e720aae491681f69883c34691da24675073 Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Mon, 10 Mar 2025 10:25:21 +0000 Subject: [PATCH] refactor(editor): gfx text package (#10738) --- blocksuite/affine/all/package.json | 2 + blocksuite/affine/all/src/gfx/text.ts | 1 + .../affine/blocks/block-root/package.json | 1 + .../auto-complete/auto-complete-panel.ts | 6 +- .../toolbar/mindmap/basket-elements.ts | 2 +- .../src/edgeless/edgeless-builtin-spec.ts | 2 +- .../src/edgeless/gfx-tool/default-tool.ts | 3 +- .../block-root/src/edgeless/gfx-tool/index.ts | 1 - .../block-root/src/edgeless/utils/text.ts | 82 +------------------ .../affine/blocks/block-root/src/effects.ts | 10 ++- .../affine/blocks/block-root/tsconfig.json | 1 + blocksuite/affine/gfx/text/package.json | 48 +++++++++++ .../text/src}/edgeless-text-editor.ts | 45 +++++----- blocksuite/affine/gfx/text/src/effects.ts | 11 +++ blocksuite/affine/gfx/text/src/index.ts | 3 + .../affine/gfx/text/src/mount-text-editor.ts | 77 +++++++++++++++++ .../text-tool.ts => gfx/text/src/tool.ts} | 15 ++-- blocksuite/affine/gfx/text/tsconfig.json | 21 +++++ tools/utils/src/workspace.gen.ts | 18 ++++ tsconfig.json | 1 + yarn.lock | 29 +++++++ 21 files changed, 260 insertions(+), 119 deletions(-) create mode 100644 blocksuite/affine/all/src/gfx/text.ts create mode 100644 blocksuite/affine/gfx/text/package.json rename blocksuite/affine/{blocks/block-root/src/edgeless/components/text => gfx/text/src}/edgeless-text-editor.ts (92%) create mode 100644 blocksuite/affine/gfx/text/src/effects.ts create mode 100644 blocksuite/affine/gfx/text/src/index.ts create mode 100644 blocksuite/affine/gfx/text/src/mount-text-editor.ts rename blocksuite/affine/{blocks/block-root/src/edgeless/gfx-tool/text-tool.ts => gfx/text/src/tool.ts} (84%) create mode 100644 blocksuite/affine/gfx/text/tsconfig.json diff --git a/blocksuite/affine/all/package.json b/blocksuite/affine/all/package.json index a641644233..491a5507b6 100644 --- a/blocksuite/affine/all/package.json +++ b/blocksuite/affine/all/package.json @@ -36,6 +36,7 @@ "@blocksuite/affine-fragment-doc-title": "workspace:*", "@blocksuite/affine-fragment-frame-panel": "workspace:*", "@blocksuite/affine-fragment-outline": "workspace:*", + "@blocksuite/affine-gfx-text": "workspace:*", "@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-rich-text": "workspace:*", "@blocksuite/affine-shared": "workspace:*", @@ -104,6 +105,7 @@ "./fragments/doc-title": "./src/fragments/doc-title.ts", "./fragments/frame-panel": "./src/fragments/frame-panel.ts", "./fragments/outline": "./src/fragments/outline.ts", + "./gfx/text": "./src/gfx/text.ts", "./components/block-selection": "./src/components/block-selection.ts", "./components/block-zero-width": "./src/components/block-zero-width.ts", "./components/caption": "./src/components/caption.ts", diff --git a/blocksuite/affine/all/src/gfx/text.ts b/blocksuite/affine/all/src/gfx/text.ts new file mode 100644 index 0000000000..0505430ce5 --- /dev/null +++ b/blocksuite/affine/all/src/gfx/text.ts @@ -0,0 +1 @@ +export * from '@blocksuite/affine-gfx-text'; diff --git a/blocksuite/affine/blocks/block-root/package.json b/blocksuite/affine/blocks/block-root/package.json index 342fb94591..f85ed27c93 100644 --- a/blocksuite/affine/blocks/block-root/package.json +++ b/blocksuite/affine/blocks/block-root/package.json @@ -30,6 +30,7 @@ "@blocksuite/affine-block-table": "workspace:*", "@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-fragment-doc-title": "workspace:*", + "@blocksuite/affine-gfx-text": "workspace:*", "@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-rich-text": "workspace:*", "@blocksuite/affine-shared": "workspace:*", diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/auto-complete-panel.ts b/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/auto-complete-panel.ts index d8b32af816..7adcbc306b 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/auto-complete-panel.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/components/auto-complete/auto-complete-panel.ts @@ -4,6 +4,7 @@ import { EdgelessCRUDIdentifier, } from '@blocksuite/affine-block-surface'; import { FontFamilyIcon } from '@blocksuite/affine-components/icons'; +import { mountTextElementEditor } from '@blocksuite/affine-gfx-text'; import type { Connection, ConnectorElementModel, @@ -56,10 +57,7 @@ import { SHAPE_OVERLAY_HEIGHT, SHAPE_OVERLAY_WIDTH, } from '../../utils/consts.js'; -import { - mountShapeTextEditor, - mountTextElementEditor, -} from '../../utils/text.js'; +import { mountShapeTextEditor } from '../../utils/text.js'; import { ShapeComponentConfig } from '../toolbar/shape/shape-menu-config.js'; import { type AUTO_COMPLETE_TARGET_TYPE, diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/components/toolbar/mindmap/basket-elements.ts b/blocksuite/affine/blocks/block-root/src/edgeless/components/toolbar/mindmap/basket-elements.ts index 3b39549578..a0a647e02d 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/components/toolbar/mindmap/basket-elements.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/components/toolbar/mindmap/basket-elements.ts @@ -2,6 +2,7 @@ import { addAttachments } from '@blocksuite/affine-block-attachment'; import { insertEdgelessTextCommand } from '@blocksuite/affine-block-edgeless-text'; import { addImages } from '@blocksuite/affine-block-image'; import { CanvasElementType } from '@blocksuite/affine-block-surface'; +import { mountTextElementEditor } from '@blocksuite/affine-gfx-text'; import { MAX_IMAGE_WIDTH, type MindmapStyle, @@ -18,7 +19,6 @@ import * as Y from 'yjs'; import type { EdgelessRootBlockComponent } from '../../../edgeless-root-block.js'; import type { EdgelessRootService } from '../../../edgeless-root-service.js'; -import { mountTextElementEditor } from '../../../utils/text.js'; export type ConfigProperty = 'x' | 'y' | 'r' | 's' | 'z' | 'o'; export type ConfigState = 'default' | 'active' | 'hover' | 'next'; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-builtin-spec.ts b/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-builtin-spec.ts index 89a0a7eec5..58cc0b62d3 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-builtin-spec.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-builtin-spec.ts @@ -4,6 +4,7 @@ import { PresentTool, } from '@blocksuite/affine-block-frame'; import { ConnectionOverlay } from '@blocksuite/affine-block-surface'; +import { TextTool } from '@blocksuite/affine-gfx-text'; import type { ExtensionType } from '@blocksuite/store'; import { EdgelessRootBlockSpec } from './edgeless-root-spec.js'; @@ -19,7 +20,6 @@ import { NoteTool } from './gfx-tool/note-tool.js'; import { PanTool } from './gfx-tool/pan-tool.js'; import { ShapeTool } from './gfx-tool/shape-tool.js'; import { TemplateTool } from './gfx-tool/template-tool.js'; -import { TextTool } from './gfx-tool/text-tool.js'; import { EditPropsMiddlewareBuilder } from './middlewares/base.js'; import { SnapManager } from './utils/snap-manager.js'; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts index 6fc7e0cf3a..3b3fca89e5 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts @@ -9,6 +9,7 @@ import { isNoteBlock, OverlayIdentifier, } from '@blocksuite/affine-block-surface'; +import { addText, mountTextElementEditor } from '@blocksuite/affine-gfx-text'; import type { EdgelessTextBlockModel, FrameBlockModel, @@ -55,12 +56,10 @@ import { calPanDelta } from '../utils/panning-utils.js'; import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js'; import type { SnapManager } from '../utils/snap-manager.js'; import { - addText, mountConnectorLabelEditor, mountFrameTitleEditor, mountGroupTitleEditor, mountShapeTextEditor, - mountTextElementEditor, } from '../utils/text.js'; import { CanvasElementEventExt } from './default-tool-ext/event-ext.js'; import type { DefaultToolExt } from './default-tool-ext/ext.js'; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/index.ts b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/index.ts index 306bc7511c..c789ab4f14 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/index.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/index.ts @@ -9,4 +9,3 @@ export { NoteTool, type NoteToolOption } from './note-tool.js'; export { PanTool, type PanToolOption } from './pan-tool.js'; export { ShapeTool, type ShapeToolOption } from './shape-tool.js'; export { TemplateTool } from './template-tool.js'; -export { TextTool } from './text-tool.js'; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/utils/text.ts b/blocksuite/affine/blocks/block-root/src/edgeless/utils/text.ts index a4b18a9509..7af647ce5a 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/utils/text.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/utils/text.ts @@ -1,16 +1,10 @@ -import { - CanvasElementType, - EdgelessCRUDIdentifier, - type IModelCoord, - TextUtils, -} from '@blocksuite/affine-block-surface'; +import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface'; import type { ConnectorElementModel, FrameBlockModel, GroupElementModel, } from '@blocksuite/affine-model'; -import { ShapeElementModel, TextElementModel } from '@blocksuite/affine-model'; -import type { PointerEventState } from '@blocksuite/block-std'; +import { ShapeElementModel } from '@blocksuite/affine-model'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import type { IVec } from '@blocksuite/global/gfx'; import { Bound } from '@blocksuite/global/gfx'; @@ -20,47 +14,8 @@ import { EdgelessConnectorLabelEditor } from '../components/text/edgeless-connec import { EdgelessFrameTitleEditor } from '../components/text/edgeless-frame-title-editor.js'; import { EdgelessGroupTitleEditor } from '../components/text/edgeless-group-title-editor.js'; import { EdgelessShapeTextEditor } from '../components/text/edgeless-shape-text-editor.js'; -import { EdgelessTextEditor } from '../components/text/edgeless-text-editor.js'; import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js'; -export function mountTextElementEditor( - textElement: TextElementModel, - edgeless: EdgelessRootBlockComponent, - focusCoord?: IModelCoord -) { - if (!edgeless.mountElm) { - throw new BlockSuiteError( - ErrorCode.ValueNotExists, - "edgeless block's mount point does not exist" - ); - } - - let cursorIndex = textElement.text.length; - if (focusCoord) { - cursorIndex = Math.min( - TextUtils.getCursorByCoord(textElement, focusCoord), - cursorIndex - ); - } - - const textEditor = new EdgelessTextEditor(); - textEditor.edgeless = edgeless; - textEditor.element = textElement; - - edgeless.append(textEditor); - textEditor.updateComplete - .then(() => { - textEditor.inlineEditor?.focusIndex(cursorIndex); - }) - .catch(console.error); - - edgeless.gfx.tool.setTool('default'); - edgeless.gfx.selection.set({ - elements: [textElement.id], - editing: true, - }); -} - export function mountShapeTextEditor( shapeElement: ShapeElementModel, edgeless: EdgelessRootBlockComponent @@ -145,39 +100,6 @@ export function mountGroupTitleEditor( }); } -/** - * @deprecated - * - * Canvas Text has been deprecated - */ -export function addText( - edgeless: EdgelessRootBlockComponent, - event: PointerEventState -) { - const [x, y] = edgeless.service.viewport.toModelCoord(event.x, event.y); - const selected = edgeless.service.gfx.getElementByPoint(x, y); - - if (!selected) { - const [modelX, modelY] = edgeless.service.viewport.toModelCoord( - event.x, - event.y - ); - const id = edgeless.std - .get(EdgelessCRUDIdentifier) - .addElement(CanvasElementType.TEXT, { - xywh: new Bound(modelX, modelY, 32, 32).serialize(), - text: new Y.Text(), - }); - if (!id) return; - edgeless.doc.captureSync(); - const textElement = edgeless.service.crud.getElementById(id); - if (!textElement) return; - if (textElement instanceof TextElementModel) { - mountTextElementEditor(textElement, edgeless); - } - } -} - export function mountConnectorLabelEditor( connector: ConnectorElementModel, edgeless: EdgelessRootBlockComponent, diff --git a/blocksuite/affine/blocks/block-root/src/effects.ts b/blocksuite/affine/blocks/block-root/src/effects.ts index 7011a1d9eb..36d7f36e78 100644 --- a/blocksuite/affine/blocks/block-root/src/effects.ts +++ b/blocksuite/affine/blocks/block-root/src/effects.ts @@ -1,3 +1,5 @@ +import { effects as gfxCanvasTextEffects } from '@blocksuite/affine-gfx-text/effects'; + import { EdgelessAutoCompletePanel } from './edgeless/components/auto-complete/auto-complete-panel.js'; import { EdgelessAutoComplete } from './edgeless/components/auto-complete/edgeless-auto-complete.js'; import { EdgelessToolIconButton } from './edgeless/components/buttons/tool-icon-button.js'; @@ -36,7 +38,6 @@ import { EdgelessConnectorLabelEditor } from './edgeless/components/text/edgeles import { EdgelessFrameTitleEditor } from './edgeless/components/text/edgeless-frame-title-editor.js'; import { EdgelessGroupTitleEditor } from './edgeless/components/text/edgeless-group-title-editor.js'; import { EdgelessShapeTextEditor } from './edgeless/components/text/edgeless-shape-text-editor.js'; -import { EdgelessTextEditor } from './edgeless/components/text/edgeless-text-editor.js'; import { EdgelessBrushMenu } from './edgeless/components/toolbar/brush/brush-menu.js'; import { EdgelessBrushToolButton } from './edgeless/components/toolbar/brush/brush-tool-button.js'; import { EdgelessSlideMenu } from './edgeless/components/toolbar/common/slide-menu.js'; @@ -122,6 +123,7 @@ export function effects() { // Register components by category registerRootComponents(); + registerGfxEffects(); registerWidgets(); registerEdgelessToolbarComponents(); registerEdgelessPanelComponents(); @@ -139,6 +141,10 @@ function registerRootComponents() { ); } +function registerGfxEffects() { + gfxCanvasTextEffects(); +} + function registerWidgets() { customElements.define(AFFINE_INNER_MODAL_WIDGET, AffineInnerModalWidget); customElements.define(AFFINE_MODAL_WIDGET, AffineModalWidget); @@ -258,7 +264,6 @@ function registerEdgelessEditorComponents() { 'edgeless-frame-title-editor', EdgelessFrameTitleEditor ); - customElements.define('edgeless-text-editor', EdgelessTextEditor); } function registerMiscComponents() { @@ -348,7 +353,6 @@ declare global { 'edgeless-frame-title-editor': EdgelessFrameTitleEditor; 'edgeless-group-title-editor': EdgelessGroupTitleEditor; 'edgeless-shape-text-editor': EdgelessShapeTextEditor; - 'edgeless-text-editor': EdgelessTextEditor; 'edgeless-toolbar-widget': EdgelessToolbarWidget; 'presentation-toolbar': PresentationToolbar; 'edgeless-brush-menu': EdgelessBrushMenu; diff --git a/blocksuite/affine/blocks/block-root/tsconfig.json b/blocksuite/affine/blocks/block-root/tsconfig.json index 23a85e97a6..4d9d9001a0 100644 --- a/blocksuite/affine/blocks/block-root/tsconfig.json +++ b/blocksuite/affine/blocks/block-root/tsconfig.json @@ -24,6 +24,7 @@ { "path": "../block-table" }, { "path": "../../components" }, { "path": "../../fragments/fragment-doc-title" }, + { "path": "../../gfx/text" }, { "path": "../../model" }, { "path": "../../rich-text" }, { "path": "../../shared" }, diff --git a/blocksuite/affine/gfx/text/package.json b/blocksuite/affine/gfx/text/package.json new file mode 100644 index 0000000000..8e07a2dc56 --- /dev/null +++ b/blocksuite/affine/gfx/text/package.json @@ -0,0 +1,48 @@ +{ + "name": "@blocksuite/affine-gfx-text", + "description": "Gfx text for BlockSuite.", + "type": "module", + "scripts": { + "build": "tsc", + "test:unit": "nx vite:test --run --passWithNoTests", + "test:unit:coverage": "nx vite:test --run --coverage", + "test:e2e": "playwright test" + }, + "sideEffects": false, + "keywords": [], + "author": "toeverything", + "license": "MIT", + "dependencies": { + "@blocksuite/affine-block-edgeless-text": "workspace:*", + "@blocksuite/affine-block-surface": "workspace:*", + "@blocksuite/affine-components": "workspace:*", + "@blocksuite/affine-model": "workspace:*", + "@blocksuite/affine-rich-text": "workspace:*", + "@blocksuite/affine-shared": "workspace:*", + "@blocksuite/block-std": "workspace:*", + "@blocksuite/global": "workspace:*", + "@blocksuite/icons": "^2.2.1", + "@blocksuite/inline": "workspace:*", + "@blocksuite/store": "workspace:*", + "@lit/context": "^1.1.2", + "@preact/signals-core": "^1.8.0", + "@toeverything/theme": "^1.1.12", + "@types/lodash-es": "^4.17.12", + "lit": "^3.2.0", + "lodash-es": "^4.17.21", + "minimatch": "^10.0.1", + "yjs": "^13.6.21", + "zod": "^3.23.8" + }, + "exports": { + ".": "./src/index.ts", + "./effects": "./src/effects.ts" + }, + "files": [ + "src", + "dist", + "!src/__tests__", + "!dist/__tests__" + ], + "version": "0.20.0" +} diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/components/text/edgeless-text-editor.ts b/blocksuite/affine/gfx/text/src/edgeless-text-editor.ts similarity index 92% rename from blocksuite/affine/blocks/block-root/src/edgeless/components/text/edgeless-text-editor.ts rename to blocksuite/affine/gfx/text/src/edgeless-text-editor.ts index 28936e11b0..aceaa75272 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/components/text/edgeless-text-editor.ts +++ b/blocksuite/affine/gfx/text/src/edgeless-text-editor.ts @@ -1,5 +1,6 @@ import { EdgelessCRUDIdentifier, + getSurfaceBlock, TextUtils, } from '@blocksuite/affine-block-surface'; import type { TextElementModel } from '@blocksuite/affine-model'; @@ -7,21 +8,26 @@ import type { RichText } from '@blocksuite/affine-rich-text'; import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { getSelectedRect } from '@blocksuite/affine-shared/utils'; import { + type BlockStdScope, RANGE_SYNC_EXCLUDE_ATTR, ShadowlessElement, + stdContext, } from '@blocksuite/block-std'; +import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; import { Bound, toRadian, Vec } from '@blocksuite/global/gfx'; import { WithDisposable } from '@blocksuite/global/lit'; +import { consume } from '@lit/context'; import { css, html, nothing } from 'lit'; import { property, query } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; -import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js'; -import { deleteElements } from '../../utils/crud.js'; - export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { get crud() { - return this.edgeless.std.get(EdgelessCRUDIdentifier); + return this.std.get(EdgelessCRUDIdentifier); + } + + get gfx() { + return this.std.get(GfxControllerIdentifier); } static BORDER_WIDTH = 1; @@ -71,10 +77,9 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { private _keeping = false; private readonly _updateRect = () => { - const edgeless = this.edgeless; const element = this.element; - if (!edgeless || !element || !this.inlineEditorContainer) return; + if (!element || !this.inlineEditorContainer) return; const newWidth = this.inlineEditorContainer.scrollWidth; const newHeight = this.inlineEditorContainer.scrollHeight; @@ -153,10 +158,6 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { override connectedCallback(): void { super.connectedCallback(); - if (!this.edgeless) { - console.error('edgeless is not set.'); - return; - } if (!this.element) { console.error('text element is not set.'); return; @@ -166,9 +167,13 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { } override firstUpdated(): void { - const edgeless = this.edgeless; const element = this.element; - const { dispatcher } = this.edgeless; + const dispatcher = this.std.event; + const surface = getSurfaceBlock(this.std.store); + if (!surface) { + console.error('surface block is not found.'); + return; + } this.updateComplete .then(() => { @@ -179,13 +184,13 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { }); this.disposables.add( - edgeless.service.surface.elementUpdated.on(({ id }) => { + surface.elementUpdated.on(({ id }) => { if (id === element.id) this.requestUpdate(); }) ); this.disposables.add( - edgeless.service.viewport.viewportUpdated.on(() => { + this.gfx.viewport.viewportUpdated.on(() => { this.requestUpdate(); }) ); @@ -197,10 +202,10 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { element.display = true; if (element.text.length === 0) { - deleteElements(edgeless, [element]); + this.crud.deleteElements([element]); } - edgeless.service.selection.set({ + this.gfx.selection.set({ elements: [], editing: false, }); @@ -346,7 +351,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { ); const rect = getSelectedRect([this.element]); - const { translateX, translateY, zoom } = this.edgeless.service.viewport; + const { translateX, translateY, zoom } = this.gfx.viewport; const [visualX, visualY] = this.getVisualPosition(this.element); const containerOffset = this.getContainerOffset(); const transformOperation = [ @@ -358,7 +363,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { ]; const isEmpty = !text.length && !this._isComposition; - const color = this.edgeless.std + const color = this.std .get(ThemeProvider) .generateColorProperty(this.element.color, '#000000'); @@ -404,8 +409,8 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { this._keeping = keeping; } - @property({ attribute: false }) - accessor edgeless!: EdgelessRootBlockComponent; + @consume({ context: stdContext }) + accessor std!: BlockStdScope; @property({ attribute: false }) accessor element!: TextElementModel; diff --git a/blocksuite/affine/gfx/text/src/effects.ts b/blocksuite/affine/gfx/text/src/effects.ts new file mode 100644 index 0000000000..c8895bc516 --- /dev/null +++ b/blocksuite/affine/gfx/text/src/effects.ts @@ -0,0 +1,11 @@ +import { EdgelessTextEditor } from './edgeless-text-editor'; + +export function effects() { + customElements.define('edgeless-text-editor', EdgelessTextEditor); +} + +declare global { + interface HTMLElementTagNameMap { + 'edgeless-text-editor': EdgelessTextEditor; + } +} diff --git a/blocksuite/affine/gfx/text/src/index.ts b/blocksuite/affine/gfx/text/src/index.ts new file mode 100644 index 0000000000..238f482701 --- /dev/null +++ b/blocksuite/affine/gfx/text/src/index.ts @@ -0,0 +1,3 @@ +export * from './edgeless-text-editor'; +export * from './mount-text-editor'; +export * from './tool'; diff --git a/blocksuite/affine/gfx/text/src/mount-text-editor.ts b/blocksuite/affine/gfx/text/src/mount-text-editor.ts new file mode 100644 index 0000000000..b7d6ba320a --- /dev/null +++ b/blocksuite/affine/gfx/text/src/mount-text-editor.ts @@ -0,0 +1,77 @@ +import { + CanvasElementType, + EdgelessCRUDIdentifier, + type IModelCoord, + TextUtils, +} from '@blocksuite/affine-block-surface'; +import { TextElementModel } from '@blocksuite/affine-model'; +import type { BlockComponent, PointerEventState } from '@blocksuite/block-std'; +import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; +import { Bound } from '@blocksuite/global/gfx'; +import * as Y from 'yjs'; + +import { EdgelessTextEditor } from './edgeless-text-editor'; + +export function mountTextElementEditor( + textElement: TextElementModel, + edgeless: BlockComponent, + focusCoord?: IModelCoord +) { + let cursorIndex = textElement.text.length; + if (focusCoord) { + cursorIndex = Math.min( + TextUtils.getCursorByCoord(textElement, focusCoord), + cursorIndex + ); + } + + const textEditor = new EdgelessTextEditor(); + textEditor.element = textElement; + + edgeless.append(textEditor); + textEditor.updateComplete + .then(() => { + textEditor.inlineEditor?.focusIndex(cursorIndex); + }) + .catch(console.error); + + const gfx = edgeless.std.get(GfxControllerIdentifier); + + // @ts-expect-error TODO: refactor gfx tool + gfx.tool.setTool('default'); + gfx.selection.set({ + elements: [textElement.id], + editing: true, + }); +} + +/** + * @deprecated + * + * Canvas Text has been deprecated + */ +export function addText(edgeless: BlockComponent, event: PointerEventState) { + const gfx = edgeless.std.get(GfxControllerIdentifier); + const crud = edgeless.std.get(EdgelessCRUDIdentifier); + const [x, y] = gfx.viewport.toModelCoord(event.x, event.y); + const selected = gfx.getElementByPoint(x, y); + + if (!selected) { + const [modelX, modelY] = gfx.viewport.toModelCoord(event.x, event.y); + + const id = edgeless.std + .get(EdgelessCRUDIdentifier) + .addElement(CanvasElementType.TEXT, { + xywh: new Bound(modelX, modelY, 32, 32).serialize(), + text: new Y.Text(), + }); + if (!id) return; + + edgeless.doc.captureSync(); + const textElement = crud.getElementById(id); + if (!textElement) return; + if (textElement instanceof TextElementModel) { + mountTextElementEditor(textElement, edgeless); + } + } +} diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/text-tool.ts b/blocksuite/affine/gfx/text/src/tool.ts similarity index 84% rename from blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/text-tool.ts rename to blocksuite/affine/gfx/text/src/tool.ts index c728f7a29d..d214c51b89 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/text-tool.ts +++ b/blocksuite/affine/gfx/text/src/tool.ts @@ -9,10 +9,9 @@ import { BaseTool, type GfxController } from '@blocksuite/block-std/gfx'; import { Bound } from '@blocksuite/global/gfx'; import * as Y from 'yjs'; -import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js'; -import { mountTextElementEditor } from '../utils/text.js'; +import { mountTextElementEditor } from './mount-text-editor'; -export function addText(gfx: GfxController, event: PointerEventState) { +function addText(gfx: GfxController, event: PointerEventState) { const [x, y] = gfx.viewport.toModelCoord(event.x, event.y); const selected = gfx.getElementByPoint(x, y); @@ -31,10 +30,11 @@ export function addText(gfx: GfxController, event: PointerEventState) { gfx.doc.captureSync(); const textElement = gfx.getElementById(id) as TextElementModel; const edgelessView = gfx.std.view.getBlock(gfx.std.store.root!.id); - mountTextElementEditor( - textElement, - edgelessView as EdgelessRootBlockComponent - ); + if (!edgelessView) { + console.error('edgeless view is not found.'); + return; + } + mountTextElementEditor(textElement, edgelessView); } } @@ -49,6 +49,7 @@ export class TextTool extends BaseTool { if (textFlag) { const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y); this.gfx.std.command.exec(insertEdgelessTextCommand, { x, y }); + // @ts-expect-error TODO: refactor gfx tool this.gfx.tool.setTool('default'); } else { addText(this.gfx, e); diff --git a/blocksuite/affine/gfx/text/tsconfig.json b/blocksuite/affine/gfx/text/tsconfig.json new file mode 100644 index 0000000000..5c9469e4ab --- /dev/null +++ b/blocksuite/affine/gfx/text/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" + }, + "include": ["./src"], + "references": [ + { "path": "../../blocks/block-edgeless-text" }, + { "path": "../../blocks/block-surface" }, + { "path": "../../components" }, + { "path": "../../model" }, + { "path": "../../rich-text" }, + { "path": "../../shared" }, + { "path": "../../../framework/block-std" }, + { "path": "../../../framework/global" }, + { "path": "../../../framework/inline" }, + { "path": "../../../framework/store" } + ] +} diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index 2540e83b37..4bfe568d15 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -297,6 +297,7 @@ export const PackageList = [ 'blocksuite/affine/blocks/block-table', 'blocksuite/affine/components', 'blocksuite/affine/fragments/fragment-doc-title', + 'blocksuite/affine/gfx/text', 'blocksuite/affine/model', 'blocksuite/affine/rich-text', 'blocksuite/affine/shared', @@ -429,6 +430,22 @@ export const PackageList = [ 'blocksuite/framework/store', ], }, + { + location: 'blocksuite/affine/gfx/text', + name: '@blocksuite/affine-gfx-text', + workspaceDependencies: [ + 'blocksuite/affine/blocks/block-edgeless-text', + 'blocksuite/affine/blocks/block-surface', + 'blocksuite/affine/components', + 'blocksuite/affine/model', + 'blocksuite/affine/rich-text', + 'blocksuite/affine/shared', + 'blocksuite/framework/block-std', + 'blocksuite/framework/global', + 'blocksuite/framework/inline', + 'blocksuite/framework/store', + ], + }, { location: 'blocksuite/affine/model', name: '@blocksuite/affine-model', @@ -923,6 +940,7 @@ export type PackageName = | '@blocksuite/affine-fragment-doc-title' | '@blocksuite/affine-fragment-frame-panel' | '@blocksuite/affine-fragment-outline' + | '@blocksuite/affine-gfx-text' | '@blocksuite/affine-model' | '@blocksuite/affine-rich-text' | '@blocksuite/affine-shared' diff --git a/tsconfig.json b/tsconfig.json index f6c0825d75..5a2f293aef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -75,6 +75,7 @@ { "path": "./blocksuite/affine/fragments/fragment-doc-title" }, { "path": "./blocksuite/affine/fragments/fragment-frame-panel" }, { "path": "./blocksuite/affine/fragments/fragment-outline" }, + { "path": "./blocksuite/affine/gfx/text" }, { "path": "./blocksuite/affine/model" }, { "path": "./blocksuite/affine/rich-text" }, { "path": "./blocksuite/affine/shared" }, diff --git a/yarn.lock b/yarn.lock index ba471dd716..2e248af9e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2617,6 +2617,7 @@ __metadata: "@blocksuite/affine-block-table": "workspace:*" "@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-fragment-doc-title": "workspace:*" + "@blocksuite/affine-gfx-text": "workspace:*" "@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-rich-text": "workspace:*" "@blocksuite/affine-shared": "workspace:*" @@ -2841,6 +2842,33 @@ __metadata: languageName: unknown linkType: soft +"@blocksuite/affine-gfx-text@workspace:*, @blocksuite/affine-gfx-text@workspace:blocksuite/affine/gfx/text": + version: 0.0.0-use.local + resolution: "@blocksuite/affine-gfx-text@workspace:blocksuite/affine/gfx/text" + dependencies: + "@blocksuite/affine-block-edgeless-text": "workspace:*" + "@blocksuite/affine-block-surface": "workspace:*" + "@blocksuite/affine-components": "workspace:*" + "@blocksuite/affine-model": "workspace:*" + "@blocksuite/affine-rich-text": "workspace:*" + "@blocksuite/affine-shared": "workspace:*" + "@blocksuite/block-std": "workspace:*" + "@blocksuite/global": "workspace:*" + "@blocksuite/icons": "npm:^2.2.1" + "@blocksuite/inline": "workspace:*" + "@blocksuite/store": "workspace:*" + "@lit/context": "npm:^1.1.2" + "@preact/signals-core": "npm:^1.8.0" + "@toeverything/theme": "npm:^1.1.12" + "@types/lodash-es": "npm:^4.17.12" + lit: "npm:^3.2.0" + lodash-es: "npm:^4.17.21" + minimatch: "npm:^10.0.1" + yjs: "npm:^13.6.21" + zod: "npm:^3.23.8" + languageName: unknown + linkType: soft + "@blocksuite/affine-model@workspace:*, @blocksuite/affine-model@workspace:blocksuite/affine/model": version: 0.0.0-use.local resolution: "@blocksuite/affine-model@workspace:blocksuite/affine/model" @@ -3108,6 +3136,7 @@ __metadata: "@blocksuite/affine-fragment-doc-title": "workspace:*" "@blocksuite/affine-fragment-frame-panel": "workspace:*" "@blocksuite/affine-fragment-outline": "workspace:*" + "@blocksuite/affine-gfx-text": "workspace:*" "@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-rich-text": "workspace:*" "@blocksuite/affine-shared": "workspace:*"