From 1fee08ffa481c16db0bbc07f8391b5bcd3360fc8 Mon Sep 17 00:00:00 2001 From: Flrande Date: Fri, 27 Sep 2024 19:32:03 +0800 Subject: [PATCH 1/4] fix(blocks): click blank area will make editor scroll --- packages/affine/block-list/src/list-block.ts | 2 +- .../block-paragraph/src/paragraph-block.ts | 2 +- packages/blocks/src/code-block/code-block.ts | 2 +- .../src/data-view-block/data-view-block.ts | 2 +- .../src/database-block/database-block.ts | 4 +- .../src/root-block/page/page-root-block.ts | 156 +++++++++++------- .../src/view/element/block-component.ts | 4 +- 7 files changed, 104 insertions(+), 68 deletions(-) diff --git a/packages/affine/block-list/src/list-block.ts b/packages/affine/block-list/src/list-block.ts index 47848969df96..60c1e69e1af2 100644 --- a/packages/affine/block-list/src/list-block.ts +++ b/packages/affine/block-list/src/list-block.ts @@ -85,7 +85,7 @@ export class ListBlockComponent extends CaptionedBlockComponent< if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') { return this.closest(NOTE_SELECTOR); } - return this.rootComponent; + return super.topContenteditableElement; } private _select() { diff --git a/packages/affine/block-paragraph/src/paragraph-block.ts b/packages/affine/block-paragraph/src/paragraph-block.ts index c6f631cbcbb4..034375f23b0b 100644 --- a/packages/affine/block-paragraph/src/paragraph-block.ts +++ b/packages/affine/block-paragraph/src/paragraph-block.ts @@ -80,7 +80,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent< if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') { return this.closest(NOTE_SELECTOR); } - return this.rootComponent; + return super.topContenteditableElement; } override connectedCallback() { diff --git a/packages/blocks/src/code-block/code-block.ts b/packages/blocks/src/code-block/code-block.ts index f68fff29596d..8fe69bb7ce98 100644 --- a/packages/blocks/src/code-block/code-block.ts +++ b/packages/blocks/src/code-block/code-block.ts @@ -74,7 +74,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent< const el = this.closest(NOTE_SELECTOR); return el; } - return this.rootComponent; + return super.topContenteditableElement; } private _updateHighlightTokens() { diff --git a/packages/blocks/src/data-view-block/data-view-block.ts b/packages/blocks/src/data-view-block/data-view-block.ts index c587c9d7e77f..f023d72d29f7 100644 --- a/packages/blocks/src/data-view-block/data-view-block.ts +++ b/packages/blocks/src/data-view-block/data-view-block.ts @@ -235,7 +235,7 @@ export class DataViewBlockComponent extends CaptionedBlockComponent('affine-note'); return note; } - return this.rootComponent; + return super.topContenteditableElement; } get view() { diff --git a/packages/blocks/src/database-block/database-block.ts b/packages/blocks/src/database-block/database-block.ts index f4e8f7a8db4a..aadafef510c6 100644 --- a/packages/blocks/src/database-block/database-block.ts +++ b/packages/blocks/src/database-block/database-block.ts @@ -152,7 +152,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent< private renderTitle = (dataViewMethod: DataViewExpose) => { const addRow = () => dataViewMethod.addRow?.('start'); - return html` (NOTE_SELECTOR); return note; } - return this.rootComponent; + return super.topContenteditableElement; } get view() { diff --git a/packages/blocks/src/root-block/page/page-root-block.ts b/packages/blocks/src/root-block/page/page-root-block.ts index 8a3fce95b21e..dd3526c002d9 100644 --- a/packages/blocks/src/root-block/page/page-root-block.ts +++ b/packages/blocks/src/root-block/page/page-root-block.ts @@ -1,6 +1,5 @@ import type { NoteBlockModel, RootBlockModel } from '@blocksuite/affine-model'; import type { Viewport } from '@blocksuite/affine-shared/types'; -import type { PointerEventState } from '@blocksuite/block-std'; import type { BlockModel, Text } from '@blocksuite/store'; import { focusTextModel } from '@blocksuite/affine-components/rich-text'; @@ -11,7 +10,7 @@ import { getScrollContainer, matchFlavours, } from '@blocksuite/affine-shared/utils'; -import { BlockComponent } from '@blocksuite/block-std'; +import { BLOCK_ID_ATTR, BlockComponent } from '@blocksuite/block-std'; import { css, html } from 'lit'; import { query } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; @@ -26,26 +25,6 @@ import { PageKeyboardManager } from '../keyboard/keyboard-manager.js'; const DOC_BLOCK_CHILD_PADDING = 24; const DOC_BOTTOM_PADDING = 32; -function testClickOnBlankArea( - state: PointerEventState, - viewportLeft: number, - viewportWidth: number, - pageWidth: number, - paddingLeft: number, - paddingRight: number -) { - const blankLeft = - viewportLeft + (viewportWidth - pageWidth) / 2 + paddingLeft; - const blankRight = - viewportLeft + (viewportWidth - pageWidth) / 2 + pageWidth - paddingRight; - - if (state.raw.clientX < blankLeft || state.raw.clientX > blankRight) { - return true; - } - - return false; -} - export class PageRootBlockComponent extends BlockComponent< RootBlockModel, PageRootService, @@ -58,14 +37,19 @@ export class PageRootBlockComponent extends BlockComponent< } affine-page-root { - display: block; + display: grid; height: 100%; + justify-content: center; } .affine-page-root-block-container { - display: flex; - flex-direction: column; - width: 100%; + display: grid; + /* prettier-ignore */ + grid-template-columns: minmax(var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px), 1fr) auto minmax(var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px), 1fr); + grid-template-rows: 1fr auto; + grid-template-areas: + 'left content right' + 'bottom bottom bottom'; height: 100%; font-family: var(--affine-font-family); font-size: var(--affine-font-base); @@ -73,25 +57,30 @@ export class PageRootBlockComponent extends BlockComponent< color: var(--affine-text-primary-color); font-weight: 400; max-width: var(--affine-editor-width); - margin: 0 auto; /* cursor: crosshair; */ cursor: default; + } - /* Leave a place for drag-handle */ - /* Do not use prettier format this style, or it will be broken */ - /* prettier-ignore */ - padding-left: var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px); - /* prettier-ignore */ - padding-right: var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px); - /* prettier-ignore */ - padding-bottom: var(--affine-editor-bottom-padding, ${DOC_BOTTOM_PADDING}px); + .affine-page-root-left-blank { + grid-area: left; + } + .affine-page-root-right-blank { + grid-area: right; + } + .affine-page-root-content { + grid-area: content; + } + + .affine-page-root-bottom-blank { + grid-area: bottom; + height: var(--affine-editor-bottom-padding, ${DOC_BOTTOM_PADDING}px); } /* Extra small devices (phones, 640px and down) */ @container viewport (width <= 640px) { .affine-page-root-block-container { - padding-left: ${DOC_BLOCK_CHILD_PADDING}px; - padding-right: ${DOC_BLOCK_CHILD_PADDING}px; + /* prettier-ignore */ + grid-template-columns: ${DOC_BLOCK_CHILD_PADDING}px auto ${DOC_BLOCK_CHILD_PADDING}px; } } @@ -299,33 +288,65 @@ export class PageRootBlockComponent extends BlockComponent< }, }); - this.handleEvent('click', ctx => { - const event = ctx.get('pointerState'); - if ( - event.raw.target !== this && - event.raw.target !== this.viewportElement && - event.raw.target !== this.rootElementContainer - ) { - return; + const getBlockAreaHandler = (side: 'left' | 'right') => (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + let blockComponent: BlockComponent | null | undefined = null; + if (side === 'left') { + const rect = this.leftBlankArea.getBoundingClientRect(); + let el = document.elementFromPoint(rect.right + 50, e.clientY); + if (!el) { + el = document.elementFromPoint(rect.right - 50, e.clientY - 10); + } + blockComponent = el?.closest(`[${BLOCK_ID_ATTR}]`); + } else { + const rect = this.rightBlankArea.getBoundingClientRect(); + let el = document.elementFromPoint(rect.left - 50, e.clientY); + if (!el) { + el = document.elementFromPoint(rect.left - 50, e.clientY - 10); + } + blockComponent = el?.closest(`[${BLOCK_ID_ATTR}]`); } - const { paddingLeft, paddingRight } = window.getComputedStyle( - this.rootElementContainer - ); - if (!this.viewport) return; - const isClickOnBlankArea = testClickOnBlankArea( - event, - this.viewport.left, - this.viewport.clientWidth, - this.rootElementContainer.clientWidth, - parseFloat(paddingLeft), - parseFloat(paddingRight) - ); - if (isClickOnBlankArea) { - this.host.selection.clear(['block']); - return; + this.selection.clear(['block', 'text']); + + if (blockComponent) { + if (blockComponent.model.text) { + if (side === 'left') { + focusTextModel(this.std, blockComponent.model.id); + } else { + focusTextModel( + this.std, + blockComponent.model.id, + blockComponent.model.text.length + ); + } + } else { + this.selection.setGroup('note', [ + this.selection.create('block', { + blockId: blockComponent.blockId, + }), + ]); + } } + }; + this.updateComplete + .then(() => { + this.disposables.addFromEvent( + this.leftBlankArea, + 'click', + getBlockAreaHandler('left') + ); + this.disposables.addFromEvent( + this.rightBlankArea, + 'click', + getBlockAreaHandler('right') + ); + }) + .catch(console.error); + this.handleEvent('click', () => { let newTextSelectionId: string | null = null; const readonly = this.doc.readonly; const lastNote = this.model.children @@ -429,10 +450,23 @@ export class PageRootBlockComponent extends BlockComponent< }); return html` -
${children} ${widgets}
+
+
+
+ ${children} ${widgets} +
+
+
+
`; } + @query('.affine-page-root-left-blank') + accessor leftBlankArea!: HTMLDivElement; + + @query('.affine-page-root-right-blank') + accessor rightBlankArea!: HTMLDivElement; + @query('.affine-page-root-block-container') accessor rootElementContainer!: HTMLDivElement; } diff --git a/packages/framework/block-std/src/view/element/block-component.ts b/packages/framework/block-std/src/view/element/block-component.ts index b5a04bcaf082..49e954776911 100644 --- a/packages/framework/block-std/src/view/element/block-component.ts +++ b/packages/framework/block-std/src/view/element/block-component.ts @@ -158,7 +158,9 @@ export class BlockComponent< } get topContenteditableElement(): BlockComponent | null { - return this.rootComponent; + return ( + this.rootComponent?.querySelector('[contenteditable="true"]') ?? null + ); } get widgetComponents(): Partial> { From c10ef6eefbb3ea764661e0c630c90dcf417ff53e Mon Sep 17 00:00:00 2001 From: Flrande Date: Sun, 6 Oct 2024 18:25:50 +0800 Subject: [PATCH 2/4] fix: fix --- packages/blocks/src/root-block/page/page-root-block.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/blocks/src/root-block/page/page-root-block.ts b/packages/blocks/src/root-block/page/page-root-block.ts index dd3526c002d9..cd39e6c5f6db 100644 --- a/packages/blocks/src/root-block/page/page-root-block.ts +++ b/packages/blocks/src/root-block/page/page-root-block.ts @@ -36,16 +36,10 @@ export class PageRootBlockComponent extends BlockComponent< height: 100%; } - affine-page-root { - display: grid; - height: 100%; - justify-content: center; - } - .affine-page-root-block-container { display: grid; /* prettier-ignore */ - grid-template-columns: minmax(var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px), 1fr) auto minmax(var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px), 1fr); + grid-template-columns: var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px) auto var(--affine-editor-side-padding, ${DOC_BLOCK_CHILD_PADDING}px); grid-template-rows: 1fr auto; grid-template-areas: 'left content right' @@ -57,6 +51,7 @@ export class PageRootBlockComponent extends BlockComponent< color: var(--affine-text-primary-color); font-weight: 400; max-width: var(--affine-editor-width); + width: 100%; /* cursor: crosshair; */ cursor: default; } From d232d68cf06a5a5c4091143ba3222b45c016771a Mon Sep 17 00:00:00 2001 From: Flrande Date: Sun, 6 Oct 2024 18:42:05 +0800 Subject: [PATCH 3/4] fix: fix --- tests/utils/actions/misc.ts | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/tests/utils/actions/misc.ts b/tests/utils/actions/misc.ts index b381b1a593c8..1062407c76b0 100644 --- a/tests/utils/actions/misc.ts +++ b/tests/utils/actions/misc.ts @@ -39,7 +39,6 @@ export const defaultPlaygroundURL = new URL( const NEXT_FRAME_TIMEOUT = 50; const DEFAULT_PLAYGROUND = defaultPlaygroundURL.toString(); -const RICH_TEXT_SELECTOR = '.inline-editor'; function generateRandomRoomId() { return `playwright-${uuidv4()}`; @@ -740,20 +739,12 @@ export async function initEmptyCodeBlockState( return ids; } -type FocusRichTextOptions = { - clickPosition?: { x: number; y: number }; -}; - -export async function focusRichText( - page: Page, - i = 0, - options?: FocusRichTextOptions -) { - await page.mouse.move(0, 0); - const editor = getEditorHostLocator(page); - const locator = editor.locator(RICH_TEXT_SELECTOR).nth(i); - // need to set `force` to true when clicking on `affine-selected-blocks` - await locator.click({ force: true, position: options?.clickPosition }); +/** + * @deprecated + * use `focusRichTextEnd` + */ +export async function focusRichText(page: Page, i = 0) { + await focusRichTextEnd(page, i); } export async function focusRichTextEnd(page: Page, i = 0) { From ee3bb0981581d6b2f72463824113cb58e80f52ab Mon Sep 17 00:00:00 2001 From: Flrande Date: Sun, 6 Oct 2024 19:45:01 +0800 Subject: [PATCH 4/4] fix: fix --- .../src/root-block/page/page-root-block.ts | 134 +++++++++--------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/packages/blocks/src/root-block/page/page-root-block.ts b/packages/blocks/src/root-block/page/page-root-block.ts index cd39e6c5f6db..beaa81055d25 100644 --- a/packages/blocks/src/root-block/page/page-root-block.ts +++ b/packages/blocks/src/root-block/page/page-root-block.ts @@ -44,7 +44,6 @@ export class PageRootBlockComponent extends BlockComponent< grid-template-areas: 'left content right' 'bottom bottom bottom'; - height: 100%; font-family: var(--affine-font-family); font-size: var(--affine-font-base); line-height: var(--affine-line-height); @@ -52,6 +51,7 @@ export class PageRootBlockComponent extends BlockComponent< font-weight: 400; max-width: var(--affine-editor-width); width: 100%; + margin: 0 auto; /* cursor: crosshair; */ cursor: default; } @@ -338,71 +338,74 @@ export class PageRootBlockComponent extends BlockComponent< 'click', getBlockAreaHandler('right') ); - }) - .catch(console.error); + this.disposables.addFromEvent(this.bottomBlankArea, 'click', () => { + let newTextSelectionId: string | null = null; + const readonly = this.doc.readonly; + const lastNote = this.model.children + .slice() + .reverse() + .find(child => { + const isNote = matchFlavours(child, ['affine:note']); + if (!isNote) return false; + const note = child as NoteBlockModel; + const displayOnDoc = + !!note.displayMode && + note.displayMode !== NoteDisplayMode.EdgelessOnly; + return displayOnDoc; + }); + if (!lastNote) { + if (readonly) return; + const noteId = this.doc.addBlock('affine:note', {}, this.model.id); + const paragraphId = this.doc.addBlock( + 'affine:paragraph', + {}, + noteId + ); + newTextSelectionId = paragraphId; + } else { + const last = lastNote.children.at(-1); + if ( + !last || + !last.text || + matchFlavours(last, [ + 'affine:code', + 'affine:divider', + 'affine:image', + 'affine:database', + 'affine:bookmark', + 'affine:attachment', + 'affine:surface-ref', + ]) || + /affine:embed-*/.test(last.flavour) + ) { + if (readonly) return; + const paragraphId = this.doc.addBlock( + 'affine:paragraph', + {}, + lastNote.id + ); + newTextSelectionId = paragraphId; + } + } - this.handleEvent('click', () => { - let newTextSelectionId: string | null = null; - const readonly = this.doc.readonly; - const lastNote = this.model.children - .slice() - .reverse() - .find(child => { - const isNote = matchFlavours(child, ['affine:note']); - if (!isNote) return false; - const note = child as NoteBlockModel; - const displayOnDoc = - !!note.displayMode && - note.displayMode !== NoteDisplayMode.EdgelessOnly; - return displayOnDoc; + this.updateComplete + .then(() => { + if (!newTextSelectionId) return; + this.host.selection.setGroup('note', [ + this.host.selection.create('text', { + from: { + blockId: newTextSelectionId, + index: 0, + length: 0, + }, + to: null, + }), + ]); + }) + .catch(console.error); }); - if (!lastNote) { - if (readonly) return; - const noteId = this.doc.addBlock('affine:note', {}, this.model.id); - const paragraphId = this.doc.addBlock('affine:paragraph', {}, noteId); - newTextSelectionId = paragraphId; - } else { - const last = lastNote.children.at(-1); - if ( - !last || - !last.text || - matchFlavours(last, [ - 'affine:code', - 'affine:divider', - 'affine:image', - 'affine:database', - 'affine:bookmark', - 'affine:attachment', - 'affine:surface-ref', - ]) || - /affine:embed-*/.test(last.flavour) - ) { - if (readonly) return; - const paragraphId = this.doc.addBlock( - 'affine:paragraph', - {}, - lastNote.id - ); - newTextSelectionId = paragraphId; - } - } - - this.updateComplete - .then(() => { - if (!newTextSelectionId) return; - this.host.selection.setGroup('note', [ - this.host.selection.create('text', { - from: { - blockId: newTextSelectionId, - index: 0, - length: 0, - }, - to: null, - }), - ]); - }) - .catch(console.error); - }); + }) + .catch(console.error); } override disconnectedCallback() { @@ -456,6 +459,9 @@ export class PageRootBlockComponent extends BlockComponent< `; } + @query('.affine-page-root-bottom-blank') + accessor bottomBlankArea!: HTMLDivElement; + @query('.affine-page-root-left-blank') accessor leftBlankArea!: HTMLDivElement;