From f4fc7f99b948c998c6a917bb8e81870643476af2 Mon Sep 17 00:00:00 2001 From: Yifeng Wang Date: Wed, 13 Dec 2023 20:09:31 +0800 Subject: [PATCH] fix(page): init setup for standalone doc editor (#5695) --- .../_common/components/file-drop-manager.ts | 9 +- .../blocks/src/_common/components/loader.ts | 4 +- .../rich-text/inline/nodes/reference-node.ts | 4 +- packages/blocks/src/_common/consts.ts | 4 - .../blocks/src/_common/utils/drag-and-drop.ts | 16 +- packages/blocks/src/_common/utils/query.ts | 141 +++++++----------- .../blocks/src/_common/utils/selection.ts | 11 +- packages/blocks/src/_common/utils/types.ts | 4 +- .../src/_legacy/content-parser/index.ts | 6 +- .../blocks/src/_legacy/service/json2block.ts | 4 +- .../service/legacy-services/code-service.ts | 4 +- .../blocks/src/frame-block/frame-model.ts | 4 +- .../image-block/image/image-resize-manager.ts | 6 +- .../blocks/src/image-block/image/utils.ts | 4 +- .../src/note-block/keymap-controller.ts | 4 +- .../src/page-block/doc/doc-page-block.ts | 3 +- .../block-portal/note/edgeless-note.ts | 7 +- .../edgeless/components/note-slicer/index.ts | 10 +- .../edgeless/components/note-slicer/utils.ts | 7 +- .../text/edgeless-frame-title-editor.ts | 4 +- .../edgeless/controllers/clipboard.ts | 8 +- .../edgeless/controllers/tools/eraser-tool.ts | 6 +- .../edgeless/utils/note-resize-observer.ts | 4 +- .../blocks/src/page-block/utils/callback.ts | 4 +- .../widgets/block-hub/components/block-hub.ts | 4 +- .../widgets/drag-handle/drag-handle.ts | 18 +-- .../page-block/widgets/drag-handle/utils.ts | 11 +- .../page-block/widgets/slash-menu/config.ts | 6 +- packages/inline/src/utils/index.ts | 1 - packages/inline/src/utils/selection.ts | 11 -- .../playground/examples/doc-editor/index.html | 12 ++ .../playground/examples/doc-editor/main.ts | 21 +++ packages/playground/vite.config.ts | 4 + 33 files changed, 174 insertions(+), 192 deletions(-) delete mode 100644 packages/inline/src/utils/selection.ts create mode 100644 packages/playground/examples/doc-editor/index.html create mode 100644 packages/playground/examples/doc-editor/main.ts diff --git a/packages/blocks/src/_common/components/file-drop-manager.ts b/packages/blocks/src/_common/components/file-drop-manager.ts index 574f6b890bdb..bdababf01e86 100644 --- a/packages/blocks/src/_common/components/file-drop-manager.ts +++ b/packages/blocks/src/_common/components/file-drop-manager.ts @@ -5,8 +5,8 @@ import type { BaseBlockModel } from '@blocksuite/store'; import { calcDropTarget, getClosestBlockElementByPoint, - getEditorContainer, - getModelByBlockElement, + getModelByBlockComponent, + isPageMode, matchFlavours, Point, } from '../../_common/utils/index.js'; @@ -54,8 +54,7 @@ export class FileDropManager { } get isPageMode(): boolean { - const editor = getEditorContainer(this._blockService.page); - return editor.mode === 'page'; + return isPageMode(this._blockService.page); } get type(): 'before' | 'after' { @@ -100,7 +99,7 @@ export class FileDropManager { let result = null; let rect = null; if (element) { - const model = getModelByBlockElement(element); + const model = getModelByBlockComponent(element); result = calcDropTarget(point, model, element); if (result) { rect = result.rect; diff --git a/packages/blocks/src/_common/components/loader.ts b/packages/blocks/src/_common/components/loader.ts index a34948be5b97..04d198ea7291 100644 --- a/packages/blocks/src/_common/components/loader.ts +++ b/packages/blocks/src/_common/components/loader.ts @@ -2,7 +2,7 @@ import type { BaseBlockModel } from '@blocksuite/store'; import { css, html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { BLOCK_ID_ATTR, BLOCK_SERVICE_LOADING_ATTR } from '../consts.js'; +import { BLOCK_ID_ATTR } from '../consts.js'; @customElement('loader-element') export class Loader extends LitElement { @@ -77,7 +77,7 @@ export class Loader extends LitElement { super.connectedCallback(); if (this.hostModel) { this.setAttribute(BLOCK_ID_ATTR, this.hostModel.id); - this.setAttribute(BLOCK_SERVICE_LOADING_ATTR, 'true'); + this.setAttribute('data-service-loading', 'true'); } const width = this.width; diff --git a/packages/blocks/src/_common/components/rich-text/inline/nodes/reference-node.ts b/packages/blocks/src/_common/components/rich-text/inline/nodes/reference-node.ts index 10146aaadab3..6243c25a3438 100644 --- a/packages/blocks/src/_common/components/rich-text/inline/nodes/reference-node.ts +++ b/packages/blocks/src/_common/components/rich-text/inline/nodes/reference-node.ts @@ -13,7 +13,7 @@ import { customElement, property, state } from 'lit/decorators.js'; import type { DocPageBlockComponent } from '../../../../../page-block/doc/doc-page-block.js'; import { FontLinkedPageIcon, FontPageIcon } from '../../../../icons/index.js'; import { - getBlockElementByModel, + getBlockComponentByModel, getClosestBlockElementByElement, getModelByElement, } from '../../../../utils/index.js'; @@ -127,7 +127,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) { const targetPageId = refMeta.id; const root = model.page.root; assertExists(root); - const element = getBlockElementByModel(root) as DocPageBlockComponent; + const element = getBlockComponentByModel(root) as DocPageBlockComponent; assertExists(element); element.slots.pageLinkClicked.emit({ pageId: targetPageId }); } diff --git a/packages/blocks/src/_common/consts.ts b/packages/blocks/src/_common/consts.ts index 495629b9cc9a..19cba5f51df4 100644 --- a/packages/blocks/src/_common/consts.ts +++ b/packages/blocks/src/_common/consts.ts @@ -1,13 +1,9 @@ export const BLOCK_ID_ATTR = 'data-block-id'; -export const BLOCK_SERVICE_LOADING_ATTR = 'data-service-loading'; -export const SCROLL_THRESHOLD = 100; export const NOTE_WIDTH = 800; export const BLOCK_CHILDREN_CONTAINER_PADDING_LEFT = 26; -export const PAGE_BLOCK_CHILD_PADDING = 24; export const EDGELESS_BLOCK_CHILD_PADDING = 24; export const EDGELESS_BLOCK_CHILD_BORDER_WIDTH = 2; -export const ACTIVE_NOTE_EXTRA_PADDING = 20; // The height of the header, which is used to calculate the scroll offset // In AFFiNE, to avoid the option element to be covered by the header, we need to reserve the space for the header diff --git a/packages/blocks/src/_common/utils/drag-and-drop.ts b/packages/blocks/src/_common/utils/drag-and-drop.ts index 3742b47e143c..14d1cbfd1309 100644 --- a/packages/blocks/src/_common/utils/drag-and-drop.ts +++ b/packages/blocks/src/_common/utils/drag-and-drop.ts @@ -3,11 +3,11 @@ import type { BaseBlockModel } from '@blocksuite/store'; import { matchFlavours } from './model.js'; import { - type BlockComponentElement, + type BlockComponent, DropFlags, getClosestBlockElementByElement, getDropRectByPoint, - getModelByBlockElement, + getModelByBlockComponent, getRectByBlockElement, } from './query.js'; import { type Point, Rect } from './rect.js'; @@ -31,7 +31,7 @@ export function calcDropTarget( point: Point, model: BaseBlockModel, element: Element, - draggingElements: BlockComponentElement[] = [], + draggingElements: BlockComponent[] = [], scale: number = 1, flavour: string | null = null // for block-hub ): DropResult | null { @@ -44,7 +44,7 @@ export function calcDropTarget( if (children.length) { if (draggingElements.length) { shouldAppendToDatabase = draggingElements - .map(getModelByBlockElement) + .map(getModelByBlockComponent) .every(m => children.includes(m.flavour)); } else if (flavour) { shouldAppendToDatabase = children.includes(flavour); @@ -55,7 +55,7 @@ export function calcDropTarget( const databaseBlockElement = element.closest('affine-database'); if (databaseBlockElement) { element = databaseBlockElement; - model = getModelByBlockElement(element); + model = getModelByBlockComponent(element); } } @@ -76,7 +76,7 @@ export function calcDropTarget( modelState: { model, rect: domRect, - element: element as BlockComponentElement, + element: element as BlockComponent, }, }; } else if (flag === DropFlags.Database) { @@ -97,7 +97,7 @@ export function calcDropTarget( modelState: { model, rect: domRect, - element: element as BlockComponentElement, + element: element as BlockComponent, }, }; } @@ -171,7 +171,7 @@ export function calcDropTarget( modelState: { model, rect: domRect, - element: element as BlockComponentElement, + element: element as BlockComponent, }, }; } diff --git a/packages/blocks/src/_common/utils/query.ts b/packages/blocks/src/_common/utils/query.ts index 698cfffa95ec..c3dc38ea1a04 100644 --- a/packages/blocks/src/_common/utils/query.ts +++ b/packages/blocks/src/_common/utils/query.ts @@ -26,7 +26,7 @@ const STEPS = MAX_SPACE / 2 / 2; // Fix use unknown type // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type BlockComponentElement = BlockElement; +export type BlockComponent = BlockElement; interface ContainerBlock { model?: BaseBlockModel; @@ -148,65 +148,36 @@ export function buildPath(model: BaseBlockModel | null): string[] { return path; } -/** - * If it's not in the page mode, it will return `null` directly. - */ -export function getDocPage(page: Page) { - const editor = getEditorContainer(page); - if (editor.mode !== 'page') return null; - const pageComponent = editor.querySelector('affine-doc-page'); - return pageComponent; +/** If it's not in the page mode, it will return `null` directly */ +export function getDocPage(page: Page): DocPageBlockComponent | null { + const pageComponent = getBlockComponentByModel(page.root); + if (pageComponent?.tagName !== 'AFFINE-DOC-PAGE') return null; + return pageComponent as DocPageBlockComponent; } -/** - * If it's not in the page mode, it will return `null` directly. - */ -export function getDocPageByElement(ele: Element) { - const editor = getClosestEditorContainer(ele); - if (editor.mode !== 'page') return null; - const pageComponent = editor.querySelector('affine-doc-page'); - return pageComponent; +/** If it's not in the page mode, it will return `null` directly */ +export function getDocPageByElement(element: Element) { + return element.closest('affine-doc-page'); } -/** - * If it's not in the edgeless mode, it will return `null` directly. - */ -export function getEdgelessPage(page: Page) { - const editor = getEditorContainer(page); - if (editor.mode !== 'edgeless') return null; - const pageComponent = editor.querySelector('affine-edgeless-page'); - return pageComponent; +/** If it's not in the edgeless mode, it will return `null` directly */ +export function getEdgelessPage(page: Page): EdgelessPageBlockComponent | null { + const pageComponent = getBlockComponentByModel(page.root); + if (pageComponent?.tagName !== 'AFFINE-EDGELESS-PAGE') return null; + return pageComponent as EdgelessPageBlockComponent; } -/** - * This function exposes higher levels of abstraction. - * - * PLEASE USE IT WITH CAUTION! - */ +/** @deprecated */ export function getEditorContainer(page: Page): AbstractEditor { - assertExists( - page.root, - 'Failed to check page mode! Page root is not exists!' - ); - const pageBlock = getBlockElementByModel(page.root); - // EditorContainer - const editorContainer = pageBlock?.closest('affine-editor-container'); - assertExists(editorContainer); - return editorContainer as AbstractEditor; -} - -function getClosestEditorContainer(ele: Element) { - const editorContainer = ele.closest('affine-editor-container'); + const pageComponent = getBlockComponentByModel(page.root); + const editorContainer = pageComponent?.closest('affine-editor-container'); assertExists(editorContainer); return editorContainer as AbstractEditor; } export function isPageMode(page: Page) { - const editor = getEditorContainer(page); - if (!('mode' in editor)) { - throw new Error('Failed to check page mode! Editor mode is not exists!'); - } - return editor.mode === 'page'; + const pageComponent = getBlockComponentByModel(page.root); + return pageComponent?.tagName === 'AFFINE-DOC-PAGE'; } export function getLitRoot() { @@ -217,7 +188,6 @@ export function getLitRoot() { /** * Get editor viewport element. - * * @example * ```ts * const viewportElement = getViewportElement(this.model.page); @@ -231,72 +201,73 @@ export function getViewportElement(page: Page) { const isPage = isPageMode(page); if (!isPage) return null; assertExists(page.root); - const defaultPageBlock = getBlockElementByModel(page.root); + const pageComponent = getBlockComponentByModel(page.root); if ( - !defaultPageBlock || - defaultPageBlock.closest('affine-doc-page') !== defaultPageBlock + !pageComponent || + pageComponent.closest('affine-doc-page') !== pageComponent ) { throw new Error('Failed to get viewport element!'); } - return (defaultPageBlock as DocPageBlockComponent).viewportElement; + return (pageComponent as DocPageBlockComponent).viewportElement; } /** - * Get block element by model. + * Get block component by model. * Note that this function is used for compatibility only, and may be removed in the future. * * Use `root.view.viewFromPath` instead. * @deprecated */ -export function getBlockElementByModel(model: BaseBlockModel) { - return getBlockElementByPath(buildPath(model)); +export function getBlockComponentByModel(model: BaseBlockModel | null) { + if (!model) return null; + return getBlockComponentByPath(buildPath(model)); } -export function getBlockElementByPath(path: string[]) { +export function getBlockComponentByPath(path: string[]) { const root = getLitRoot(); return root.view.viewFromPath('block', path); } /** - * Get block element by its model and wait for the page element to finish updating. + * Get block component by its model and wait for the page element to finish updating. * Note that this function is used for compatibility only, and may be removed in the future. * * Use `root.view.viewFromPath` instead. * @deprecated */ -export async function asyncGetBlockElementByModel( +export async function asyncGetBlockComponentByModel( model: BaseBlockModel -): Promise { +): Promise { assertExists(model.page.root); - const pageElement = getBlockElementByModel(model.page.root); - if (!pageElement) return null; - await pageElement.updateComplete; + const pageComponent = getBlockComponentByModel(model.page.root); + if (!pageComponent) return null; + await pageComponent.updateComplete; if (model.id === model.page.root.id) { - return pageElement; + return pageComponent; } - const blockElement = getBlockElementByModel(model); - return blockElement; + const blockComponent = getBlockComponentByModel(model); + return blockComponent; } /** * @deprecated In most cases, you not need RichText, you can use {@link getInlineEditorByModel} instead. */ export function getRichTextByModel(model: BaseBlockModel) { - const blockElement = getBlockElementByModel(model); - const richText = blockElement?.querySelector('rich-text'); + const blockComponent = getBlockComponentByModel(model); + const richText = blockComponent?.querySelector('rich-text'); if (!richText) return null; return richText; } export async function asyncGetRichTextByModel(model: BaseBlockModel) { - const blockElement = await asyncGetBlockElementByModel(model); - if (!blockElement) return null; - await blockElement.updateComplete; - const richText = blockElement?.querySelector('rich-text'); + const blockComponent = await asyncGetBlockComponentByModel(model); + if (!blockComponent) return null; + await blockComponent.updateComplete; + const richText = blockComponent?.querySelector('rich-text'); if (!richText) return null; return richText; } @@ -325,7 +296,7 @@ export async function asyncGetInlineEditorByModel(model: BaseBlockModel) { export function getModelByElement(element: Element): BaseBlockModel { const closestBlock = element.closest(ATTR_SELECTOR); assertExists(closestBlock, 'Cannot find block element by element'); - return getModelByBlockElement(closestBlock); + return getModelByBlockComponent(closestBlock); } export function isInsidePageTitle(element: unknown): boolean { @@ -367,7 +338,7 @@ function contains(parent: Element, node: Element) { /** * Returns `true` if element has `data-block-id` attribute. */ -function hasBlockId(element: Element): element is BlockComponentElement { +function hasBlockId(element: Element): element is BlockComponent { return element.hasAttribute(ATTR); } @@ -401,7 +372,7 @@ function isPageOrNoteOrSurface(element: Element) { ); } -function isBlock(element: BlockComponentElement) { +function isBlock(element: BlockComponent) { return !isPageOrNoteOrSurface(element); } @@ -573,7 +544,7 @@ export function getClosestBlockElementByPoint( * @param point position */ export function findClosestBlockElement( - container: BlockComponentElement, + container: BlockComponent, point: Point, selector: string ) { @@ -607,14 +578,14 @@ export function findClosestBlockElement( */ export function getClosestBlockElementByElement( element: Element | null -): BlockComponentElement | null { +): BlockComponent | null { if (!element) return null; if (hasBlockId(element) && isBlock(element)) { return element; } - const blockElement = element.closest(ATTR_SELECTOR); - if (blockElement && isBlock(blockElement)) { - return blockElement; + const blockComponent = element.closest(ATTR_SELECTOR); + if (blockComponent && isBlock(blockComponent)) { + return blockComponent; } return null; } @@ -622,8 +593,8 @@ export function getClosestBlockElementByElement( /** * Returns the model of the block element. */ -export function getModelByBlockElement(element: Element) { - const containerBlock = element as ContainerBlock; +export function getModelByBlockComponent(component: Element) { + const containerBlock = component as ContainerBlock; // In extreme cases, the block may be loading, and the model is not yet available. // For example // // `` @@ -643,9 +614,7 @@ export function getModelByBlockElement(element: Element) { * https://github.com/toeverything/blocksuite/issues/902 * https://github.com/toeverything/blocksuite/pull/1121 */ -export function getRectByBlockElement( - element: Element | BlockComponentElement -) { +export function getRectByBlockElement(element: Element | BlockComponent) { if (isDatabase(element)) return element.getBoundingClientRect(); return (element.firstElementChild ?? element).getBoundingClientRect(); } @@ -655,7 +624,7 @@ export function getRectByBlockElement( * Only keep block elements of same level. */ export function getBlockElementsExcludeSubtrees( - elements: Element[] | BlockComponentElement[] + elements: Element[] | BlockComponent[] ) { if (elements.length <= 1) return elements; let parent = elements[0]; diff --git a/packages/blocks/src/_common/utils/selection.ts b/packages/blocks/src/_common/utils/selection.ts index ecaec26c10f6..69c58adf9ce0 100644 --- a/packages/blocks/src/_common/utils/selection.ts +++ b/packages/blocks/src/_common/utils/selection.ts @@ -4,11 +4,10 @@ import { type InlineRange, type VLine } from '@blocksuite/inline'; import type { BaseBlockModel, Page } from '@blocksuite/store'; import type { DocPageBlockComponent } from '../../page-block/doc/doc-page-block.js'; -import { SCROLL_THRESHOLD } from '../consts.js'; import { matchFlavours } from './model.js'; import { asyncGetRichTextByModel, - getBlockElementByModel, + getBlockComponentByModel, getDocPage, getDocPageByElement, } from './query.js'; @@ -114,6 +113,8 @@ function setEndRange(editableContainer: Element) { } async function setNewTop(y: number, editableContainer: Element, zoom = 1) { + const SCROLL_THRESHOLD = 100; + const scrollContainer = editableContainer.closest('.affine-doc-viewport'); const { top, bottom } = Rect.fromDOM(editableContainer); const { clientHeight } = document.documentElement; @@ -169,7 +170,7 @@ export function focusTitle(page: Page, index = Infinity, len = 0) { pageComponent.titleInlineEditor.setInlineRange({ index, length: len }); } -export async function focusRichText( +async function focusRichText( editableContainer: Element, position: SelectionPosition = 'end', zoom = 1 @@ -222,12 +223,12 @@ export function focusBlockByModel( } assertExists(model.page.root); - const pageBlock = getBlockElementByModel( + const pageBlock = getBlockComponentByModel( model.page.root ) as DocPageBlockComponent; assertExists(pageBlock); - const element = getBlockElementByModel(model); + const element = getBlockComponentByModel(model); assertExists(element); const editableContainer = element?.querySelector('[contenteditable]'); if (editableContainer) { diff --git a/packages/blocks/src/_common/utils/types.ts b/packages/blocks/src/_common/utils/types.ts index 7250bbfb7701..af16eeffb49c 100644 --- a/packages/blocks/src/_common/utils/types.ts +++ b/packages/blocks/src/_common/utils/types.ts @@ -22,7 +22,7 @@ import { import type { RefNodeSlots } from '../components/rich-text/inline/nodes/reference-node.js'; import type { AffineTextAttributes } from '../components/rich-text/inline/types.js'; import type { NavigatorMode } from '../edgeless/frame/consts.js'; -import type { BlockComponentElement } from './query.js'; +import type { BlockComponent } from './query.js'; import type { Point } from './rect.js'; export type SelectionPosition = 'start' | 'end' | Point; @@ -39,7 +39,7 @@ export interface BlockTransformContext { } export interface EditingState { - element: BlockComponentElement; + element: BlockComponent; model: BaseBlockModel; rect: DOMRect; } diff --git a/packages/blocks/src/_legacy/content-parser/index.ts b/packages/blocks/src/_legacy/content-parser/index.ts index 984debcae967..a4f6f2a4f5bd 100644 --- a/packages/blocks/src/_legacy/content-parser/index.ts +++ b/packages/blocks/src/_legacy/content-parser/index.ts @@ -5,7 +5,7 @@ import { marked } from 'marked'; import { getTagColor } from '../../_common/components/tags/colors.js'; import { - getBlockElementByModel, + getBlockComponentByModel, getEditorContainer, isPageMode, type SerializedBlock, @@ -113,7 +113,7 @@ export class ContentParser { reject(e); } const root = this._page.root; - const pageBlock = root ? getBlockElementByModel(root) : null; + const pageBlock = root ? getBlockComponentByModel(root) : null; const imageCard = document.querySelector('affine-image-block-card'); const isReady = !imageCard || imageCard.getAttribute('imageState') === '0'; @@ -451,7 +451,7 @@ export class ContentParser { const root = this._page.root; if (!root) return; - const edgeless = getBlockElementByModel( + const edgeless = getBlockComponentByModel( root ) as EdgelessPageBlockComponent; const bound = edgeless.getElementsBound(); diff --git a/packages/blocks/src/_legacy/service/json2block.ts b/packages/blocks/src/_legacy/service/json2block.ts index efda45d6283b..bb0cdc741fdd 100644 --- a/packages/blocks/src/_legacy/service/json2block.ts +++ b/packages/blocks/src/_legacy/service/json2block.ts @@ -5,7 +5,7 @@ import { Text } from '@blocksuite/store'; import { handleBlockSplit } from '../../_common/components/rich-text/rich-text-operations.js'; import { - asyncGetBlockElementByModel, + asyncGetBlockComponentByModel, asyncSetInlineRange, getInlineEditorByModel, type SerializedBlock, @@ -195,7 +195,7 @@ export async function json2block( }); } else { if (lastMergedModel) { - asyncGetBlockElementByModel(lastMergedModel).then(element => { + asyncGetBlockComponentByModel(lastMergedModel).then(element => { if (element) { const selectionManager = element.host.selection; const blockSelection = selectionManager?.getInstance('block', { diff --git a/packages/blocks/src/_legacy/service/legacy-services/code-service.ts b/packages/blocks/src/_legacy/service/legacy-services/code-service.ts index c36bd031d5ab..28178496091a 100644 --- a/packages/blocks/src/_legacy/service/legacy-services/code-service.ts +++ b/packages/blocks/src/_legacy/service/legacy-services/code-service.ts @@ -3,7 +3,7 @@ import { assertExists } from '@blocksuite/global/utils'; import type { BaseBlockModel } from '@blocksuite/store'; import { - getBlockElementByModel, + getBlockComponentByModel, getInlineEditorByModel, getThemeMode, } from '../../../_common/utils/index.js'; @@ -43,7 +43,7 @@ export class CodeBlockService extends BaseService { { childText = '', begin, end }: BlockTransformContext = {} ): Promise { const richTextElement = - getBlockElementByModel(block)?.querySelector('rich-text'); + getBlockComponentByModel(block)?.querySelector('rich-text'); if (!richTextElement) { return await super.block2html(block, { childText, diff --git a/packages/blocks/src/frame-block/frame-model.ts b/packages/blocks/src/frame-block/frame-model.ts index e5468cb954a6..b2f90fb31c08 100644 --- a/packages/blocks/src/frame-block/frame-model.ts +++ b/packages/blocks/src/frame-block/frame-model.ts @@ -3,7 +3,7 @@ import type { Text } from '@blocksuite/store'; import { BaseBlockModel, defineBlockSchema } from '@blocksuite/store'; import { selectable } from '../_common/edgeless/mixin/edgeless-selectable.js'; -import { getBlockElementByPath } from '../_common/utils/index.js'; +import { getBlockComponentByPath } from '../_common/utils/index.js'; import { FRAME_BATCH } from '../surface-block/batch.js'; import type { EdgelessBlockType } from '../surface-block/edgeless-types.js'; import { @@ -49,7 +49,7 @@ export class FrameBlockModel extends selectable( const hit = bound.isPointOnBound([x, y]); if (hit) return true; assertExists(this.page.root); - const block = getBlockElementByPath([ + const block = getBlockComponentByPath([ this.page.root?.id, this.id, ]) as FrameBlockComponent; diff --git a/packages/blocks/src/image-block/image/image-resize-manager.ts b/packages/blocks/src/image-block/image/image-resize-manager.ts index 7a1e465b133f..6ae22fb8ad4d 100644 --- a/packages/blocks/src/image-block/image/image-resize-manager.ts +++ b/packages/blocks/src/image-block/image/image-resize-manager.ts @@ -2,7 +2,7 @@ import type { PointerEventState } from '@blocksuite/block-std'; import { assertExists } from '@blocksuite/global/utils'; import { - type BlockComponentElement, + type BlockComponent, getClosestBlockElementByElement, getModelByElement, isEdgelessPage, @@ -10,7 +10,7 @@ import { import { getClosestPageBlockComponent } from '../../page-block/utils/query.js'; export class ImageResizeManager { - private _activeComponent: BlockComponentElement | null = null; + private _activeComponent: BlockComponent | null = null; private _imageContainer: HTMLElement | null = null; private _imageCenterX = 0; private _dragMoveTarget = 'right'; @@ -20,7 +20,7 @@ export class ImageResizeManager { const eventTarget = e.raw.target as HTMLElement; this._activeComponent = getClosestBlockElementByElement( eventTarget - ) as BlockComponentElement; + ) as BlockComponent; const pageElement = getClosestPageBlockComponent(this._activeComponent); if (pageElement && isEdgelessPage(pageElement)) { diff --git a/packages/blocks/src/image-block/image/utils.ts b/packages/blocks/src/image-block/image/utils.ts index bf08931649cb..f4dc04f38aa4 100644 --- a/packages/blocks/src/image-block/image/utils.ts +++ b/packages/blocks/src/image-block/image/utils.ts @@ -4,7 +4,7 @@ import { Buffer } from 'buffer'; import { toast } from '../../_common/components/toast.js'; import { downloadBlob } from '../../_common/utils/filesys.js'; -import { getBlockElementByModel } from '../../_common/utils/query.js'; +import { getBlockComponentByModel } from '../../_common/utils/query.js'; import { ImageBlockModel, type ImageBlockProps } from '../image-model.js'; async function getImageBlob(model: ImageBlockModel) { @@ -97,7 +97,7 @@ export async function downloadImage(model: ImageBlockModel) { } export function focusCaption(model: BaseBlockModel) { - const blockEle = getBlockElementByModel(model); + const blockEle = getBlockComponentByModel(model); assertExists(blockEle); const dom = blockEle.querySelector( '.affine-embed-wrapper-caption' diff --git a/packages/blocks/src/note-block/keymap-controller.ts b/packages/blocks/src/note-block/keymap-controller.ts index ef7478b15dd2..2f660eb07b53 100644 --- a/packages/blocks/src/note-block/keymap-controller.ts +++ b/packages/blocks/src/note-block/keymap-controller.ts @@ -7,7 +7,7 @@ import type { ReactiveControllerHost } from 'lit'; import { moveBlockConfigs } from '../_common/configs/move-block.js'; import { quickActionConfig } from '../_common/configs/quick-action/config.js'; import { textConversionConfigs } from '../_common/configs/text-conversion.js'; -import { getBlockElementByModel } from '../_common/utils/index.js'; +import { getBlockComponentByModel } from '../_common/utils/index.js'; import { onModelElementUpdated } from '../page-block/utils/callback.js'; import { updateBlockElementType } from '../page-block/utils/operations/element/block-level.js'; import { ensureBlockInContainer } from './utils.js'; @@ -697,7 +697,7 @@ export class KeymapController implements ReactiveController { const [codeModel] = newModels; onModelElementUpdated(codeModel, () => { - const codeElement = getBlockElementByModel(codeModel); + const codeElement = getBlockComponentByModel(codeModel); assertExists(codeElement); this._std.selection.setGroup('note', [ this._std.selection.getInstance('text', { diff --git a/packages/blocks/src/page-block/doc/doc-page-block.ts b/packages/blocks/src/page-block/doc/doc-page-block.ts index caf6aa136d3a..f15f50e84cde 100644 --- a/packages/blocks/src/page-block/doc/doc-page-block.ts +++ b/packages/blocks/src/page-block/doc/doc-page-block.ts @@ -6,7 +6,6 @@ import { css, html } from 'lit'; import { customElement, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; -import { PAGE_BLOCK_CHILD_PADDING } from '../../_common/consts.js'; import type { EditingState } from '../../_common/utils/index.js'; import { asyncFocusRichText, @@ -31,6 +30,8 @@ export interface PageViewport { clientWidth: number; } +const PAGE_BLOCK_CHILD_PADDING = 24; + function testClickOnBlankArea( state: PointerEventState, viewportWidth: number, diff --git a/packages/blocks/src/page-block/edgeless/components/block-portal/note/edgeless-note.ts b/packages/blocks/src/page-block/edgeless/components/block-portal/note/edgeless-note.ts index 67014e02c412..334ad18f8231 100644 --- a/packages/blocks/src/page-block/edgeless/components/block-portal/note/edgeless-note.ts +++ b/packages/blocks/src/page-block/edgeless/components/block-portal/note/edgeless-note.ts @@ -5,16 +5,15 @@ import { html, nothing } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { - ACTIVE_NOTE_EXTRA_PADDING, - EDGELESS_BLOCK_CHILD_PADDING, -} from '../../../../../_common/consts.js'; +import { EDGELESS_BLOCK_CHILD_PADDING } from '../../../../../_common/consts.js'; import { DEFAULT_NOTE_COLOR } from '../../../../../_common/edgeless/note/consts.js'; import { type NoteBlockModel } from '../../../../../note-block/note-model.js'; import { Bound, StrokeStyle } from '../../../../../surface-block/index.js'; import type { SurfaceBlockComponent } from '../../../../../surface-block/surface-block.js'; import { EdgelessPortalBase } from '../edgeless-portal-base.js'; +const ACTIVE_NOTE_EXTRA_PADDING = 20; + @customElement('edgeless-note-mask') export class EdgelessNoteMask extends WithDisposable(ShadowlessElement) { @property({ attribute: false }) diff --git a/packages/blocks/src/page-block/edgeless/components/note-slicer/index.ts b/packages/blocks/src/page-block/edgeless/components/note-slicer/index.ts index 80adb192dd51..701f9cc1ded5 100644 --- a/packages/blocks/src/page-block/edgeless/components/note-slicer/index.ts +++ b/packages/blocks/src/page-block/edgeless/components/note-slicer/index.ts @@ -8,8 +8,8 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { EDGELESS_BLOCK_CHILD_PADDING } from '../../../../_common/consts.js'; import { buildPath, - getBlockElementByPath, - getModelByBlockElement, + getBlockComponentByPath, + getModelByBlockComponent, getRectByBlockElement, Point, } from '../../../../_common/utils/index.js'; @@ -146,7 +146,7 @@ export class NoteSlicer extends WithDisposable(LitElement) { } private _getEditingState(e: PointerEventState, block: NoteBlockModel) { - const noteBlockElement = getBlockElementByPath( + const noteBlockElement = getBlockComponentByPath( buildPath(block) ) as NoteBlockComponent; @@ -172,8 +172,8 @@ export class NoteSlicer extends WithDisposable(LitElement) { return null; } - const currentBlock = getModelByBlockElement(element); - const nearbyBlock = getModelByBlockElement(nearbyBlockElement); + const currentBlock = getModelByBlockComponent(element); + const nearbyBlock = getModelByBlockComponent(nearbyBlockElement); const nearbyBlockRect = nearbyBlockElement.getBoundingClientRect(); const upperBlockRect = onUpperPart ? nearbyBlockRect : elementRect; const lowerBlockRect = onUpperPart ? elementRect : nearbyBlockRect; diff --git a/packages/blocks/src/page-block/edgeless/components/note-slicer/utils.ts b/packages/blocks/src/page-block/edgeless/components/note-slicer/utils.ts index dfcd320a67ab..840dbc4fd4a5 100644 --- a/packages/blocks/src/page-block/edgeless/components/note-slicer/utils.ts +++ b/packages/blocks/src/page-block/edgeless/components/note-slicer/utils.ts @@ -1,5 +1,5 @@ import { - type BlockComponentElement, + type BlockComponent, type Point, } from '../../../../_common/utils/index.js'; @@ -8,10 +8,7 @@ import { * @param container container which the blocks can be found inside * @param point position */ -export function findClosestBlock( - container: BlockComponentElement, - point: Point -) { +export function findClosestBlock(container: BlockComponent, point: Point) { const children = Array.from( container.querySelectorAll( '.affine-note-block-container > .affine-block-children-container > [data-block-id]' diff --git a/packages/blocks/src/page-block/edgeless/components/text/edgeless-frame-title-editor.ts b/packages/blocks/src/page-block/edgeless/components/text/edgeless-frame-title-editor.ts index b1dcef015038..4ba825a2c994 100644 --- a/packages/blocks/src/page-block/edgeless/components/text/edgeless-frame-title-editor.ts +++ b/packages/blocks/src/page-block/edgeless/components/text/edgeless-frame-title-editor.ts @@ -5,7 +5,7 @@ import { customElement, property, query } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import type { RichText } from '../../../../_common/components/rich-text/rich-text.js'; -import { getBlockElementByPath } from '../../../../_common/utils/index.js'; +import { getBlockComponentByPath } from '../../../../_common/utils/index.js'; import type { FrameBlockComponent, FrameBlockModel, @@ -35,7 +35,7 @@ export class EdgelessFrameTitleEditor extends WithDisposable( get frameBlock() { assertExists(this.frameModel.page.root); - const block = getBlockElementByPath([ + const block = getBlockComponentByPath([ this.frameModel.page.root.id, this.frameModel.id, ]) as FrameBlockComponent | null; diff --git a/packages/blocks/src/page-block/edgeless/controllers/clipboard.ts b/packages/blocks/src/page-block/edgeless/controllers/clipboard.ts index 428fb717238a..f68bc8a60dee 100644 --- a/packages/blocks/src/page-block/edgeless/controllers/clipboard.ts +++ b/packages/blocks/src/page-block/edgeless/controllers/clipboard.ts @@ -15,8 +15,8 @@ import { import { matchFlavours } from '../../../_common/utils/index.js'; import { groupBy } from '../../../_common/utils/iterable.js'; import { - getBlockElementByModel, - getBlockElementByPath, + getBlockComponentByModel, + getBlockComponentByPath, getEditorContainer, isPageMode, } from '../../../_common/utils/query.js'; @@ -137,9 +137,9 @@ export class EdgelessClipboardController extends PageClipboard { current = current.page.getParent(current); } - return getBlockElementByPath(path); + return getBlockComponentByPath(path); } else { - return getBlockElementByModel(model); + return getBlockComponentByModel(model); } } diff --git a/packages/blocks/src/page-block/edgeless/controllers/tools/eraser-tool.ts b/packages/blocks/src/page-block/edgeless/controllers/tools/eraser-tool.ts index ddd8040fd194..4b319c6725b8 100644 --- a/packages/blocks/src/page-block/edgeless/controllers/tools/eraser-tool.ts +++ b/packages/blocks/src/page-block/edgeless/controllers/tools/eraser-tool.ts @@ -4,7 +4,7 @@ import { noop } from '@blocksuite/global/utils'; import type { Erasable, IPoint } from '../../../../_common/utils/index.js'; import { type EraserTool, - getBlockElementByModel, + getBlockComponentByModel, } from '../../../../_common/utils/index.js'; import { Bound, @@ -111,7 +111,7 @@ export class EraserToolController extends EdgelessToolController { linePolygonIntersects(this._prevPoint, currentPoint, bound.points) ) { this._eraseTargets.add(erasable); - const ele = getBlockElementByModel(erasable); + const ele = getBlockComponentByModel(erasable); ele && ((ele).style.opacity = '0.3'); } } else { @@ -128,7 +128,7 @@ export class EraserToolController extends EdgelessToolController { override beforeModeSwitch() { this._eraseTargets.forEach(erasable => { if (isTopLevelBlock(erasable)) { - const ele = getBlockElementByModel(erasable); + const ele = getBlockComponentByModel(erasable); ele && ((ele).style.opacity = '1'); } else { this._edgeless.localRecord.update(erasable.id, { opacity: 1 }); diff --git a/packages/blocks/src/page-block/edgeless/utils/note-resize-observer.ts b/packages/blocks/src/page-block/edgeless/utils/note-resize-observer.ts index ca3e6b4f1470..19c30650905f 100644 --- a/packages/blocks/src/page-block/edgeless/utils/note-resize-observer.ts +++ b/packages/blocks/src/page-block/edgeless/utils/note-resize-observer.ts @@ -4,7 +4,7 @@ import type { Page } from '@blocksuite/store'; import { BLOCK_ID_ATTR } from '../../../_common/consts.js'; import { almostEqual } from '../../../_common/utils/math.js'; import { matchFlavours } from '../../../_common/utils/model.js'; -import { getBlockElementByModel } from '../../../_common/utils/query.js'; +import { getBlockComponentByModel } from '../../../_common/utils/query.js'; export class NoteResizeObserver { private _observer: ResizeObserver; @@ -62,7 +62,7 @@ export class NoteResizeObserver { const blockId = model.id; unCachedKeys.delete(blockId); - const blockElement = getBlockElementByModel(model); + const blockElement = getBlockComponentByModel(model); const container = blockElement?.querySelector( '.affine-note-block-container' ); diff --git a/packages/blocks/src/page-block/utils/callback.ts b/packages/blocks/src/page-block/utils/callback.ts index cea2aa6659f3..c7a6fed1e12b 100644 --- a/packages/blocks/src/page-block/utils/callback.ts +++ b/packages/blocks/src/page-block/utils/callback.ts @@ -4,7 +4,7 @@ import { type BaseBlockModel } from '@blocksuite/store'; import type { RichText } from '../../_common/components/rich-text/rich-text.js'; import { - asyncGetBlockElementByModel, + asyncGetBlockComponentByModel, asyncGetRichTextByModel, } from '../../_common/utils/query.js'; @@ -33,7 +33,7 @@ export async function onModelElementUpdated( model: BaseBlockModel, callback: (blockElement: BlockElement) => void ) { - const element = await asyncGetBlockElementByModel(model); + const element = await asyncGetBlockComponentByModel(model); if (element) { callback(element); } diff --git a/packages/blocks/src/page-block/widgets/block-hub/components/block-hub.ts b/packages/blocks/src/page-block/widgets/block-hub/components/block-hub.ts index ca67f609a2ee..815229ad6a07 100644 --- a/packages/blocks/src/page-block/widgets/block-hub/components/block-hub.ts +++ b/packages/blocks/src/page-block/widgets/block-hub/components/block-hub.ts @@ -20,7 +20,7 @@ import { getEdgelessPage, getHoveringNote, getImageFilesFromLocal, - getModelByBlockElement, + getModelByBlockComponent, isPageMode, Point, Rect, @@ -373,7 +373,7 @@ export class BlockHub extends WithDisposable(ShadowlessElement) { let type: DroppingType = 'none'; let rect = null; let lastModelState = null; - const model = getModelByBlockElement(element); + const model = getModelByBlockComponent(element); const result = calcDropTarget( point, model, diff --git a/packages/blocks/src/page-block/widgets/drag-handle/drag-handle.ts b/packages/blocks/src/page-block/widgets/drag-handle/drag-handle.ts index c1920c89978e..616d3d13f155 100644 --- a/packages/blocks/src/page-block/widgets/drag-handle/drag-handle.ts +++ b/packages/blocks/src/page-block/widgets/drag-handle/drag-handle.ts @@ -13,10 +13,10 @@ import { customElement, query, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import { - getBlockElementByModel, + getBlockComponentByModel, getBlockElementsExcludeSubtrees, getCurrentNativeRange, - getModelByBlockElement, + getModelByBlockComponent, isEdgelessPage, isPageMode, matchFlavours, @@ -114,13 +114,9 @@ export class AffineDragHandleWidget extends WidgetElement< } get pageBlockElement() { - const pageElement = this.blockElement; - const pageBlock = isPageMode(this.page) - ? (pageElement as DocPageBlockComponent) - : (pageElement as EdgelessPageBlockComponent); - assertExists(pageBlock); - - return pageBlock; + return this.blockElement as + | DocPageBlockComponent + | EdgelessPageBlockComponent; } get selectedBlocks() { @@ -892,7 +888,7 @@ export class AffineDragHandleWidget extends WidgetElement< } const selectedBlocks = getBlockElementsExcludeSubtrees(draggingElements) - .map(element => getModelByBlockElement(element)) + .map(element => getModelByBlockComponent(element)) .filter((x): x is BaseBlockModel => !!x); const targetBlock = this.page.getBlockById(targetBlockId); @@ -922,7 +918,7 @@ export class AffineDragHandleWidget extends WidgetElement< assertExists(parent); // Need to update selection when moving blocks successfully // Because the block path may be changed after moving - const parentElement = getBlockElementByModel(parent); + const parentElement = getBlockComponentByModel(parent); if (parentElement) { const newSelectedBlocks = selectedBlocks .map(block => parentElement.path.concat(block.id)) diff --git a/packages/blocks/src/page-block/widgets/drag-handle/utils.ts b/packages/blocks/src/page-block/widgets/drag-handle/utils.ts index f2f212f48691..c30482bc5282 100644 --- a/packages/blocks/src/page-block/widgets/drag-handle/utils.ts +++ b/packages/blocks/src/page-block/widgets/drag-handle/utils.ts @@ -18,7 +18,7 @@ import { Point, Rect, } from '../../../_common/utils/index.js'; -import type { BlockComponentElement } from '../../../index.js'; +import type { BlockComponent } from '../../../index.js'; import type { ParagraphBlockModel } from '../../../paragraph-block/index.js'; import { BLOCK_CHILDREN_CONTAINER_PADDING_LEFT, @@ -120,7 +120,7 @@ export const getContainerOffsetPoint = (state: PointerEventState) => { export const getClosestNoteBlock = ( page: Page, - pageBlock: BlockComponentElement, + pageBlock: BlockComponent, point: Point ) => { return isPageMode(page) @@ -130,7 +130,7 @@ export const getClosestNoteBlock = ( export const getClosestBlockByPoint = ( page: Page, - pageBlock: BlockComponentElement, + pageBlock: BlockComponent, point: Point ) => { const closestNoteBlock = getClosestNoteBlock(page, pageBlock, point); @@ -164,7 +164,7 @@ export function calcDropTarget( point: Point, model: BaseBlockModel, element: Element, - draggingElements: BlockComponentElement[], + draggingElements: BlockComponent[], scale: number ): DropResult | null { let type: DropType | 'none' = 'none'; @@ -208,8 +208,7 @@ export function calcDropTarget( // To drop in, the position must after the target first // If drop in target has children, we can use insert before or after of that children // to achieve the same effect. - const hasChild = (element as BlockComponentElement).childBlockElements - .length; + const hasChild = (element as BlockComponent).childBlockElements.length; if ( matchFlavours(model, ['affine:list']) && !hasChild && diff --git a/packages/blocks/src/page-block/widgets/slash-menu/config.ts b/packages/blocks/src/page-block/widgets/slash-menu/config.ts index f0f59e0ea6da..2362319df3e9 100644 --- a/packages/blocks/src/page-block/widgets/slash-menu/config.ts +++ b/packages/blocks/src/page-block/widgets/slash-menu/config.ts @@ -27,7 +27,7 @@ import { } from '../../../_common/icons/index.js'; import { createPage, - getBlockElementByModel, + getBlockComponentByModel, getCurrentNativeRange, getImageFilesFromLocal, getInlineEditorByModel, @@ -208,7 +208,7 @@ export const menuGroups: SlashMenuOptions['menus'] = [ icon: DualLinkIcon, showWhen: model => { assertExists(model.page.root); - const pageBlock = getBlockElementByModel(model.page.root); + const pageBlock = getBlockComponentByModel(model.page.root); assertExists(pageBlock); const linkedPageWidgetEle = pageBlock.widgetElements['affine-linked-page-widget']; @@ -225,7 +225,7 @@ export const menuGroups: SlashMenuOptions['menus'] = [ const triggerKey = '@'; insertContent(model, triggerKey); assertExists(model.page.root); - const pageBlock = getBlockElementByModel(model.page.root); + const pageBlock = getBlockComponentByModel(model.page.root); const widgetEle = pageBlock?.widgetElements['affine-linked-page-widget']; assertExists(widgetEle); diff --git a/packages/inline/src/utils/index.ts b/packages/inline/src/utils/index.ts index e7879e6391bf..eb03b99a363d 100644 --- a/packages/inline/src/utils/index.ts +++ b/packages/inline/src/utils/index.ts @@ -7,6 +7,5 @@ export * from './point-conversion.js'; export * from './query.js'; export * from './range-conversion.js'; export * from './renderer.js'; -export * from './selection.js'; export * from './text.js'; export * from './transform-input.js'; diff --git a/packages/inline/src/utils/selection.ts b/packages/inline/src/utils/selection.ts deleted file mode 100644 index 4a80faebcb7d..000000000000 --- a/packages/inline/src/utils/selection.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function isSelectionBackwards(selection: Selection): boolean { - let backwards = false; - if (!selection.isCollapsed && selection.anchorNode && selection.focusNode) { - const range = document.createRange(); - range.setStart(selection.anchorNode, selection.anchorOffset); - range.setEnd(selection.focusNode, selection.focusOffset); - backwards = range.collapsed; - range.detach(); - } - return backwards; -} diff --git a/packages/playground/examples/doc-editor/index.html b/packages/playground/examples/doc-editor/index.html new file mode 100644 index 000000000000..5f04ce5c1b6c --- /dev/null +++ b/packages/playground/examples/doc-editor/index.html @@ -0,0 +1,12 @@ + + + + + + + DocEditor Example + + + + + diff --git a/packages/playground/examples/doc-editor/main.ts b/packages/playground/examples/doc-editor/main.ts new file mode 100644 index 000000000000..1c9c462dc36d --- /dev/null +++ b/packages/playground/examples/doc-editor/main.ts @@ -0,0 +1,21 @@ +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import '@blocksuite/presets/themes/affine.css'; + +import { AffineSchemas } from '@blocksuite/blocks/models'; +import { DocEditor } from '@blocksuite/presets'; +import { Schema, Workspace } from '@blocksuite/store'; + +const schema = new Schema().register(AffineSchemas); +const workspace = new Workspace({ id: 'foo', schema }); + +const page = workspace.createPage(); +await page.load(() => { + const pageBlockId = page.addBlock('affine:page', {}); + page.addBlock('affine:surface', {}, pageBlockId); + const noteId = page.addBlock('affine:note', {}, pageBlockId); + page.addBlock('affine:paragraph', {}, noteId); +}); + +const editor = new DocEditor(); +editor.page = page; +document.body.appendChild(editor); diff --git a/packages/playground/vite.config.ts b/packages/playground/vite.config.ts index b90fb87a71cc..0e84eb396ca9 100644 --- a/packages/playground/vite.config.ts +++ b/packages/playground/vite.config.ts @@ -48,6 +48,10 @@ export default ({ mode }) => { 'examples/basic': resolve(__dirname, 'examples/basic/index.html'), 'examples/inline': resolve(__dirname, 'examples/inline/index.html'), 'examples/store': resolve(__dirname, 'examples/store/index.html'), + 'examples/doc-editor': resolve( + __dirname, + 'examples/doc-editor/index.html' + ), }, }, },