mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 09:59:55 +08:00
refactor(editor): refactor page note empty checker (#9570)
Close [BS-2320](https://linear.app/affine-design/issue/BS-2320/内容为空的状态判断)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { beforeEach, expect, test } from 'vitest';
|
||||
import { LocalShapeElementModel } from '@blocksuite/affine-model';
|
||||
import { Text } from '@blocksuite/store';
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { getSurface } from '../utils/edgeless.js';
|
||||
import { addNote, getSurface } from '../utils/edgeless.js';
|
||||
import { setupEditor } from '../utils/setup.js';
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -16,3 +18,136 @@ test('basic assert', () => {
|
||||
|
||||
expect(getSurface(window.doc, window.editor)).toBeDefined();
|
||||
});
|
||||
|
||||
describe('doc / note empty checker', () => {
|
||||
test('a paragraph is empty if it dose not contain text and child blocks', () => {
|
||||
const noteId = addNote(doc);
|
||||
const paragraphId = doc.addBlock('affine:paragraph', {}, noteId);
|
||||
const paragraph = doc.getBlock(paragraphId)?.model;
|
||||
expect(paragraph?.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
test('a paragraph is not empty if it contains text', () => {
|
||||
const noteId = addNote(doc);
|
||||
const paragraphId = doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new Text('hello'),
|
||||
},
|
||||
noteId
|
||||
);
|
||||
const paragraph = doc.getBlock(paragraphId)?.model;
|
||||
expect(paragraph?.isEmpty()).toBe(false);
|
||||
});
|
||||
|
||||
test('a paragraph is not empty if it contains children blocks', () => {
|
||||
const noteId = addNote(doc);
|
||||
const paragraphId = doc.addBlock('affine:paragraph', {}, noteId);
|
||||
const paragraph = doc.getBlock(paragraphId)?.model;
|
||||
|
||||
// sub paragraph
|
||||
doc.addBlock('affine:paragraph', {}, paragraphId);
|
||||
expect(paragraph?.isEmpty()).toBe(false);
|
||||
});
|
||||
|
||||
test('a note is empty if it dose not contain any blocks', () => {
|
||||
const noteId = addNote(doc);
|
||||
const note = doc.getBlock(noteId)!.model;
|
||||
note.children.forEach(child => {
|
||||
doc.deleteBlock(child);
|
||||
});
|
||||
expect(note.children.length).toBe(0);
|
||||
expect(note.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
test('a note is empty if it only contains a empty paragraph', () => {
|
||||
// `addNote` will create a empty paragraph
|
||||
const noteId = addNote(doc);
|
||||
const note = doc.getBlock(noteId)!.model;
|
||||
expect(note.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
test('a note is not empty if it contains multi blocks', () => {
|
||||
const noteId = addNote(doc);
|
||||
const note = doc.getBlock(noteId)!.model;
|
||||
doc.addBlock('affine:paragraph', {}, noteId);
|
||||
expect(note.isEmpty()).toBe(false);
|
||||
});
|
||||
|
||||
test('a surface is empty if it dose not contains any element or blocks', () => {
|
||||
const surface = getSurface(doc, editor).model;
|
||||
expect(surface.isEmpty()).toBe(true);
|
||||
|
||||
const shapeId = surface.addElement({
|
||||
type: 'shape',
|
||||
});
|
||||
expect(surface.isEmpty()).toBe(false);
|
||||
surface.deleteElement(shapeId);
|
||||
expect(surface.isEmpty()).toBe(true);
|
||||
|
||||
const frameId = doc.addBlock('affine:frame', {}, surface.id);
|
||||
const frame = doc.getBlock(frameId)!.model;
|
||||
expect(surface.isEmpty()).toBe(false);
|
||||
doc.deleteBlock(frame);
|
||||
expect(surface.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
test('a surface is empty if it only contains local elements', () => {
|
||||
const surface = getSurface(doc, editor).model;
|
||||
const localShape = new LocalShapeElementModel(surface);
|
||||
surface.addLocalElement(localShape);
|
||||
expect(surface.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
test('a just initialized doc is empty', () => {
|
||||
expect(doc.isEmpty).toBe(true);
|
||||
expect(editor.rootModel.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
test('a doc is empty if it only contains a note', () => {
|
||||
addNote(doc);
|
||||
expect(doc.isEmpty).toBe(true);
|
||||
|
||||
addNote(doc);
|
||||
expect(
|
||||
doc.isEmpty,
|
||||
'a doc is not empty if it contains multi-notes'
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('a note is empty if its children array is empty', () => {
|
||||
const noteId = addNote(doc);
|
||||
const note = doc.getBlock(noteId)?.model;
|
||||
note?.children.forEach(child => doc.deleteBlock(child));
|
||||
expect(note?.children.length === 0).toBe(true);
|
||||
expect(note?.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
test('a doc is empty if its only contains an empty note and an empty surface', () => {
|
||||
const noteId = addNote(doc);
|
||||
const note = doc.getBlock(noteId)!.model;
|
||||
expect(doc.isEmpty).toBe(true);
|
||||
|
||||
const newNoteId = addNote(doc);
|
||||
const newNote = doc.getBlock(newNoteId)!.model;
|
||||
expect(doc.isEmpty).toBe(false);
|
||||
doc.deleteBlock(newNote);
|
||||
expect(doc.isEmpty).toBe(true);
|
||||
|
||||
const newParagraphId = doc.addBlock('affine:paragraph', {}, note);
|
||||
const newParagraph = doc.getBlock(newParagraphId)!.model;
|
||||
expect(doc.isEmpty).toBe(false);
|
||||
doc.deleteBlock(newParagraph);
|
||||
expect(doc.isEmpty).toBe(true);
|
||||
|
||||
const surface = getSurface(doc, editor).model;
|
||||
expect(doc.isEmpty).toBe(true);
|
||||
|
||||
const shapeId = surface.addElement({
|
||||
type: 'shape',
|
||||
});
|
||||
expect(doc.isEmpty).toBe(false);
|
||||
surface.deleteElement(shapeId);
|
||||
expect(doc.isEmpty).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,9 +61,8 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
private readonly _onTitleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.isComposing || this.doc.readonly) return;
|
||||
const hasContent = !this.doc.isEmpty;
|
||||
|
||||
if (event.key === 'Enter' && hasContent && !event.isComposing) {
|
||||
if (event.key === 'Enter' && this._pageRoot) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
@@ -73,10 +72,10 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
|
||||
const rightText = this._rootModel.title.split(inlineRange.index);
|
||||
this._pageRoot.prependParagraphWithText(rightText);
|
||||
}
|
||||
} else if (event.key === 'ArrowDown' && hasContent) {
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._pageRoot.focusFirstParagraph();
|
||||
this._pageRoot?.focusFirstParagraph();
|
||||
} else if (event.key === 'Tab') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -94,9 +93,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
|
||||
private get _pageRoot() {
|
||||
const pageRoot = this._viewport.querySelector('affine-page-root');
|
||||
assertExists(pageRoot);
|
||||
return pageRoot;
|
||||
return this._viewport.querySelector('affine-page-root');
|
||||
}
|
||||
|
||||
private get _rootModel() {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import type { BlockModel, Blocks } from '@blocksuite/store';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
|
||||
import { css, html, LitElement, unsafeCSS } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
@@ -286,7 +286,7 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
}
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
override updated() {
|
||||
this._displayModePopper = createButtonPopper(
|
||||
this._displayModeButtonGroup,
|
||||
this._displayModePanel,
|
||||
@@ -303,8 +303,6 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.note.isEmpty.peek()) return nothing;
|
||||
|
||||
const { children, displayMode } = this.note;
|
||||
const currentMode = this._getCurrentModeLabel(displayMode);
|
||||
const cardHeaderClasses = classMap({
|
||||
|
||||
Reference in New Issue
Block a user