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
@@ -74,7 +74,7 @@ const conversionsActionGroup = {
textConversionConfigs.find(
({ flavour, type }) =>
flavour === model.flavour &&
(type ? 'type' in model && type === model.type : true)
(type ? 'type' in model.props && type === model.props.type : true)
) ?? textConversionConfigs[0];
const update = (flavour: string, type?: string) => {
chain
@@ -293,9 +293,9 @@ export function createEdgelessElement(
id = doc.addBlock(
'affine:note',
{
background: current.background,
displayMode: current.displayMode,
edgeless: current.edgeless,
background: current.props.background,
displayMode: current.props.displayMode,
edgeless: current.props.edgeless,
xywh: bound.serialize(),
},
edgeless.model.id
@@ -309,7 +309,7 @@ export function createEdgelessElement(
}
assertType<NoteBlockModel>(note);
doc.updateBlock(note, () => {
note.edgeless.collapse = true;
note.props.edgeless.collapse = true;
});
doc.addBlock('affine:paragraph', {}, note.id);
@@ -154,14 +154,14 @@ export class NoteSlicer extends WidgetComponent<
index: originIndex,
xywh,
background,
children,
displayMode,
} = this._anchorNote;
} = this._anchorNote.props;
const { children } = this._anchorNote;
const {
collapse: _,
collapsedHeight: __,
...restOfEdgeless
} = this._anchorNote.edgeless;
} = this._anchorNote.props.edgeless;
const anchorBlockId = this._noteBlockIds[this._activeSlicerIndex];
if (!anchorBlockId) return;
const sliceIndex = children.findIndex(block => block.id === anchorBlockId);
@@ -1009,7 +1009,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
direction === HandleDirection.BottomRight ||
direction === HandleDirection.BottomLeft
) {
const newScale = element.scale * (bound.w / oldXYWH.w);
const newScale = element.props.scale * (bound.w / oldXYWH.w);
this._scalePercent = `${Math.round(newScale * 100)}%`;
this._scaleDirection = direction;
@@ -1030,11 +1030,11 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
if (!textPortal.checkWidthOverflow(bound.w)) return;
const newRealWidth = clamp(
bound.w / element.scale,
bound.w / element.props.scale,
EDGELESS_TEXT_BLOCK_MIN_WIDTH,
Infinity
);
bound.w = newRealWidth * element.scale;
bound.w = newRealWidth * element.props.scale;
this.gfx.updateElement(element.id, {
xywh: Bound.serialize({
...bound,
@@ -1068,7 +1068,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
) {
const curBound = Bound.deserialize(element.xywh);
let scale = element.scale ?? 1;
let scale = element.props.scale ?? 1;
let width = curBound.w / scale;
let height = curBound.h / scale;
if (this._shiftKey) {
@@ -1123,7 +1123,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
) {
const curBound = Bound.deserialize(element.xywh);
let scale = element.edgeless.scale ?? 1;
let scale = element.props.edgeless.scale ?? 1;
if (this._shiftKey) {
scale = (bound.w / curBound.w) * scale;
this._scalePercent = `${Math.round(scale * 100)}%`;
@@ -1138,14 +1138,14 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
if (bound.h > NOTE_MIN_HEIGHT * scale) {
this.doc.updateBlock(element, () => {
element.edgeless.collapse = true;
element.edgeless.collapsedHeight = bound.h / scale;
element.props.edgeless.collapse = true;
element.props.edgeless.collapsedHeight = bound.h / scale;
});
}
this.gfx.updateElement(element.id, {
edgeless: {
...element.edgeless,
...element.props.edgeless,
scale,
},
xywh: bound.serialize(),
@@ -1160,13 +1160,13 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<
const curBound = Bound.deserialize(element.xywh);
if (isImageBlock(element)) {
const { height } = element;
const { height } = element.props;
if (height) {
this._scalePercent = `${Math.round((bound.h / height) * 100)}%`;
this._scaleDirection = direction;
}
} else {
const cardStyle = (element as BookmarkBlockModel).style;
const cardStyle = (element as BookmarkBlockModel).props.style;
const height = EMBED_CARD_HEIGHT[cardStyle];
this._scalePercent = `${Math.round((bound.h / height) * 100)}%`;
this._scaleDirection = direction;
@@ -158,7 +158,7 @@ export class EdgelessFrameTitleEditor extends WithDisposable(
return html`<div class="frame-title-editor" style=${inlineEditorStyle}>
<rich-text
.yText=${this.frameModel.title.yText}
.yText=${this.frameModel.props.title.yText}
.enableFormat=${false}
.enableAutoScrollHorizontally=${false}
style=${richTextStyle}
@@ -179,8 +179,9 @@ export class EdgelessFrameOrderMenu extends SignalWatcher(
// Legacy compatibility
frameMgr.refreshLegacyFrameOrder();
const before = this._frames[newIndex - 1]?.presentationIndex || null;
const after = this._frames[newIndex]?.presentationIndex || null;
const before =
this._frames[newIndex - 1]?.props.presentationIndex || null;
const after = this._frames[newIndex]?.props.presentationIndex || null;
const frame = this._frames[index];
@@ -223,7 +224,7 @@ export class EdgelessFrameOrderMenu extends SignalWatcher(
(frame, index) => html`
<div class="item draggable" id=${frame.id} index=${index}>
<div class="drag-indicator"></div>
<div class="title">${frame.title.toString()}</div>
<div class="title">${frame.props.title.toString()}</div>
</div>
`
)}
@@ -232,7 +233,7 @@ export class EdgelessFrameOrderMenu extends SignalWatcher(
${frame
? html`<div class="drag-indicator"></div>
<div class="index">${this._curIndex + 1}</div>
<div class="title">${frame.title.toString()}</div>`
<div class="title">${frame.props.title.toString()}</div>`
: nothing}
</div>
</div>
@@ -345,7 +345,7 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
class="edgeless-frame-navigator-title"
@click=${() => this._moveToCurrentFrame()}
>
${frame?.title ?? 'no frame'}
${frame?.props.title ?? 'no frame'}
</span>`}
<span class="edgeless-frame-navigator-count">
@@ -261,7 +261,7 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
block.group === null &&
!(
matchModels(block, [NoteBlockModel]) &&
block.displayMode === NoteDisplayMode.DocOnly
block.props.displayMode === NoteDisplayMode.DocOnly
)
)
.map(block => block.id),
@@ -318,10 +318,10 @@ export class EdgelessRootBlockComponent extends BlockComponent<
const note = this.model.children.find(
(child): child is NoteBlockModel =>
matchModels(child, [NoteBlockModel]) &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
child.props.displayMode !== NoteDisplayMode.EdgelessOnly
);
if (primaryMode !== 'page' || !note || note.edgeless.collapse)
if (primaryMode !== 'page' || !note || note.props.edgeless.collapse)
return false;
const leftPadding = parseInt(
@@ -106,9 +106,7 @@ export function createNewPresentationIndexes(
const { data } = BlockSnapshotSchema.safeParse(block);
return data?.flavour === 'affine:frame';
})
.sort((a, b) =>
EdgelessFrameManager.framePresentationComparator(a.props, b.props)
);
.sort((a, b) => EdgelessFrameManager.framePresentationComparator(a, b));
const frameMgr = edgeless.service.frame;
let before = frameMgr.generatePresentationIndex();
@@ -40,8 +40,8 @@ export class PageKeyboardManager {
if (
matchModels(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
model.props.type.startsWith('h') &&
model.props.collapsed
) {
const component = this.rootComponent.host.view.getBlock(id);
if (!(component instanceof ParagraphBlockComponent)) return;
@@ -238,7 +238,7 @@ export class PageRootBlockComponent extends BlockComponent<
const blocks = this.model.children
.filter(model => {
if (matchModels(model, [NoteBlockModel])) {
if (model.displayMode === NoteDisplayMode.EdgelessOnly)
if (model.props.displayMode === NoteDisplayMode.EdgelessOnly)
return false;
return true;
@@ -332,7 +332,7 @@ export class PageRootBlockComponent extends BlockComponent<
const notes = this.model.children.filter(
(child): child is NoteBlockModel =>
child instanceof NoteBlockModel &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
child.props.displayMode !== NoteDisplayMode.EdgelessOnly
);
// make sure there is a block can be focused
@@ -358,7 +358,7 @@ export class PageRootBlockComponent extends BlockComponent<
if (
!lastBlock ||
!matchModels(lastBlock, [ParagraphBlockModel]) ||
lastBlock.text.length !== 0
lastBlock.props.text.length !== 0
) {
this.std.command.exec(appendParagraphCommand);
}
@@ -436,7 +436,8 @@ export class PageRootBlockComponent extends BlockComponent<
const isNote = matchModels(child, [NoteBlockModel]);
const note = child as NoteBlockModel;
const displayOnEdgeless =
!!note.displayMode && note.displayMode === NoteDisplayMode.EdgelessOnly;
!!note.props.displayMode &&
note.props.displayMode === NoteDisplayMode.EdgelessOnly;
// Should remove deprecated `hidden` property in the future
return !(isNote && displayOnEdgeless);
});
@@ -36,7 +36,8 @@ export class PreviewRootBlockComponent extends BlockComponent {
const isNote = matchModels(child, [NoteBlockModel]);
const note = child as NoteBlockModel;
const displayOnEdgeless =
!!note.displayMode && note.displayMode === NoteDisplayMode.EdgelessOnly;
!!note.props.displayMode &&
note.props.displayMode === NoteDisplayMode.EdgelessOnly;
// Should remove deprecated `hidden` property in the future
return !(isNote && displayOnEdgeless);
});
@@ -85,7 +85,7 @@ export class EdgelessChangeAttachmentButton extends WithDisposable(LitElement) {
override render() {
return join(
[
this.model.style === 'pdf'
this.model.props.style === 'pdf'
? null
: html`
<editor-menu-button
@@ -100,7 +100,7 @@ export class EdgelessChangeAttachmentButton extends WithDisposable(LitElement) {
`}
>
<card-style-panel
.value=${this.model.style}
.value=${this.model.props.style}
.options=${this._getCardStyleOptions}
.onSelect=${this._setCardStyle}
>
@@ -147,7 +147,8 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
return;
}
const { id, url, xywh, style, caption } = this.model;
const { xywh, style, caption } = this.model.props;
const { id, url } = this.model;
let targetFlavour = 'affine:bookmark',
targetStyle = style;
@@ -207,7 +208,8 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
const { flavour, styles } = this._embedOptions;
const { id, url, xywh, style } = this.model;
const { id, url, xywh } = this.model;
const { style } = this.model.props;
const targetStyle = styles.includes(style) ? style : styles[0];
@@ -241,12 +243,12 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
private readonly _copyUrl = () => {
let url!: ReturnType<GenerateDocUrlService['generateDocUrl']>;
if ('url' in this.model) {
url = this.model.url;
if ('url' in this.model.props) {
url = this.model.props.url;
} else if (isInternalEmbedModel(this.model)) {
url = this.std
.getOptional(GenerateDocUrlProvider)
?.generateDocUrl(this.model.pageId, this.model.params);
?.generateDocUrl(this.model.props.pageId, this.model.props.params);
}
if (!url) return;
@@ -263,14 +265,14 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
private _embedOptions: EmbedOptions | null = null;
private readonly _getScale = () => {
if ('scale' in this.model) {
return this.model.scale ?? 1;
if ('scale' in this.model.props) {
return this.model.props.scale ?? 1;
} else if (isEmbedHtmlBlock(this.model)) {
return 1;
}
const bound = Bound.deserialize(this.model.xywh);
return bound.h / EMBED_CARD_HEIGHT[this.model.style];
return bound.h / EMBED_CARD_HEIGHT[this.model.props.style];
};
private readonly _open = ({ openMode }: { openMode?: OpenDocMode } = {}) => {
@@ -343,16 +345,16 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
if (isEmbedHtmlBlock(this.model)) return;
const bound = Bound.deserialize(this.model.xywh);
if ('scale' in this.model) {
const oldScale = this.model.scale ?? 1;
if ('scale' in this.model.props) {
const oldScale = this.model.props.scale ?? 1;
const ratio = scale / oldScale;
bound.w *= ratio;
bound.h *= ratio;
const xywh = bound.serialize();
this.model.doc.updateBlock(this.model, { scale, xywh });
} else {
bound.h = EMBED_CARD_HEIGHT[this.model.style] * scale;
bound.w = EMBED_CARD_WIDTH[this.model.style] * scale;
bound.h = EMBED_CARD_HEIGHT[this.model.props.style] * scale;
bound.w = EMBED_CARD_WIDTH[this.model.props.style] * scale;
const xywh = bound.serialize();
this.model.doc.updateBlock(this.model, { xywh });
}
@@ -456,9 +458,9 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
}
return (
isEmbedLinkedDocBlock(this.model) &&
(referenceToNode(this.model) ||
(referenceToNode(this.model.props) ||
!!this._blockComponent?.closest('affine-embed-synced-doc-block') ||
this.model.pageId === this._doc.id)
this.model.props.pageId === this._doc.id)
);
}
@@ -516,7 +518,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
get _originalDocInfo(): AliasInfo | undefined {
const model = this.model;
const doc = isInternalEmbedModel(model)
? this.std.workspace.getDoc(model.pageId)
? this.std.workspace.getDoc(model.props.pageId)
: null;
if (doc) {
@@ -533,7 +535,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
get _originalDocTitle() {
const model = this.model;
const doc = isInternalEmbedModel(model)
? this.std.workspace.getDoc(model.pageId)
? this.std.workspace.getDoc(model.props.pageId)
: null;
return doc?.meta?.title || 'Untitled';
@@ -582,7 +584,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
type: item.type,
icon: item.icon,
disabled:
this.model.pageId === this._doc.id &&
this.model.props.pageId === this._doc.id &&
item.type === 'open-in-active-view',
action: () => {
if (item.type === 'open-in-center-peek') {
@@ -723,30 +725,30 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
const model = this.model;
const isHtmlBlockModel = isEmbedHtmlBlock(model);
if ('url' in this.model) {
if ('url' in this.model.props) {
this._embedOptions = this.std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(this.model.url);
.getEmbedBlockOptions(this.model.props.url);
}
const buttons = [
this._openMenuButton(),
this._canShowUrlOptions && 'url' in model
this._canShowUrlOptions && 'url' in model.props
? html`
<a
class="affine-link-preview"
href=${model.url}
href=${model.props.url}
rel="noopener noreferrer"
target="_blank"
>
<span>${getHostName(model.url)}</span>
<span>${getHostName(model.props.url)}</span>
</a>
`
: nothing,
// internal embed model
isEmbedLinkedDocBlock(model) && model.title
isEmbedLinkedDocBlock(model) && model.props.title
? html`
<editor-icon-button
class="doc-title"
@@ -34,7 +34,7 @@ function getMostCommonColor(
colorScheme: ColorScheme
): string {
const colors = countBy(elements, (ele: FrameBlockModel) =>
resolveColor(ele.background, colorScheme)
resolveColor(ele.props.background, colorScheme)
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max ? (max[0] as string) : 'transparent';
@@ -73,7 +73,7 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
const notes = rootModel.children.filter(
model =>
matchModels(model, [NoteBlockModel]) &&
model.displayMode !== NoteDisplayMode.EdgelessOnly
model.props.displayMode !== NoteDisplayMode.EdgelessOnly
);
const lastNote = notes[notes.length - 1];
const referenceFrame = this.frames[0];
@@ -180,7 +180,7 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
.pick=${this.pickColor}
.color=${background}
.theme=${colorScheme}
.originalColor=${this.frames[0].background}
.originalColor=${this.frames[0].props.background}
.enableCustomColor=${enableCustomColor}
>
</edgeless-color-picker-button>
@@ -25,7 +25,7 @@ export class EdgelessChangeGroupButton extends WithDisposable(LitElement) {
const notes = rootModel.children.filter(
model =>
matchModels(model, [NoteBlockModel]) &&
model.displayMode !== NoteDisplayMode.EdgelessOnly
model.props.displayMode !== NoteDisplayMode.EdgelessOnly
);
const lastNote = notes[notes.length - 1];
const referenceGroup = this.groups[0];
@@ -73,7 +73,7 @@ function getMostCommonBackground(
colorScheme: ColorScheme
): string {
const colors = countBy(elements, (ele: NoteBlockModel) =>
resolveColor(ele.background, colorScheme)
resolveColor(ele.props.background, colorScheme)
);
const max = maxBy(Object.entries(colors), ([_k, count]) => count);
return max
@@ -90,9 +90,9 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
this.notes.forEach(note => {
const props = {
edgeless: {
...note.edgeless,
...note.props.edgeless,
style: {
...note.edgeless.style,
...note.props.edgeless.style,
borderRadius,
},
},
@@ -105,13 +105,13 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
this.notes.forEach(note => {
this.doc.updateBlock(note, () => {
const bound = Bound.deserialize(note.xywh);
const oldScale = note.edgeless.scale ?? 1;
const oldScale = note.props.edgeless.scale ?? 1;
const ratio = scale / oldScale;
bound.w *= ratio;
bound.h *= ratio;
const xywh = bound.serialize();
note.xywh = xywh;
note.edgeless.scale = scale;
note.props.edgeless.scale = scale;
});
});
};
@@ -155,19 +155,19 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
private _setCollapse() {
this.doc.captureSync();
this.notes.forEach(note => {
const { collapse, collapsedHeight } = note.edgeless;
const { collapse, collapsedHeight } = note.props.edgeless;
if (collapse) {
this.doc.updateBlock(note, () => {
note.edgeless.collapse = false;
note.props.edgeless.collapse = false;
});
} else if (collapsedHeight) {
const { xywh, edgeless } = note;
const { xywh, edgeless } = note.props;
const bound = Bound.deserialize(xywh);
bound.h = collapsedHeight * (edgeless.scale ?? 1);
this.doc.updateBlock(note, () => {
note.edgeless.collapse = true;
note.xywh = bound.serialize();
note.props.edgeless.collapse = true;
note.props.xywh = bound.serialize();
});
}
});
@@ -175,7 +175,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
}
private _setDisplayMode(note: NoteBlockModel, newMode: NoteDisplayMode) {
const oldMode = note.displayMode;
const oldMode = note.props.displayMode;
this.edgeless.std.command.exec(changeNoteDisplayMode, {
noteId: note.id,
mode: newMode,
@@ -268,9 +268,9 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
this.notes.forEach(note => {
const props = {
edgeless: {
...note.edgeless,
...note.props.edgeless,
style: {
...note.edgeless.style,
...note.props.edgeless.style,
shadowType,
},
},
@@ -283,9 +283,9 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
this.notes.forEach(note => {
const props = {
edgeless: {
...note.edgeless,
...note.props.edgeless,
style: {
...note.edgeless.style,
...note.props.edgeless.style,
borderStyle,
},
},
@@ -298,9 +298,9 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
this.notes.forEach(note => {
const props = {
edgeless: {
...note.edgeless,
...note.props.edgeless,
style: {
...note.edgeless.style,
...note.props.edgeless.style,
borderSize,
},
},
@@ -322,7 +322,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
override render() {
const len = this.notes.length;
const note = this.notes[0];
const { edgeless, displayMode } = note;
const { edgeless, displayMode } = note.props;
const { shadowType, borderRadius, borderSize, borderStyle } =
edgeless.style;
const colorScheme = this.edgeless.surface.renderer.getColorScheme();
@@ -406,7 +406,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
.colorPanelClass=${'small'}
.theme=${colorScheme}
.palettes=${DefaultTheme.NoteBackgroundColorPalettes}
.originalColor=${note.background}
.originalColor=${note.props.background}
.enableCustomColor=${enableCustomColor}
>
</edgeless-color-picker-button>
@@ -153,7 +153,7 @@ export const openGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
if (!linkedDocBlock) return;
const disabled = linkedDocBlock.pageId === ctx.doc.id;
const disabled = linkedDocBlock.props.pageId === ctx.doc.id;
return {
action: () => {
@@ -86,7 +86,7 @@ export function createLinkedDocFromEdgelessElements(
});
} else {
if (isFrameBlock(model)) {
mapFrameIds(blockProps as unknown as FrameBlockModel, ids);
mapFrameIds(blockProps as FrameBlockModel['props'], ids);
}
newId = linkedDoc.addBlock(model.flavour, blockProps, surfaceId);
@@ -584,7 +584,7 @@ const documentGroupFrameToolGroup: DynamicKeyboardToolPanelGroup = ({
.map(block => block.model) as FrameBlockModel[];
const frameItems = frameModels.map<KeyboardToolbarActionItem>(frameModel => ({
name: 'Frame: ' + frameModel.title.toString(),
name: 'Frame: ' + frameModel.props.title.toString(),
icon: FrameIcon(),
action: ({ std }) => {
std.command