Saul-Mirone
2025-03-27 05:27:59 +00:00
parent 5f5c27fbdf
commit d9d5aa407a
12 changed files with 90 additions and 45 deletions

View File

@@ -82,8 +82,10 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
private readonly _onTitleKeyDown = (event: KeyboardEvent) => { private readonly _onTitleKeyDown = (event: KeyboardEvent) => {
if (event.isComposing || this.doc.readonly) return; if (event.isComposing || this.doc.readonly) return;
if (!this._std) return;
if (event.key === 'Enter') { if (event.key === 'Enter') {
this._std.event.active = true;
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@@ -99,6 +101,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
if (this._std) focusTextModel(this._std, newFirstParagraphId); if (this._std) focusTextModel(this._std, newFirstParagraphId);
} }
} else if (event.key === 'ArrowDown') { } else if (event.key === 'ArrowDown') {
this._std.event.active = true;
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();

View File

@@ -1,8 +1,12 @@
import { changeNoteDisplayMode } from '@blocksuite/affine-block-note'; import { changeNoteDisplayMode } from '@blocksuite/affine-block-note';
import { NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model'; import { NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model';
import { DocModeProvider } from '@blocksuite/affine-shared/services'; import { DocModeProvider } from '@blocksuite/affine-shared/services';
import { matchModels } from '@blocksuite/affine-shared/utils'; import { focusTitle, matchModels } from '@blocksuite/affine-shared/utils';
import { ShadowlessElement, SurfaceSelection } from '@blocksuite/block-std'; import {
BlockSelection,
ShadowlessElement,
SurfaceSelection,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/gfx'; import { Bound } from '@blocksuite/global/gfx';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
@@ -170,6 +174,19 @@ export class OutlinePanelBody extends SignalWatcher(
} }
private async _scrollToBlock(blockId: string) { private async _scrollToBlock(blockId: string) {
// if focus title
if (blockId === this.doc.root?.id) {
this.editor.std.selection.setGroup('note', []);
this.editor.std.event.active = false;
focusTitle(this.editor);
} else {
this.editor.std.event.active = true;
this.editor.std.selection.setGroup('note', [
this.editor.std.selection.create(BlockSelection, {
blockId,
}),
]);
}
this._lockActiveHeadingId = true; this._lockActiveHeadingId = true;
this._activeHeadingId$.value = blockId; this._activeHeadingId$.value = blockId;
this._clearHighlightMask = await scrollToBlockWithHighlight( this._clearHighlightMask = await scrollToBlockWithHighlight(

View File

@@ -68,6 +68,7 @@ export function focusTextModel(
id: string, id: string,
offset: number = 0 offset: number = 0
) { ) {
std.event.active = true;
selectTextModel(std, id, offset); selectTextModel(std, id, offset);
} }

View File

@@ -19,6 +19,7 @@ export const focusBlockEnd: Command<{
const { selection } = std; const { selection } = std;
std.event.active = true;
if (force) selection.clear(); if (force) selection.clear();
selection.setGroup('note', [ selection.setGroup('note', [

View File

@@ -1,5 +1,6 @@
import { DisposableGroup } from '@blocksuite/global/disposable'; import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { signal } from '@preact/signals-core';
import { LifeCycleWatcher } from '../extension/index.js'; import { LifeCycleWatcher } from '../extension/index.js';
import { KeymapIdentifier } from '../identifier.js'; import { KeymapIdentifier } from '../identifier.js';
@@ -81,7 +82,7 @@ export class UIEventDispatcher extends LifeCycleWatcher {
static override readonly key = 'UIEventDispatcher'; static override readonly key = 'UIEventDispatcher';
private _active = false; private readonly _active = signal(false);
private readonly _clipboardControl: ClipboardControl; private readonly _clipboardControl: ClipboardControl;
@@ -105,6 +106,10 @@ export class UIEventDispatcher extends LifeCycleWatcher {
} }
get active() { get active() {
return this._active.peek();
}
get active$() {
return this._active; return this._active;
} }
@@ -302,19 +307,24 @@ export class UIEventDispatcher extends LifeCycleWatcher {
if (active) { if (active) {
if (UIEventDispatcher._activeDispatcher !== this) { if (UIEventDispatcher._activeDispatcher !== this) {
if (UIEventDispatcher._activeDispatcher) { if (UIEventDispatcher._activeDispatcher) {
UIEventDispatcher._activeDispatcher._active = false; UIEventDispatcher._activeDispatcher._active.value = false;
} }
UIEventDispatcher._activeDispatcher = this; UIEventDispatcher._activeDispatcher = this;
} }
this._active = true; this._active.value = true;
} else { } else {
if (UIEventDispatcher._activeDispatcher === this) { if (UIEventDispatcher._activeDispatcher === this) {
UIEventDispatcher._activeDispatcher = null; UIEventDispatcher._activeDispatcher = null;
} }
this._active = false; this._active.value = false;
} }
} }
set active(active: boolean) {
if (active === this._active.peek()) return;
this._setActive(active);
}
add(name: EventName, handler: UIEventHandler, options?: EventOptions) { add(name: EventName, handler: UIEventHandler, options?: EventOptions) {
const runner: EventHandlerRunner = { const runner: EventHandlerRunner = {
fn: handler, fn: handler,

View File

@@ -169,7 +169,10 @@ export class RangeBinding {
private readonly _onNativeSelectionChanged = async () => { private readonly _onNativeSelectionChanged = async () => {
if (this.isComposing) return; if (this.isComposing) return;
if (!this.host) return; // Unstable when switching views, card <-> embed if (!this.host) return; // Unstable when switching views, card <-> embed
if (!isActiveInEditor(this.host)) return; if (!isActiveInEditor(this.host)) {
this._prevTextSelection = null;
return;
}
await this.host.updateComplete; await this.host.updateComplete;
@@ -251,6 +254,8 @@ export class RangeBinding {
// TODO(@mirone): this is a trade-off, we need to use separate awareness store for every store to make sure the selection is isolated. // TODO(@mirone): this is a trade-off, we need to use separate awareness store for every store to make sure the selection is isolated.
const closestHost = document.activeElement?.closest('editor-host'); const closestHost = document.activeElement?.closest('editor-host');
if (closestHost && closestHost !== this.host) return; if (closestHost && closestHost !== this.host) return;
const active = this.host.event.active;
if (!active) return;
const text = const text =
selections.find((selection): selection is TextSelection => selections.find((selection): selection is TextSelection =>
@@ -266,7 +271,7 @@ export class RangeBinding {
const id = text?.blockId; const id = text?.blockId;
const path = id && this._computePath(id); const path = id && this._computePath(id);
if (this.host.event.active) { if (active) {
const eq = const eq =
text && this._prevTextSelection && path text && this._prevTextSelection && path
? text.equals(this._prevTextSelection.selection) && ? text.equals(this._prevTextSelection.selection) &&

View File

@@ -1,3 +1,4 @@
import { waitNextFrame } from '@affine-test/kit/bs/misc';
import { test } from '@affine-test/kit/playwright'; import { test } from '@affine-test/kit/playwright';
import { locateEditorContainer } from '@affine-test/kit/utils/editor'; import { locateEditorContainer } from '@affine-test/kit/utils/editor';
import { openHomePage } from '@affine-test/kit/utils/load-page'; import { openHomePage } from '@affine-test/kit/utils/load-page';
@@ -55,6 +56,7 @@ test('link page is useable', async ({ page }) => {
await page.keyboard.press('g'); await page.keyboard.press('g');
await page.keyboard.press('e'); await page.keyboard.press('e');
await page.keyboard.press('1'); await page.keyboard.press('1');
await waitNextFrame(page);
await page.locator('icon-button:has-text("page1")').first().click(); await page.locator('icon-button:has-text("page1")').first().click();
const link = page.locator('.affine-reference'); const link = page.locator('.affine-reference');
await expect(link).toBeVisible(); await expect(link).toBeVisible();

View File

@@ -127,6 +127,7 @@ test(
await activeNoteInEdgeless(page, ids.noteId); await activeNoteInEdgeless(page, ids.noteId);
await waitForInlineEditorStateUpdated(page); await waitForInlineEditorStateUpdated(page);
await focusRichText(page);
await selectAllByKeyboard(page); await selectAllByKeyboard(page);
await copyByKeyboard(page); await copyByKeyboard(page);
await pressArrowRight(page); await pressArrowRight(page);

View File

@@ -5,7 +5,6 @@ import { expect } from '@playwright/test';
import { import {
captureHistory, captureHistory,
copyByKeyboard, copyByKeyboard,
dragBetweenCoords,
dragOverTitle, dragOverTitle,
enterPlaygroundRoom, enterPlaygroundRoom,
focusRichText, focusRichText,
@@ -20,15 +19,18 @@ import {
mockParseDocUrlService, mockParseDocUrlService,
pasteByKeyboard, pasteByKeyboard,
pasteContent, pasteContent,
pressArrowRight,
pressEnter, pressEnter,
pressShiftTab, pressShiftTab,
pressTab, pressTab,
resetHistory, resetHistory,
selectAllByKeyboard,
setInlineRangeInSelectedRichText, setInlineRangeInSelectedRichText,
setSelection, setSelection,
SHORT_KEY, SHORT_KEY,
type, type,
undoByClick, undoByClick,
undoByKeyboard,
waitNextFrame, waitNextFrame,
} from '../utils/actions/index.js'; } from '../utils/actions/index.js';
import { import {
@@ -106,15 +108,15 @@ test(scoped`split block when paste`, async ({ page }) => {
`, `,
}; };
await type(page, 'abc'); await type(page, 'abc');
await captureHistory(page);
await setInlineRangeInSelectedRichText(page, 1, 1); await setInlineRangeInSelectedRichText(page, 1, 1);
await captureHistory(page);
await pasteContent(page, clipData); await pasteContent(page, clipData);
await waitNextFrame(page); await waitNextFrame(page);
await assertRichTexts(page, ['atext', 'h1c']); await assertRichTexts(page, ['atext', 'h1c']);
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['abc']); await assertRichTexts(page, ['abc']);
await type(page, 'aa'); await type(page, 'aa');
@@ -132,11 +134,12 @@ test(scoped`split block when paste`, async ({ page }) => {
if (!bottomRight789) { if (!bottomRight789) {
throw new Error('bottomRight789 is not found'); throw new Error('bottomRight789 is not found');
} }
await dragBetweenCoords(page, topLeft123, bottomRight789); await selectAllByKeyboard(page);
await pressArrowRight(page);
await pressEnter(page);
// FIXME see https://github.com/toeverything/blocksuite/pull/878 await pasteContent(page, clipData);
// await pasteContent(page, clipData); await assertRichTexts(page, ['aaa', 'bbc', 'text', 'h1']);
// await assertRichTexts(page, ['aaa', 'bbc', 'text', 'h1']);
}); });
test(scoped`copy clipItems format`, async ({ page }) => { test(scoped`copy clipItems format`, async ({ page }) => {

View File

@@ -481,6 +481,7 @@ test(scoped`copy when text note active in edgeless`, async ({ page }) => {
await activeNoteInEdgeless(page, ids.noteId); await activeNoteInEdgeless(page, ids.noteId);
await waitForInlineEditorStateUpdated(page); await waitForInlineEditorStateUpdated(page);
await focusRichText(page);
await setInlineRangeInSelectedRichText(page, 0, 4); await setInlineRangeInSelectedRichText(page, 0, 4);
await copyByKeyboard(page); await copyByKeyboard(page);
await pressArrowRight(page); await pressArrowRight(page);

View File

@@ -288,6 +288,7 @@ test('link bar should not be appear when the range is collapsed', async ({
await pressCreateLinkShortCut(page); await pressCreateLinkShortCut(page);
await expect(linkPopoverLocator).toBeVisible(); await expect(linkPopoverLocator).toBeVisible();
await focusRichText(page); // click to cancel the link popover
await focusRichTextEnd(page); await focusRichTextEnd(page);
await pressEnter(page); await pressEnter(page);
// create auto line-break in span element // create auto line-break in span element

View File

@@ -37,9 +37,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'todo'); await assertBlockType(page, id, 'todo');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '[] '); await assertText(page, '[] ');
await undoByClick(page); await undoByKeyboard(page);
//FIXME: it just failed in playwright //FIXME: it just failed in playwright
await focusRichText(page); await focusRichText(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
@@ -49,9 +49,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'todo'); await assertBlockType(page, id, 'todo');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '[ ] '); await assertText(page, '[ ] ');
await undoByClick(page); await undoByKeyboard(page);
//FIXME: it just failed in playwright //FIXME: it just failed in playwright
await focusRichText(page); await focusRichText(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
@@ -61,9 +61,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'todo'); await assertBlockType(page, id, 'todo');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '[x] '); await assertText(page, '[x] ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -71,9 +71,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'bulleted'); await assertBlockType(page, id, 'bulleted');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '* '); await assertText(page, '* ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -81,9 +81,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'bulleted'); await assertBlockType(page, id, 'bulleted');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '- '); await assertText(page, '- ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -91,9 +91,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'numbered'); await assertBlockType(page, id, 'numbered');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '1. '); await assertText(page, '1. ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -101,9 +101,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'numbered'); await assertBlockType(page, id, 'numbered');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '20. '); await assertText(page, '20. ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -111,9 +111,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'h1'); await assertBlockType(page, id, 'h1');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '# '); await assertText(page, '# ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -121,9 +121,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'h2'); await assertBlockType(page, id, 'h2');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '## '); await assertText(page, '## ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -131,9 +131,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'h3'); await assertBlockType(page, id, 'h3');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '### '); await assertText(page, '### ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -141,9 +141,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'h4'); await assertBlockType(page, id, 'h4');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '#### '); await assertText(page, '#### ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -151,9 +151,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'h5'); await assertBlockType(page, id, 'h5');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '##### '); await assertText(page, '##### ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -161,9 +161,9 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'h6'); await assertBlockType(page, id, 'h6');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '###### '); await assertText(page, '###### ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);
@@ -171,17 +171,17 @@ test('markdown shortcut', async ({ page }) => {
await waitNextFrame(page); await waitNextFrame(page);
[id] = await getCursorBlockIdAndHeight(page); [id] = await getCursorBlockIdAndHeight(page);
await assertBlockType(page, id, 'quote'); await assertBlockType(page, id, 'quote');
await undoByClick(page); await undoByKeyboard(page);
await assertText(page, '> '); await assertText(page, '> ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
// testing various horizontal dividers // testing various horizontal dividers
await waitNextFrame(page); await waitNextFrame(page);
await type(page, '--- '); await type(page, '--- ');
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['--- ']); await assertRichTexts(page, ['--- ']);
await undoByClick(page); await undoByKeyboard(page);
await assertRichTexts(page, ['']); await assertRichTexts(page, ['']);
await waitNextFrame(page); await waitNextFrame(page);