feat(editor): type safe draft model and transformer (#10486)

This commit is contained in:
Saul-Mirone
2025-02-27 09:19:49 +00:00
parent 272d41e32d
commit 4c736bc190
15 changed files with 125 additions and 48 deletions

View File

@@ -304,11 +304,12 @@ export function getDocContentWithMaxLength(doc: Store, maxlength = 500) {
export function getTitleFromSelectedModels(selectedModels: DraftModel[]) {
const firstBlock = selectedModels[0];
if (
matchModels(firstBlock, [ParagraphBlockModel]) &&
firstBlock.type.startsWith('h')
) {
return firstBlock.text.toString();
const isParagraph = (
model: DraftModel
): model is DraftModel<ParagraphBlockModel> =>
model.flavour === 'affine:paragraph';
if (isParagraph(firstBlock) && firstBlock.type.startsWith('h')) {
return firstBlock.text?.toString();
}
return undefined;
}
@@ -394,7 +395,7 @@ export async function convertSelectedBlocksToLinkedDoc(
'before'
);
// delete selected elements
models.forEach(model => doc.deleteBlock(model));
models.forEach(model => doc.deleteBlock(model.id));
return linkedDoc;
}

View File

@@ -9,6 +9,7 @@ import {
getSelectedModelsCommand,
} from '@blocksuite/affine-shared/commands';
import type { BlockStdScope } from '@blocksuite/block-std';
import { toDraftModel } from '@blocksuite/store';
export interface QuickActionConfig {
id: string;
@@ -45,7 +46,9 @@ export const quickActionConfig: QuickActionConfig[] = [
std.selection.clear();
const doc = std.store;
const autofill = getTitleFromSelectedModels(selectedModels);
const autofill = getTitleFromSelectedModels(
selectedModels.map(toDraftModel)
);
promptDocTitle(std, autofill)
.then(title => {
if (title === null) return;

View File

@@ -17,6 +17,7 @@ import {
type UIEventHandler,
} from '@blocksuite/block-std';
import { IS_MAC, IS_WINDOWS } from '@blocksuite/global/env';
import { toDraftModel } from '@blocksuite/store';
export class PageKeyboardManager {
private readonly _handleDelete: UIEventHandler = ctx => {
@@ -143,7 +144,9 @@ export class PageKeyboardManager {
}
const doc = rootComponent.host.doc;
const autofill = getTitleFromSelectedModels(selectedModels);
const autofill = getTitleFromSelectedModels(
selectedModels.map(toDraftModel)
);
promptDocTitle(rootComponent.std, autofill)
.then(title => {
if (title === null) return;

View File

@@ -64,7 +64,7 @@ import type {
import { tableViewMeta } from '@blocksuite/data-view/view-presets';
import { assertExists } from '@blocksuite/global/utils';
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
import { Slice } from '@blocksuite/store';
import { Slice, toDraftModel } from '@blocksuite/store';
import { html, type TemplateResult } from 'lit';
import { FormatBarContext } from './context.js';
@@ -230,7 +230,9 @@ export function toolbarDefaultConfig(toolbar: AffineFormatBarWidget) {
host.selection.clear();
const doc = host.doc;
const autofill = getTitleFromSelectedModels(selectedModels);
const autofill = getTitleFromSelectedModels(
selectedModels.map(toDraftModel)
);
promptDocTitle(std, autofill)
.then(async title => {
if (title === null) return;

View File

@@ -1,4 +1,4 @@
import { RootBlockModel } from '@blocksuite/affine-model';
import type { RootBlockModel } from '@blocksuite/affine-model';
import {
type BlockStdScope,
type EditorHost,
@@ -12,7 +12,9 @@ import type {
TransformerSlots,
} from '@blocksuite/store';
import { matchModels } from '../../utils';
const isRootDraftModel = (
model: DraftModel
): model is DraftModel<RootBlockModel> => model.flavour === 'affine:root';
const handlePoint = (
point: TextRangePoint,
@@ -20,7 +22,7 @@ const handlePoint = (
model: DraftModel
) => {
const { index, length } = point;
if (matchModels(model, [RootBlockModel])) {
if (isRootDraftModel(model)) {
if (length === 0) return;
(snapshot.props.title as Record<string, unknown>).delta =
model.title.sliceToDelta(index, length + index);

View File

@@ -11,7 +11,7 @@ type ModelList<T> =
export function matchModels<
const Model extends ConstructorType<BlockModel>[],
U extends ModelList<Model>[number] = ModelList<Model>[number],
>(model: unknown, expected: Model): model is U {
>(model: BlockModel | null, expected: Model): model is U {
return (
!!model && expected.some(expectedModel => model instanceof expectedModel)
);

View File

@@ -1139,7 +1139,7 @@ export class DragEventWatcher {
block.flavour === 'affine:bookmark' ||
block.flavour.startsWith('affine:embed-'))
) {
store.updateBlock(block as BlockModel, {
store.updateBlock(block.id, {
xywh: content[idx].props.xywh,
style: content[idx].props.style,
});
@@ -1164,7 +1164,7 @@ export class DragEventWatcher {
block.flavour === 'affine:attachment' ||
block.flavour.startsWith('affine:embed-')
) {
store.updateBlock(block as BlockModel, {
store.updateBlock(block.id, {
xywh: content[idx].props.xywh,
style: content[idx].props.style,
});