feat(editor): unify block props api (#10888)

Closes: [BS-2707](https://linear.app/affine-design/issue/BS-2707/统一使用props获取和更新block-prop)
This commit is contained in:
Saul-Mirone
2025-03-16 05:48:34 +00:00
parent 8f9e5bf0aa
commit 26285f7dcb
193 changed files with 1019 additions and 891 deletions

View File

@@ -72,7 +72,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
const note = this._rootModel.children.find(
(child): child is NoteBlockModel =>
matchModels(child, [NoteBlockModel]) &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
child.props.displayMode !== NoteDisplayMode.EdgelessOnly
);
if (note) return note;
@@ -89,7 +89,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
const inlineRange = this.inlineEditor?.getInlineRange();
if (inlineRange) {
const rightText = this._rootModel.title.split(inlineRange.index);
const rightText = this._rootModel.props.title.split(inlineRange.index);
const newFirstParagraphId = this.doc.addBlock(
'affine:paragraph',
{ text: rightText },
@@ -129,7 +129,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
private readonly _updateTitleInMeta = () => {
this.doc.workspace.meta.setDocMeta(this.doc.id, {
title: this._rootModel.title.toString(),
title: this._rootModel.props.title.toString(),
});
};
@@ -188,14 +188,14 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
this._updateTitleInMeta();
this.requestUpdate();
};
this._rootModel.title.yText.observe(updateMetaTitle);
this._rootModel.props.title.yText.observe(updateMetaTitle);
this._disposables.add(() => {
this._rootModel.title.yText.unobserve(updateMetaTitle);
this._rootModel.props.title.yText.unobserve(updateMetaTitle);
});
}
override render() {
const isEmpty = !this._rootModel.title.length && !this._isComposing;
const isEmpty = !this._rootModel.props.title.length && !this._isComposing;
return html`
<div
@@ -205,7 +205,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
data-block-is-title="true"
>
<rich-text
.yText=${this._rootModel.title.yText}
.yText=${this._rootModel.props.title.yText}
.undoManager=${this.doc.history}
.verticalScrollContainerGetter=${() => this._viewport}
.readonly=${this.doc.readonly}

View File

@@ -150,7 +150,7 @@ export class FramePanelBody extends SignalWatcher(
private readonly _updateFrameItems = () => {
this._frameItems = this.frames.map((frame, idx) => ({
frame,
frameIndex: frame.presentationIndex ?? frame.index,
frameIndex: frame.props.presentationIndex ?? frame.props.index,
cardIndex: idx,
}));
};
@@ -307,8 +307,8 @@ export class FramePanelBody extends SignalWatcher(
// update selected frames index
// make the indexes larger than the frame before and smaller than the frame after
let before = frames[insertIndex - 1]?.presentationIndex || null;
const after = frames[insertIndex]?.presentationIndex || null;
let before = frames[insertIndex - 1]?.props.presentationIndex || null;
const after = frames[insertIndex]?.props.presentationIndex || null;
selectedFrames.forEach(frame => {
const newIndex = generateKeyBetweenV2(before, after);
frame.doc.updateBlock(frame, {
@@ -352,7 +352,7 @@ export class FramePanelBody extends SignalWatcher(
frames.forEach((frame, idx) => {
const frameItem = {
frame,
frameIndex: frame.presentationIndex ?? frame.index,
frameIndex: frame.props.presentationIndex ?? frame.props.index,
cardIndex: idx,
};

View File

@@ -94,7 +94,7 @@ export class FrameCardTitleEditor extends WithDisposable(ShadowlessElement) {
display: 'block',
});
return html`<rich-text
.yText=${this.frameModel.title.yText}
.yText=${this.frameModel.props.title.yText}
.enableFormat=${false}
.enableAutoScrollHorizontally=${true}
.enableUndoRedo=${false}

View File

@@ -116,7 +116,7 @@ export class FrameCardTitle extends WithDisposable(ShadowlessElement) {
<span
@click=${(e: MouseEvent) => e.stopPropagation()}
@dblclick=${this._mountTitleEditor}
>${this.frame.title}</span
>${this.frame.props.title}</span
>
</div>
</div>`;
@@ -124,7 +124,7 @@ export class FrameCardTitle extends WithDisposable(ShadowlessElement) {
override updated(_changedProperties: PropertyValues) {
if (_changedProperties.has('frame')) {
this._setFrameDisposables(this.frame.title.yText);
this._setFrameDisposables(this.frame.props.title.yText);
}
}

View File

@@ -90,7 +90,8 @@ export class OutlineNoteCard extends SignalWatcher(
this.disposables.add(
std.dnd.draggable<NoteCardEntity>({
element: this,
canDrag: () => this.note.displayMode !== NoteDisplayMode.EdgelessOnly,
canDrag: () =>
this.note.props.displayMode !== NoteDisplayMode.EdgelessOnly,
onDragStart: () => {
if (this.status !== 'selected') {
this.dispatchEvent(
@@ -139,7 +140,7 @@ export class OutlineNoteCard extends SignalWatcher(
noteId: this.note.id,
}),
canDrop: () => {
return this.note.displayMode !== NoteDisplayMode.EdgelessOnly;
return this.note.props.displayMode !== NoteDisplayMode.EdgelessOnly;
},
})
);
@@ -167,16 +168,17 @@ export class OutlineNoteCard extends SignalWatcher(
}
override render() {
const { children, displayMode } = this.note;
const { displayMode } = this.note.props;
const { children } = this.note;
const currentMode = this._getCurrentModeLabel(displayMode);
const invisible =
this.note.displayMode$.value === NoteDisplayMode.EdgelessOnly;
this.note.props.displayMode$.value === NoteDisplayMode.EdgelessOnly;
const enableSorting = this._context.enableSorting$.value;
return html`
<div
data-visibility=${this.note.displayMode}
data-visibility=${this.note.props.displayMode}
data-sortable=${enableSorting}
data-status=${this.status}
class=${styles.outlineCard}

View File

@@ -45,8 +45,8 @@ export class OutlineBlockPreview extends SignalWatcher(
private _TextBlockPreview(block: ParagraphBlockModel | ListBlockModel) {
const deltas: DeltaInsert<AffineTextAttributes>[] =
block.text.yText.toDelta();
if (!block.text.length) return nothing;
block.props.text.yText.toDelta();
if (!block.props.text.length) return nothing;
const iconClass = this.disabledIcon ? styles.iconDisabled : styles.icon;
const previewText = deltas.map(delta => {
@@ -90,17 +90,21 @@ export class OutlineBlockPreview extends SignalWatcher(
});
const headingClass =
block.type in styles.subtypeStyles
? styles.subtypeStyles[block.type as keyof typeof styles.subtypeStyles]
block.props.type in styles.subtypeStyles
? styles.subtypeStyles[
block.props.type as keyof typeof styles.subtypeStyles
]
: '';
return html`<span
data-testid="outline-block-preview-${block.type}"
data-testid="outline-block-preview-${block.props.type}"
class="${styles.text} ${styles.textGeneral} ${headingClass}"
>${previewText}</span
>
${this._context.showIcons$.value
? html`<span class=${iconClass}>${previewIconMap[block.type]}</span>`
? html`<span class=${iconClass}
>${previewIconMap[block.props.type]}</span
>`
: nothing}`;
}
@@ -126,12 +130,12 @@ export class OutlineBlockPreview extends SignalWatcher(
switch (block.flavour) {
case 'affine:page':
assertType<RootBlockModel>(block);
return block.title.length > 0
return block.props.title.length > 0
? html`<span
data-testid="outline-block-preview-title"
class="${styles.text} ${styles.subtypeStyles.title}"
>
${block.title$.value}
${block.props.title$.value}
</span>`
: nothing;
case 'affine:paragraph':
@@ -144,7 +148,9 @@ export class OutlineBlockPreview extends SignalWatcher(
assertType<BookmarkBlockModel>(block);
return html`
<span class="${styles.text} ${styles.textGeneral}"
>${block.title || block.url || placeholderMap['bookmark']}</span
>${block.props.title ||
block.props.url ||
placeholderMap['bookmark']}</span
>
${showPreviewIcon
? html`<span class=${iconClass}
@@ -156,7 +162,7 @@ export class OutlineBlockPreview extends SignalWatcher(
assertType<CodeBlockModel>(block);
return html`
<span class="${styles.text} ${styles.textGeneral}"
>${block.language ?? placeholderMap['code']}</span
>${block.props.language ?? placeholderMap['code']}</span
>
${showPreviewIcon
? html`<span class=${iconClass}>${previewIconMap['code']}</span>`
@@ -166,8 +172,8 @@ export class OutlineBlockPreview extends SignalWatcher(
assertType<DatabaseBlockModel>(block);
return html`
<span class="${styles.text} ${styles.textGeneral}"
>${block.title.toString().length
? block.title.toString()
>${block.props.title.toString().length
? block.props.title.toString()
: placeholderMap['database']}</span
>
${showPreviewIcon
@@ -178,8 +184,8 @@ export class OutlineBlockPreview extends SignalWatcher(
assertType<ImageBlockModel>(block);
return html`
<span class="${styles.text} ${styles.textGeneral}"
>${block.caption?.length
? block.caption
>${block.props.caption?.length
? block.props.caption
: placeholderMap['image']}</span
>
${showPreviewIcon
@@ -190,8 +196,8 @@ export class OutlineBlockPreview extends SignalWatcher(
assertType<AttachmentBlockModel>(block);
return html`
<span class="${styles.text} ${styles.textGeneral}"
>${block.name?.length
? block.name
>${block.props.name?.length
? block.props.name
: placeholderMap['attachment']}</span
>
${showPreviewIcon

View File

@@ -27,7 +27,7 @@ import type { TemplateResult } from 'lit';
const _16px = { width: '16px', height: '16px' };
const paragraphIconMap: Record<
ParagraphBlockModel['type'],
ParagraphBlockModel['props']['type'],
TemplateResult<1>
> = {
quote: QuoteIcon(_16px),
@@ -54,7 +54,10 @@ export const previewIconMap = {
attachment: AttachmentIcon(_16px),
};
const paragraphPlaceholderMap: Record<ParagraphBlockModel['type'], string> = {
const paragraphPlaceholderMap: Record<
ParagraphBlockModel['props']['type'],
string
> = {
quote: 'Quote',
text: 'Text Block',
h1: 'Heading 1',

View File

@@ -140,13 +140,13 @@ export class MobileOutlineMenu extends SignalWatcher(
let text = '';
if (matchModels(item, [RootBlockModel])) {
className = 'title';
text = item.title$.value.toString();
text = item.props.title$.value.toString();
} else if (
matchModels(item, [ParagraphBlockModel]) &&
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(item.type$.value)
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(item.props.type$.value)
) {
className = item.type$.value;
text = item.text$.value.toString();
className = item.props.type$.value;
text = item.props.text$.value.toString();
} else {
return nothing;
}

View File

@@ -25,7 +25,7 @@ export function getNotesFromDoc(
rootModel.children.forEach(block => {
if (!matchModels(block, [NoteBlockModel])) return;
if (modes.includes(block.displayMode$.value)) {
if (modes.includes(block.props.displayMode$.value)) {
notes.push(block);
}
});
@@ -42,7 +42,7 @@ export function isHeadingBlock(
): block is ParagraphBlockModel {
return (
matchModels(block, [ParagraphBlockModel]) &&
headingKeys.has(block.type$.value)
headingKeys.has(block.props.type$.value)
);
}