refactor(editor): remove global types in model (#10082)

Closes: [BS-2249](https://linear.app/affine-design/issue/BS-2249/remove-global-types-in-model)

```ts
// before
matchFlavours(model, ['affine:page']);
// after
matchFlavours(model, [PageBlockModel]);
```
This commit is contained in:
Saul-Mirone
2025-02-11 08:18:57 +00:00
parent 64bb6c5a71
commit 652865c7cf
97 changed files with 492 additions and 323 deletions
@@ -1,3 +1,4 @@
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { FileDropConfigExtension } from '@blocksuite/affine-components/drop-indicator';
import { AttachmentBlockSchema } from '@blocksuite/affine-model';
import {
@@ -23,7 +24,7 @@ export const AttachmentDropOption = FileDropConfigExtension({
const maxFileSize = std.store.get(FileSizeLimitService).maxFileSize;
if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
if (targetModel && !matchFlavours(targetModel, [SurfaceBlockModel])) {
addSiblingAttachmentBlocks(
std.host,
attachmentFiles,
@@ -1,6 +1,9 @@
import type {
DatabaseBlockModel,
RootBlockModel,
import {
CodeBlockModel,
type DatabaseBlockModel,
ListBlockModel,
ParagraphBlockModel,
type RootBlockModel,
} from '@blocksuite/affine-model';
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
@@ -73,9 +76,9 @@ export class NoteRenderer
.find(child => child.flavour === 'affine:note')
?.children.find(block =>
matchFlavours(block, [
'affine:paragraph',
'affine:list',
'affine:code',
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
);
}
@@ -1,6 +1,10 @@
import { TextUtils } from '@blocksuite/affine-block-surface';
import { formatBlockCommand } from '@blocksuite/affine-components/rich-text';
import type { EdgelessTextBlockModel } from '@blocksuite/affine-model';
import {
type EdgelessTextBlockModel,
ListBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/block-std';
@@ -158,7 +162,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
const firstChild = this.model.firstChild();
if (
!firstChild ||
!matchFlavours(firstChild, ['affine:list', 'affine:paragraph'])
!matchFlavours(firstChild, [ListBlockModel, ParagraphBlockModel])
) {
newParagraphId = this.doc.addBlock(
'affine:paragraph',
@@ -171,7 +175,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
const lastChild = this.model.lastChild();
if (
!lastChild ||
!matchFlavours(lastChild, ['affine:list', 'affine:paragraph'])
!matchFlavours(lastChild, [ListBlockModel, ParagraphBlockModel])
) {
newParagraphId = this.doc.addBlock(
'affine:paragraph',
@@ -1,8 +1,11 @@
import { getSurfaceBlock } from '@blocksuite/affine-block-surface';
import {
type DocMode,
type ImageBlockModel,
ImageBlockModel,
ListBlockModel,
NoteBlockModel,
NoteDisplayMode,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { EMBED_CARD_HEIGHT } from '@blocksuite/affine-shared/consts';
import { NotificationProvider } from '@blocksuite/affine-shared/services';
@@ -68,7 +71,7 @@ async function renderPageAsBanner(card: EmbedSyncedDocCard) {
}
const target = notes.flatMap(note =>
note.children.filter(child => matchFlavours(child, ['affine:image']))
note.children.filter(child => matchFlavours(child, [ImageBlockModel]))
)[0];
if (target) {
@@ -135,9 +138,7 @@ async function renderNoteContent(
const cardStyle = card.model.style;
const isHorizontal = cardStyle === 'horizontal';
const allowFlavours: (keyof BlockSuite.BlockModels)[] = isHorizontal
? []
: ['affine:image'];
const allowFlavours = isHorizontal ? [] : [ImageBlockModel];
const noteChildren = notes.flatMap(note =>
note.children.filter(model => {
@@ -214,7 +215,7 @@ async function renderNoteContent(
}
function filterTextModel(model: BlockModel) {
if (matchFlavours(model, ['affine:paragraph', 'affine:list'])) {
if (matchFlavours(model, [ParagraphBlockModel, ListBlockModel])) {
return !!model.text?.toString().length;
}
return false;
@@ -223,7 +224,7 @@ function filterTextModel(model: BlockModel) {
export function getNotesFromDoc(doc: Store) {
const notes = doc.root?.children.filter(
child =>
matchFlavours(child, ['affine:note']) &&
matchFlavours(child, [NoteBlockModel]) &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
);
@@ -304,7 +305,7 @@ export function getDocContentWithMaxLength(doc: Store, maxlength = 500) {
export function getTitleFromSelectedModels(selectedModels: DraftModel[]) {
const firstBlock = selectedModels[0];
if (
matchFlavours(firstBlock, ['affine:paragraph']) &&
matchFlavours(firstBlock, [ParagraphBlockModel]) &&
firstBlock.type.startsWith('h')
) {
return firstBlock.text.toString();
@@ -1,3 +1,4 @@
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { isPeekable, Peekable } from '@blocksuite/affine-components/peek';
import { RefNodeSlotsProvider } from '@blocksuite/affine-components/rich-text';
import type {
@@ -373,7 +374,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
const isLoading = this._loading;
const isError = this.isError;
const isEmpty = this._isDocEmpty() && this.isBannerEmpty;
const inCanvas = matchFlavours(this.model.parent, ['affine:surface']);
const inCanvas = matchFlavours(this.model.parent, [SurfaceBlockModel]);
const cardClassMap = classMap({
loading: isLoading,
@@ -1,3 +1,4 @@
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { FileDropConfigExtension } from '@blocksuite/affine-components/drop-indicator';
import { ImageBlockSchema, MAX_IMAGE_WIDTH } from '@blocksuite/affine-model';
import {
@@ -25,7 +26,7 @@ export const ImageDropOption = FileDropConfigExtension({
const maxFileSize = std.store.get(FileSizeLimitService).maxFileSize;
if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
if (targetModel && !matchFlavours(targetModel, [SurfaceBlockModel])) {
addSiblingImageBlock(
std.host,
imageFiles,
@@ -1,3 +1,4 @@
import { ListBlockModel } from '@blocksuite/affine-model';
import type { IndentContext } from '@blocksuite/affine-shared/types';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { type Command, TextSelection } from '@blocksuite/block-std';
@@ -53,7 +54,7 @@ export const canDedentListCommand: Command<
* ccc
*/
const model = store.getBlock(blockId)?.model;
if (!model || !matchFlavours(model, ['affine:list'])) {
if (!model || !matchFlavours(model, [ListBlockModel])) {
return;
}
/**
@@ -1,3 +1,4 @@
import { ListBlockModel, ParagraphBlockModel } from '@blocksuite/affine-model';
import type { IndentContext } from '@blocksuite/affine-shared/types';
import {
getNearestHeadingBefore,
@@ -54,7 +55,7 @@ export const canIndentListCommand: Command<
* ccc
*/
const model = store.getBlock(blockId)?.model;
if (!model || !matchFlavours(model, ['affine:list'])) {
if (!model || !matchFlavours(model, [ListBlockModel])) {
return;
}
const schema = std.store.schema;
@@ -125,7 +126,7 @@ export const indentListCommand: Command<{
const nearestHeading = getNearestHeadingBefore(model);
if (
nearestHeading &&
matchFlavours(nearestHeading, ['affine:paragraph']) &&
matchFlavours(nearestHeading, [ParagraphBlockModel]) &&
nearestHeading.collapsed
) {
store.updateBlock(nearestHeading, {
@@ -1,4 +1,5 @@
import { focusTextModel } from '@blocksuite/affine-components/rich-text';
import { ListBlockModel } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { Command } from '@blocksuite/block-std';
@@ -16,7 +17,7 @@ export const listToParagraphCommand: Command<
const doc = std.store;
const model = doc.getBlock(id)?.model;
if (!model || !matchFlavours(model, ['affine:list'])) return false;
if (!model || !matchFlavours(model, [ListBlockModel])) return false;
const parent = doc.getParent(model);
if (!parent) return false;
@@ -1,4 +1,5 @@
import { focusTextModel } from '@blocksuite/affine-components/rich-text';
import { ListBlockModel } from '@blocksuite/affine-model';
import {
getNextContinuousNumberedLists,
matchFlavours,
@@ -17,7 +18,7 @@ export const splitListCommand: Command<{
const doc = host.doc;
const model = doc.getBlock(blockId)?.model;
if (!model || !matchFlavours(model, ['affine:list'])) {
if (!model || !matchFlavours(model, [ListBlockModel])) {
console.error(`block ${blockId} is not a list block`);
return;
}
@@ -1,4 +1,4 @@
import type { ListBlockModel } from '@blocksuite/affine-model';
import { ListBlockModel } from '@blocksuite/affine-model';
import {
getNextContinuousNumberedLists,
matchFlavours,
@@ -22,7 +22,7 @@ export function correctNumberedListsOrderToPrev(
if (!model) return;
if (
!matchFlavours(model, ['affine:list']) ||
!matchFlavours(model, [ListBlockModel]) ||
model.type$.value !== 'numbered'
) {
return;
@@ -33,7 +33,7 @@ export function correctNumberedListsOrderToPrev(
const previousSibling = doc.getPrev(model);
if (
previousSibling &&
matchFlavours(previousSibling, ['affine:list']) &&
matchFlavours(previousSibling, [ListBlockModel]) &&
previousSibling.type === 'numbered'
) {
if (!previousSibling.order) previousSibling.order = 1;
@@ -1,3 +1,4 @@
import { ListBlockModel } from '@blocksuite/affine-model';
import {
getNextContentBlock,
matchFlavours,
@@ -25,7 +26,7 @@ export function forwardDelete(std: BlockStdScope): true | undefined {
const isCollapsed = text.isCollapsed();
const doc = std.store;
const model = doc.getBlock(text.from.blockId)?.model;
if (!model || !matchFlavours(model, ['affine:list'])) return;
if (!model || !matchFlavours(model, [ListBlockModel])) return;
const isEnd = isCollapsed && text.from.index === model.text.length;
if (!isEnd) return;
// Has children in list
@@ -3,6 +3,11 @@ import {
focusTextModel,
onModelTextUpdated,
} from '@blocksuite/affine-components/rich-text';
import {
CodeBlockModel,
ListBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import {
getBlockSelectionsCommand,
getSelectedBlocksCommand,
@@ -196,9 +201,9 @@ export const updateBlockType: Command<
blockModels.forEach(model => {
if (
!matchFlavours(model, [
'affine:paragraph',
'affine:list',
'affine:code',
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
) {
return;
@@ -1,4 +1,4 @@
import { NoteDisplayMode } from '@blocksuite/affine-model';
import { NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { Command } from '@blocksuite/block-std';
@@ -10,7 +10,7 @@ export const changeNoteDisplayMode: Command<{
const { std, noteId, mode, stopCapture } = ctx;
const noteBlockModel = std.store.getBlock(noteId)?.model;
if (!noteBlockModel || !matchFlavours(noteBlockModel, ['affine:note']))
if (!noteBlockModel || !matchFlavours(noteBlockModel, [NoteBlockModel]))
return;
const currentMode = noteBlockModel.displayMode;
@@ -27,7 +27,7 @@ export const changeNoteDisplayMode: Command<{
const parent = std.store.getParent(noteBlockModel);
if (parent) {
const notes = parent.children.filter(child =>
matchFlavours(child, ['affine:note'])
matchFlavours(child, [NoteBlockModel])
);
const firstEdgelessOnlyNote = notes.find(
note => note.displayMode === NoteDisplayMode.EdgelessOnly
@@ -1,3 +1,4 @@
import { NoteBlockModel } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { Command } from '@blocksuite/block-std';
@@ -20,7 +21,7 @@ export const dedentBlockToRoot: Command<{
let parent = store.getParent(model);
let changed = false;
while (parent && !matchFlavours(parent, ['affine:note'])) {
while (parent && !matchFlavours(parent, [NoteBlockModel])) {
if (!changed) {
if (stopCapture) store.captureSync();
changed = true;
@@ -1,3 +1,4 @@
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import {
calculateCollapsedSiblings,
matchFlavours,
@@ -45,7 +46,7 @@ export const dedentBlock: Command<{
if (stopCapture) store.captureSync();
if (
matchFlavours(model, ['affine:paragraph']) &&
matchFlavours(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
) {
@@ -1,3 +1,4 @@
import { NoteBlockModel } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { type Command, TextSelection } from '@blocksuite/block-std';
@@ -30,7 +31,7 @@ export const dedentBlocksToRoot: Command<{
for (let i = blockIds.length - 1; i >= 0; i--) {
const model = blockIds[i];
const parent = store.getParent(model);
if (parent && !matchFlavours(parent, ['affine:note'])) {
if (parent && !matchFlavours(parent, [NoteBlockModel])) {
std.command.exec(dedentBlockToRoot, {
blockId: model,
stopCapture: false,
@@ -1,3 +1,4 @@
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import {
calculateCollapsedSiblings,
matchFlavours,
@@ -57,7 +58,7 @@ export const dedentBlocks: Command<{
const model = store.getBlock(id)?.model;
if (!model) return;
if (
matchFlavours(model, ['affine:paragraph']) &&
matchFlavours(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
) {
@@ -1,4 +1,4 @@
import type { ListBlockModel } from '@blocksuite/affine-model';
import { ListBlockModel, ParagraphBlockModel } from '@blocksuite/affine-model';
import {
calculateCollapsedSiblings,
matchFlavours,
@@ -50,7 +50,7 @@ export const indentBlock: Command<{
if (stopCapture) store.captureSync();
if (
matchFlavours(model, ['affine:paragraph']) &&
matchFlavours(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
) {
@@ -62,12 +62,12 @@ export const indentBlock: Command<{
// update collapsed state of affine list
if (
matchFlavours(previousSibling, ['affine:list']) &&
matchFlavours(previousSibling, [ListBlockModel]) &&
previousSibling.collapsed
) {
store.updateBlock(previousSibling, {
collapsed: false,
} as Partial<ListBlockModel>);
});
}
return next();
@@ -1,3 +1,4 @@
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import {
calculateCollapsedSiblings,
getNearestHeadingBefore,
@@ -58,7 +59,7 @@ export const indentBlocks: Command<{
const model = store.getBlock(id)?.model;
if (!model) return;
if (
matchFlavours(model, ['affine:paragraph']) &&
matchFlavours(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
) {
@@ -83,12 +84,10 @@ export const indentBlocks: Command<{
const nearestHeading = getNearestHeadingBefore(firstModel);
if (
nearestHeading &&
matchFlavours(nearestHeading, ['affine:paragraph']) &&
matchFlavours(nearestHeading, [ParagraphBlockModel]) &&
nearestHeading.collapsed
) {
store.updateBlock(nearestHeading, {
collapsed: false,
});
store.updateBlock(nearestHeading, { collapsed: false });
}
}
@@ -106,12 +105,10 @@ export const indentBlocks: Command<{
const nearestHeading = getNearestHeadingBefore(firstModel);
if (
nearestHeading &&
matchFlavours(nearestHeading, ['affine:paragraph']) &&
matchFlavours(nearestHeading, [ParagraphBlockModel]) &&
nearestHeading.collapsed
) {
store.updateBlock(nearestHeading, {
collapsed: false,
});
store.updateBlock(nearestHeading, { collapsed: false });
}
}
@@ -1,6 +1,8 @@
import {
DefaultTheme,
ListBlockModel,
NoteBlockModel,
ParagraphBlockModel,
StrokeStyle,
} from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
@@ -98,10 +100,10 @@ export class EdgelessNoteBackground extends SignalWatcher(
if (
(!nearestModel.text ||
!matchFlavours(nearestModel, ['affine:paragraph', 'affine:list'])) &&
!matchFlavours(nearestModel, [ParagraphBlockModel, ListBlockModel])) &&
(!siblingModel ||
!siblingModel.text ||
!matchFlavours(siblingModel, ['affine:paragraph', 'affine:list']))
!matchFlavours(siblingModel, [ParagraphBlockModel, ListBlockModel]))
) {
const [pId] = this.doc.addSiblingBlocks(
nearestModel,
@@ -1,5 +1,11 @@
import { textConversionConfigs } from '@blocksuite/affine-components/rich-text';
import { NoteBlockSchema } from '@blocksuite/affine-model';
import {
CodeBlockModel,
ListBlockModel,
NoteBlockModel,
NoteBlockSchema,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import {
getBlockSelectionsCommand,
getNextBlockCommand,
@@ -149,7 +155,7 @@ export class NoteBlockService extends BlockService {
const doc = this._std.store;
let parent = doc.getBlock(blockId)?.model ?? null;
while (parent) {
if (matchFlavours(parent, [NoteBlockSchema.model.flavour])) {
if (matchFlavours(parent, [NoteBlockModel])) {
return parent;
}
parent = doc.getParent(parent);
@@ -189,9 +195,9 @@ export class NoteBlockService extends BlockService {
if (
!matchFlavours(nextBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
) {
this._std.command.exec(selectBlock, {
@@ -225,9 +231,9 @@ export class NoteBlockService extends BlockService {
event.preventDefault();
if (
matchFlavours(nextBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
) {
this._std.command.exec(focusBlockStart, {
@@ -279,9 +285,9 @@ export class NoteBlockService extends BlockService {
if (
!matchFlavours(prevBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
) {
this._std.command.exec(selectBlock, {
@@ -313,9 +319,9 @@ export class NoteBlockService extends BlockService {
if (
matchFlavours(prevBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
) {
event.preventDefault();
+2 -2
View File
@@ -1,4 +1,4 @@
import { type NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model';
import { NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
@@ -11,7 +11,7 @@ export function isPageBlock(std: BlockStdScope, note: NoteBlockModel) {
std.get(FeatureFlagService).getFlag('enable_page_block') &&
note.parent?.children.find(
child =>
matchFlavours(child, ['affine:note']) &&
matchFlavours(child, [NoteBlockModel]) &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
) === note
);
@@ -1,3 +1,4 @@
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import type { IndentContext } from '@blocksuite/affine-shared/types';
import {
calculateCollapsedSiblings,
@@ -34,7 +35,7 @@ export const canDedentParagraphCommand: Command<
}
const model = store.getBlock(blockId)?.model;
if (!model || !matchFlavours(model, ['affine:paragraph'])) {
if (!model || !matchFlavours(model, [ParagraphBlockModel])) {
return;
}
@@ -88,7 +89,7 @@ export const dedentParagraphCommand: Command<{
store.captureSync();
if (
matchFlavours(model, ['affine:paragraph']) &&
matchFlavours(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
) {
@@ -1,4 +1,4 @@
import type { ListBlockModel } from '@blocksuite/affine-model';
import { ListBlockModel, ParagraphBlockModel } from '@blocksuite/affine-model';
import type { IndentContext } from '@blocksuite/affine-shared/types';
import {
calculateCollapsedSiblings,
@@ -37,7 +37,7 @@ export const canIndentParagraphCommand: Command<
}
const model = std.store.getBlock(blockId)?.model;
if (!model || !matchFlavours(model, ['affine:paragraph'])) {
if (!model || !matchFlavours(model, [ParagraphBlockModel])) {
return;
}
@@ -95,7 +95,7 @@ export const indentParagraphCommand: Command<{
const nearestHeading = getNearestHeadingBefore(model);
if (
nearestHeading &&
matchFlavours(nearestHeading, ['affine:paragraph']) &&
matchFlavours(nearestHeading, [ParagraphBlockModel]) &&
nearestHeading.collapsed
) {
store.updateBlock(nearestHeading, {
@@ -105,7 +105,7 @@ export const indentParagraphCommand: Command<{
}
if (
matchFlavours(model, ['affine:paragraph']) &&
matchFlavours(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
) {
@@ -124,7 +124,7 @@ export const indentParagraphCommand: Command<{
const nearestHeading = getNearestHeadingBefore(model);
if (
nearestHeading &&
matchFlavours(nearestHeading, ['affine:paragraph']) &&
matchFlavours(nearestHeading, [ParagraphBlockModel]) &&
nearestHeading.collapsed
) {
store.updateBlock(nearestHeading, {
@@ -135,12 +135,12 @@ export const indentParagraphCommand: Command<{
// update collapsed state of affine list
if (
matchFlavours(previousSibling, ['affine:list']) &&
matchFlavours(previousSibling, [ListBlockModel]) &&
previousSibling.collapsed
) {
store.updateBlock(previousSibling, {
collapsed: false,
} as Partial<ListBlockModel>);
});
}
const textSelection = selection.find(TextSelection);
@@ -2,6 +2,7 @@ import {
focusTextModel,
getInlineEditorByModel,
} from '@blocksuite/affine-components/rich-text';
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { type Command, TextSelection } from '@blocksuite/block-std';
@@ -23,7 +24,7 @@ export const splitParagraphCommand: Command<
if (!blockId) return;
const model = store.getBlock(blockId)?.model;
if (!model || !matchFlavours(model, ['affine:paragraph'])) return;
if (!model || !matchFlavours(model, [ParagraphBlockModel])) return;
const inlineEditor = getInlineEditorByModel(host, model);
const range = inlineEditor?.getInlineRange();
@@ -4,7 +4,10 @@ import {
markdownInput,
textKeymap,
} from '@blocksuite/affine-components/rich-text';
import { ParagraphBlockSchema } from '@blocksuite/affine-model';
import {
ParagraphBlockModel,
ParagraphBlockSchema,
} from '@blocksuite/affine-model';
import {
calculateCollapsedSiblings,
matchFlavours,
@@ -37,7 +40,7 @@ export const ParagraphKeymapExtension = KeymapExtension(
const { store } = std;
const model = store.getBlock(text.from.blockId)?.model;
if (!model || !matchFlavours(model, ['affine:paragraph'])) return;
if (!model || !matchFlavours(model, [ParagraphBlockModel])) return;
const event = ctx.get('keyboardState').raw;
event.preventDefault();
@@ -68,7 +71,7 @@ export const ParagraphKeymapExtension = KeymapExtension(
const text = std.selection.find(TextSelection);
if (!text) return;
const model = store.getBlock(text.from.blockId)?.model;
if (!model || !matchFlavours(model, ['affine:paragraph'])) return;
if (!model || !matchFlavours(model, [ParagraphBlockModel])) return;
const inlineEditor = getInlineEditorByModel(
std.host,
text.from.blockId
@@ -95,7 +98,7 @@ export const ParagraphKeymapExtension = KeymapExtension(
const text = std.selection.find(TextSelection);
if (!text) return;
const model = store.getBlock(text.from.blockId)?.model;
if (!model || !matchFlavours(model, ['affine:paragraph'])) return;
if (!model || !matchFlavours(model, [ParagraphBlockModel])) return;
const inlineEditor = getInlineEditorByModel(
std.host,
text.from.blockId
@@ -1,4 +1,14 @@
import { EMBED_BLOCK_FLAVOUR_LIST } from '@blocksuite/affine-shared/consts';
import {
AttachmentBlockModel,
BookmarkBlockModel,
CodeBlockModel,
DatabaseBlockModel,
DividerBlockModel,
ImageBlockModel,
ListBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { EMBED_BLOCK_MODEL_LIST } from '@blocksuite/affine-shared/consts';
import {
getNextContentBlock,
matchFlavours,
@@ -15,31 +25,32 @@ export function forwardDelete(std: BlockStdScope) {
if (!text) return;
const isCollapsed = text.isCollapsed();
const model = store.getBlock(text.from.blockId)?.model;
if (!model || !matchFlavours(model, ['affine:paragraph'])) return;
if (!model || !matchFlavours(model, [ParagraphBlockModel])) return;
const isEnd = isCollapsed && text.from.index === model.text.length;
if (!isEnd) return;
const parent = store.getParent(model);
if (!parent) return;
const nextSibling = store.getNext(model);
const ignoreForwardDeleteFlavourList: BlockSuite.Flavour[] = [
'affine:attachment',
'affine:bookmark',
'affine:database',
'affine:code',
'affine:image',
'affine:divider',
...EMBED_BLOCK_FLAVOUR_LIST,
];
if (matchFlavours(nextSibling, ignoreForwardDeleteFlavourList)) {
if (
matchFlavours(nextSibling, [
AttachmentBlockModel,
BookmarkBlockModel,
DatabaseBlockModel,
CodeBlockModel,
ImageBlockModel,
DividerBlockModel,
...EMBED_BLOCK_MODEL_LIST,
] as const)
) {
std.selection.setGroup('note', [
std.selection.create(BlockSelection, { blockId: nextSibling.id }),
]);
return true;
}
if (nextSibling?.text) {
if (matchFlavours(nextSibling, [ParagraphBlockModel, ListBlockModel])) {
model.text.join(nextSibling.text);
if (nextSibling.children) {
const parent = store.getParent(nextSibling);
@@ -2,8 +2,19 @@ import {
asyncSetInlineRange,
focusTextModel,
} from '@blocksuite/affine-components/rich-text';
import type { RootBlockModel } from '@blocksuite/affine-model';
import { EMBED_BLOCK_FLAVOUR_LIST } from '@blocksuite/affine-shared/consts';
import {
AttachmentBlockModel,
BookmarkBlockModel,
CodeBlockModel,
DatabaseBlockModel,
DividerBlockModel,
EdgelessTextBlockModel,
ImageBlockModel,
ListBlockModel,
ParagraphBlockModel,
type RootBlockModel,
} from '@blocksuite/affine-model';
import { EMBED_BLOCK_MODEL_LIST } from '@blocksuite/affine-shared/consts';
import type { ExtendedModel } from '@blocksuite/affine-shared/types';
import {
focusTitle,
@@ -33,7 +44,7 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
const parent = doc.getParent(model);
if (!parent) return false;
if (matchFlavours(parent, ['affine:edgeless-text'])) {
if (matchFlavours(parent, [EdgelessTextBlockModel])) {
return true;
}
@@ -42,7 +53,7 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
return handleNoPreviousSibling(editorHost, model);
}
if (matchFlavours(prevBlock, ['affine:paragraph', 'affine:list'])) {
if (matchFlavours(prevBlock, [ParagraphBlockModel, ListBlockModel])) {
const modelIndex = parent.children.indexOf(model);
if (
(modelIndex === -1 || modelIndex === parent.children.length - 1) &&
@@ -64,12 +75,12 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
if (
matchFlavours(prevBlock, [
'affine:attachment',
'affine:bookmark',
'affine:code',
'affine:image',
'affine:divider',
...EMBED_BLOCK_FLAVOUR_LIST,
AttachmentBlockModel,
BookmarkBlockModel,
CodeBlockModel,
ImageBlockModel,
DividerBlockModel,
...EMBED_BLOCK_MODEL_LIST,
])
) {
const selection = editorHost.selection.create(BlockSelection, {
@@ -86,7 +97,7 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) {
return true;
}
if (matchFlavours(parent, ['affine:database'])) {
if (matchFlavours(parent, [DatabaseBlockModel])) {
doc.deleteBlock(model);
focusTextModel(editorHost.std, prevBlock.id, prevBlock.text?.yText.length);
return true;
@@ -104,7 +115,7 @@ function handleNoPreviousSibling(editorHost: EditorHost, model: ExtendedModel) {
// Probably no title, e.g. in edgeless mode
if (!titleEditor) {
if (
matchFlavours(parent, ['affine:edgeless-text']) ||
matchFlavours(parent, [EdgelessTextBlockModel]) ||
model.children.length > 0
) {
doc.deleteBlock(model, {
@@ -1,5 +1,8 @@
import { getSurfaceBlock } from '@blocksuite/affine-block-surface';
import type { SurfaceRefProps } from '@blocksuite/affine-model';
import {
FrameBlockModel,
type SurfaceRefProps,
} from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { Command } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
@@ -38,7 +41,7 @@ export const insertSurfaceRefBlockCommand: Command<
if (element?.type === 'group') {
surfaceRefProps.refFlavour = 'group';
} else if (matchFlavours(blockModel, ['affine:frame'])) {
} else if (matchFlavours(blockModel, [FrameBlockModel])) {
surfaceRefProps.refFlavour = 'frame';
} else {
console.error(`reference not found ${reference}`);
@@ -1,6 +1,9 @@
import {
type NoteBlockModel,
CodeBlockModel,
ListBlockModel,
NoteBlockModel,
NoteDisplayMode,
ParagraphBlockModel,
type RootBlockModel,
} from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
@@ -69,7 +72,7 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
private _getOrCreateFirstPageVisibleNote() {
const note = this._rootModel.children.find(
(child): child is NoteBlockModel =>
matchFlavours(child, ['affine:note']) &&
matchFlavours(child, [NoteBlockModel]) &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
);
if (note) return note;
@@ -102,7 +105,11 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
const note = this._getOrCreateFirstPageVisibleNote();
const firstText = note?.children.find(block =>
matchFlavours(block, ['affine:paragraph', 'affine:list', 'affine:code'])
matchFlavours(block, [
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
);
if (firstText) {
if (this._std) focusTextModel(this._std, firstText.id);
@@ -1,3 +1,4 @@
import { NoteBlockModel } from '@blocksuite/affine-model';
import {
calcDropTarget,
type DropTarget,
@@ -11,6 +12,7 @@ import {
type EditorHost,
LifeCycleWatcher,
} from '@blocksuite/block-std';
import { SurfaceBlockModel } from '@blocksuite/block-std/gfx';
import { createIdentifier } from '@blocksuite/global/di';
import type { IVec } from '@blocksuite/global/utils';
import { Point, throttle } from '@blocksuite/global/utils';
@@ -57,7 +59,7 @@ export class FileDropExtension extends LifeCycleWatcher {
const model = element.model;
const parent = this.std.store.getParent(model);
if (!matchFlavours(parent, ['affine:surface' as BlockSuite.Flavour])) {
if (!matchFlavours(parent, [SurfaceBlockModel])) {
const point = this.point$.value;
target = point && calcDropTarget(point, model, element);
}
@@ -73,7 +75,7 @@ export class FileDropExtension extends LifeCycleWatcher {
if (!rootModel) return null;
let lastNote = rootModel.children[rootModel.children.length - 1];
if (!lastNote || !matchFlavours(lastNote, ['affine:note'])) {
if (!lastNote || !matchFlavours(lastNote, [NoteBlockModel])) {
const newNoteId = this.doc.addBlock('affine:note', {}, rootModel.id);
const newNote = this.doc.getBlock(newNoteId)?.model;
if (!newNote) return null;
@@ -1,3 +1,4 @@
import { DatabaseBlockModel } from '@blocksuite/affine-model';
import {
asyncGetBlockComponent,
getCurrentNativeRange,
@@ -41,7 +42,7 @@ export function getInlineEditorByModel(
typeof model === 'string'
? editorHost.std.store.getBlock(model)?.model
: model;
if (!blockModel || matchFlavours(blockModel, ['affine:database'])) {
if (!blockModel || matchFlavours(blockModel, [DatabaseBlockModel])) {
// Not support database model since it's may be have multiple inline editor instances.
// Support to enter the editing state through the Enter key in the database.
return null;
@@ -1,3 +1,4 @@
import { RootBlockModel } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { type Command, TextSelection } from '@blocksuite/block-std';
import type { Text } from '@blocksuite/store';
@@ -24,7 +25,7 @@ export const deleteTextCommand: Command<{
if (!fromElement) return;
let fromText: Text | undefined;
if (matchFlavours(fromElement.model, ['affine:page'])) {
if (matchFlavours(fromElement.model, [RootBlockModel])) {
fromText = fromElement.model.title;
} else {
fromText = fromElement.model.text;
@@ -1,3 +1,4 @@
import { CodeBlockModel } from '@blocksuite/affine-model';
import { BRACKET_PAIRS } from '@blocksuite/affine-shared/consts';
import {
createDefaultDoc,
@@ -28,7 +29,7 @@ export const bracketKeymap = (
if (!textSelection) return;
const model = doc.getBlock(textSelection.from.blockId)?.model;
if (!model) return;
if (!matchFlavours(model, ['affine:code'])) return;
if (!matchFlavours(model, [CodeBlockModel])) return;
const inlineEditor = getInlineEditorByModel(
std.host,
textSelection.from.blockId
@@ -55,7 +56,7 @@ export const bracketKeymap = (
const model = doc.getBlock(textSelection.from.blockId)?.model;
if (!model) return;
const isCodeBlock = matchFlavours(model, ['affine:code']);
const isCodeBlock = matchFlavours(model, [CodeBlockModel]);
// When selection is collapsed, only trigger auto complete in code block
if (textSelection.isCollapsed() && !isCodeBlock) return;
if (!textSelection.isInSameBlock()) return;
@@ -1,3 +1,7 @@
import {
DividerBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
@@ -12,8 +16,8 @@ export function toDivider(
) {
const { store: doc } = std;
if (
matchFlavours(model, ['affine:divider']) ||
(matchFlavours(model, ['affine:paragraph']) && model.type === 'quote')
matchFlavours(model, [DividerBlockModel]) ||
(matchFlavours(model, [ParagraphBlockModel]) && model.type === 'quote')
) {
return;
}
@@ -1,4 +1,8 @@
import type { ListProps, ListType } from '@blocksuite/affine-model';
import {
type ListProps,
type ListType,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { matchFlavours, toNumberedList } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
@@ -13,7 +17,7 @@ export function toList(
prefix: string,
otherProperties?: Partial<ListProps>
) {
if (!matchFlavours(model, ['affine:paragraph'])) {
if (!matchFlavours(model, [ParagraphBlockModel])) {
return;
}
const { store: doc } = std;
@@ -1,3 +1,4 @@
import { CodeBlockModel, ParagraphBlockModel } from '@blocksuite/affine-model';
import {
isMarkdownPrefix,
matchFlavours,
@@ -31,10 +32,10 @@ export function markdownInput(
const prefixText = getPrefixText(inline);
if (!isMarkdownPrefix(prefixText)) return;
const isParagraph = matchFlavours(model, ['affine:paragraph']);
const isParagraph = matchFlavours(model, [ParagraphBlockModel]);
const isHeading = isParagraph && model.type.startsWith('h');
const isParagraphQuoteBlock = isParagraph && model.type === 'quote';
const isCodeBlock = matchFlavours(model, ['affine:code']);
const isCodeBlock = matchFlavours(model, [CodeBlockModel]);
if (isHeading || isParagraphQuoteBlock || isCodeBlock) return;
const lineInfo = inline.getLine(range.index);
@@ -1,4 +1,7 @@
import type { ParagraphType } from '@blocksuite/affine-model';
import {
ParagraphBlockModel,
type ParagraphType,
} from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
@@ -13,7 +16,7 @@ export function toParagraph(
prefix: string
) {
const { store: doc } = std;
if (!matchFlavours(model, ['affine:paragraph'])) {
if (!matchFlavours(model, [ParagraphBlockModel])) {
const parent = doc.getParent(model);
if (!parent) return;
@@ -33,7 +36,7 @@ export function toParagraph(
return id;
}
if (matchFlavours(model, ['affine:paragraph']) && model.type !== type) {
if (matchFlavours(model, [ParagraphBlockModel]) && model.type !== type) {
beforeConvert(std, model, prefix.length);
doc.updateBlock(model, { type });
@@ -1,3 +1,4 @@
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
@@ -10,7 +11,7 @@ export function toCode(
prefixText: string,
language: string | null
) {
if (matchFlavours(model, ['affine:paragraph']) && model.type === 'quote') {
if (matchFlavours(model, [ParagraphBlockModel]) && model.type === 'quote') {
return;
}
@@ -1,8 +1,4 @@
import {
defineBlockSchema,
type SchemaToModel,
type Text,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
interface CodeBlockProps {
text: Text;
@@ -31,9 +27,12 @@ export const CodeBlockSchema = defineBlockSchema({
],
children: [],
},
toModel: () => new CodeBlockModel(),
});
export type CodeBlockModel = SchemaToModel<typeof CodeBlockSchema>;
export class CodeBlockModel extends BlockModel<CodeBlockProps> {
override text!: Text;
}
declare global {
namespace BlockSuite {
@@ -1,4 +1,4 @@
import { defineBlockSchema, type SchemaToModel } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export const DividerBlockSchema = defineBlockSchema({
flavour: 'affine:divider',
@@ -7,9 +7,14 @@ export const DividerBlockSchema = defineBlockSchema({
role: 'content',
children: [],
},
toModel: () => new DividerBlockModel(),
});
export type DividerBlockModel = SchemaToModel<typeof DividerBlockSchema>;
type Props = {
text: string;
};
export class DividerBlockModel extends BlockModel<Props> {}
declare global {
namespace BlockSuite {
@@ -1,5 +1,5 @@
import type { SchemaToModel, Text } from '@blocksuite/store';
import { defineBlockSchema } from '@blocksuite/store';
import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
// `toggle` type has been deprecated, do not use it
export type ListType = 'bulleted' | 'numbered' | 'todo' | 'toggle';
@@ -35,9 +35,12 @@ export const ListBlockSchema = defineBlockSchema({
'affine:edgeless-text',
],
},
toModel: () => new ListBlockModel(),
});
export type ListBlockModel = SchemaToModel<typeof ListBlockSchema>;
export class ListBlockModel extends BlockModel<ListProps> {
override text!: Text;
}
declare global {
namespace BlockSuite {
@@ -1,4 +1,4 @@
import { defineBlockSchema, type SchemaToModel } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export type SurfaceRefProps = {
reference: string;
@@ -18,9 +18,10 @@ export const SurfaceRefBlockSchema = defineBlockSchema({
role: 'content',
parent: ['affine:note', 'affine:paragraph', 'affine:list'],
},
toModel: () => new SurfaceRefBlockModel(),
});
export type SurfaceRefBlockModel = SchemaToModel<typeof SurfaceRefBlockSchema>;
export class SurfaceRefBlockModel extends BlockModel<SurfaceRefProps> {}
declare global {
namespace BlockSuite {
@@ -1,3 +1,4 @@
import { RootBlockModel } from '@blocksuite/affine-model';
import {
type BlockStdScope,
type EditorHost,
@@ -19,7 +20,7 @@ const handlePoint = (
model: DraftModel
) => {
const { index, length } = point;
if (matchFlavours(model, ['affine:page'])) {
if (matchFlavours(model, [RootBlockModel])) {
if (length === 0) return;
(snapshot.props.title as Record<string, unknown>).delta =
model.title.sliceToDelta(index, length + index);
@@ -1,6 +1,8 @@
import {
CodeBlockModel,
type DocMode,
DocModes,
ImageBlockModel,
type ParagraphBlockModel,
type ReferenceInfo,
} from '@blocksuite/affine-model';
@@ -291,7 +293,7 @@ class PasteTr {
return;
}
if (!cursorModel.text) {
if (matchFlavours(cursorModel, ['affine:image'])) {
if (matchFlavours(cursorModel, [ImageBlockModel])) {
const selection = this.std.selection.create(ImageSelection, {
blockId: target.blockId,
});
@@ -356,7 +358,7 @@ class PasteTr {
if (
this.firstSnapshot !== this.lastSnapshot &&
this.lastSnapshot.props.text &&
!matchFlavours(this.pointState.model, ['affine:code'])
!matchFlavours(this.pointState.model, [CodeBlockModel])
) {
const text = fromJSON(this.lastSnapshot.props.text) as Text;
const doc = new Y.Doc();
+20 -1
View File
@@ -1,4 +1,13 @@
import type { EmbedCardStyle } from '@blocksuite/affine-model';
import {
type EmbedCardStyle,
EmbedFigmaModel,
EmbedGithubModel,
EmbedHtmlModel,
EmbedLinkedDocModel,
EmbedLoomModel,
EmbedSyncedDocModel,
EmbedYoutubeModel,
} from '@blocksuite/affine-model';
export const BLOCK_CHILDREN_CONTAINER_PADDING_LEFT = 24;
export const EDGELESS_BLOCK_CHILD_PADDING = 24;
@@ -48,6 +57,16 @@ export const EMBED_BLOCK_FLAVOUR_LIST = [
'affine:embed-loom',
] as const;
export const EMBED_BLOCK_MODEL_LIST = [
EmbedGithubModel,
EmbedYoutubeModel,
EmbedFigmaModel,
EmbedLinkedDocModel,
EmbedSyncedDocModel,
EmbedHtmlModel,
EmbedLoomModel,
] as const;
export const DEFAULT_IMAGE_PROXY_ENDPOINT =
'https://affine-worker.toeverything.workers.dev/api/worker/image-proxy';
@@ -1,4 +1,4 @@
import type { ParagraphBlockModel } from '@blocksuite/affine-model';
import { ParagraphBlockModel } from '@blocksuite/affine-model';
import type { BlockModel } from '@blocksuite/store';
import { matchFlavours } from '../model/checker.js';
@@ -15,7 +15,7 @@ export function calculateCollapsedSiblings(
const collapsedEdgeIndex = children.findIndex((child, i) => {
if (
i > index &&
matchFlavours(child, ['affine:paragraph']) &&
matchFlavours(child, [ParagraphBlockModel]) &&
child.type.startsWith('h')
) {
const modelLevel = parseInt(model.type.slice(1));
@@ -46,7 +46,7 @@ export function getNearestHeadingBefore(
for (let i = index - 1; i >= 0; i--) {
const sibling = parent.children[i];
if (
matchFlavours(sibling, ['affine:paragraph']) &&
matchFlavours(sibling, [ParagraphBlockModel]) &&
sibling.type.startsWith('h')
) {
return sibling;
@@ -1,3 +1,4 @@
import { DatabaseBlockModel, ListBlockModel } from '@blocksuite/affine-model';
import type { BlockComponent } from '@blocksuite/block-std';
import { type Point, Rect } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
@@ -55,7 +56,7 @@ export function calcDropTarget(
.every(m => children.includes(m.flavour));
}
if (!shouldAppendToDatabase && !matchFlavours(model, ['affine:database'])) {
if (!shouldAppendToDatabase && !matchFlavours(model, [DatabaseBlockModel])) {
const databaseBlockComponent =
element.closest<BlockComponent>('affine-database');
if (databaseBlockComponent) {
@@ -149,7 +150,7 @@ export function calcDropTarget(
const hasChild = (element as BlockComponent).childBlocks.length;
if (
allowSublist &&
matchFlavours(model, ['affine:list']) &&
matchFlavours(model, [ListBlockModel]) &&
!hasChild &&
point.x > domRect.x + BLOCK_CHILDREN_CONTAINER_PADDING_LEFT
) {
@@ -1,3 +1,4 @@
import { DatabaseBlockModel } from '@blocksuite/affine-model';
import { BLOCK_ID_ATTR } from '@blocksuite/block-std';
import type { Point } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
@@ -24,7 +25,7 @@ export function getDropRectByPoint(
flag: DropFlags.Normal,
};
const isDatabase = matchFlavours(model, ['affine:database']);
const isDatabase = matchFlavours(model, [DatabaseBlockModel]);
if (isDatabase) {
const table = getDatabaseBlockTableElement(element);
@@ -1,4 +1,6 @@
import { NoteBlockModel, RootBlockModel } from '@blocksuite/affine-model';
import { BLOCK_ID_ATTR, type BlockComponent } from '@blocksuite/block-std';
import { SurfaceBlockModel } from '@blocksuite/block-std/gfx';
import type { Point, Rect } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
@@ -35,10 +37,9 @@ function hasBlockId(element: Element): element is BlockComponent {
*/
function isRootOrNoteOrSurface(element: BlockComponent) {
return matchFlavours(element.model, [
'affine:page',
'affine:note',
// @ts-expect-error TODO: migrate surface model to @blocksuite/affine-model
'affine:surface',
RootBlockModel,
NoteBlockModel,
SurfaceBlockModel,
]);
}
@@ -1,15 +1,19 @@
import type { BlockModel, DraftModel, Store } from '@blocksuite/store';
import { minimatch } from 'minimatch';
import type { BlockModel, Store } from '@blocksuite/store';
export function matchFlavours<Key extends (keyof BlockSuite.BlockModels)[]>(
model: DraftModel | null,
expected: Key
): model is BlockSuite.BlockModels[Key[number]] {
type ConstructorType<U> = { new (): U };
type ModelList<T> =
T extends Array<infer U>
? U extends ConstructorType<infer C>
? Array<C>
: never
: never;
export function matchFlavours<
const Model extends ConstructorType<BlockModel>[],
U extends ModelList<Model>[number] = ModelList<Model>[number],
>(model: unknown, expected: Model): model is U {
return (
!!model &&
expected.some(key =>
minimatch(model.flavour as keyof BlockSuite.BlockModels, key)
)
!!model && expected.some(expectedModel => model instanceof expectedModel)
);
}
@@ -16,7 +16,6 @@ export function createDefaultDoc(
title,
});
// @ts-expect-error FIXME: will be fixed when surface model migrated to affine-model
doc.addBlock('affine:surface', {}, rootId);
const noteId = doc.addBlock('affine:note', {}, rootId);
doc.addBlock('affine:paragraph', {}, noteId);
@@ -1,3 +1,4 @@
import { FrameBlockModel } from '@blocksuite/affine-model';
import type { EditorHost } from '@blocksuite/block-std';
import type { BlockModel } from '@blocksuite/store';
@@ -73,7 +74,7 @@ export function getPrevContentBlock(
const prev = getPrev(model);
if (prev) {
if (prev.role === 'content' && !matchFlavours(prev, ['affine:frame'])) {
if (prev.role === 'content' && !matchFlavours(prev, [FrameBlockModel])) {
return prev;
}
@@ -1,4 +1,4 @@
import { type NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model';
import { NoteBlockModel, NoteDisplayMode } from '@blocksuite/affine-model';
import type { BlockComponent, EditorHost } from '@blocksuite/block-std';
import type { BlockModel, Store } from '@blocksuite/store';
@@ -37,7 +37,7 @@ export async function asyncGetBlockComponent(
export function findNoteBlockModel(model: BlockModel) {
return findAncestorModel(model, m =>
matchFlavours(m, ['affine:note'])
matchFlavours(m, [NoteBlockModel])
) as NoteBlockModel | null;
}
@@ -48,7 +48,7 @@ export function getLastNoteBlock(doc: Store) {
for (let i = children.length - 1; i >= 0; i--) {
const child = children[i];
if (
matchFlavours(child, ['affine:note']) &&
matchFlavours(child, [NoteBlockModel]) &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
) {
note = child as NoteBlockModel;
@@ -1,4 +1,4 @@
import type { ListBlockModel } from '@blocksuite/affine-model';
import { ListBlockModel } from '@blocksuite/affine-model';
import type { BlockStdScope } from '@blocksuite/block-std';
import type { BlockModel, Store } from '@blocksuite/store';
@@ -23,7 +23,7 @@ export function getNextContinuousNumberedLists(
const firstNotNumberedListIndex = parent.children.findIndex(
(model, i) =>
i > modelIndex &&
(!matchFlavours(model, ['affine:list']) || model.type !== 'numbered')
(!matchFlavours(model, [ListBlockModel]) || model.type !== 'numbered')
);
const newContinuousLists = parent.children.slice(
modelIndex + 1,
@@ -32,7 +32,7 @@ export function getNextContinuousNumberedLists(
if (
!newContinuousLists.every(
model =>
matchFlavours(model, ['affine:list']) && model.type === 'numbered'
matchFlavours(model, [ListBlockModel]) && model.type === 'numbered'
)
)
return [];
@@ -56,7 +56,7 @@ export function toNumberedList(
// if there is a numbered list before, the order continues from the previous list
if (
prevSibling &&
matchFlavours(prevSibling, ['affine:list']) &&
matchFlavours(prevSibling, [ListBlockModel]) &&
prevSibling.type === 'numbered'
) {
doc.transact(() => {
@@ -1,4 +1,5 @@
import { correctNumberedListsOrderToPrev } from '@blocksuite/affine-block-list';
import { ListBlockModel } from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std';
import type { TransformerMiddleware } from '@blocksuite/store';
@@ -10,7 +11,7 @@ export const reorderList =
if (payload.type === 'block') {
const model = payload.model;
if (
matchFlavours(model, ['affine:list']) &&
matchFlavours(model, [ListBlockModel]) &&
model.type === 'numbered'
) {
const next = std.store.getNext(model);
@@ -3,7 +3,11 @@ import {
type EdgelessNoteBlockComponent,
} from '@blocksuite/affine-block-note';
import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph';
import type { ParagraphBlockModel } from '@blocksuite/affine-model';
import {
DatabaseBlockModel,
ListBlockModel,
type ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import {
calcDropTarget,
@@ -216,7 +220,7 @@ export const getDropResult = (
const model = closestBlock.model;
const isDatabase = matchFlavours(model, ['affine:database']);
const isDatabase = matchFlavours(model, [DatabaseBlockModel]);
if (isDatabase) {
return dropIndicator;
}
@@ -232,7 +236,7 @@ export const getDropResult = (
export function getDragHandleLeftPadding(blocks: BlockComponent[]) {
const hasToggleList = blocks.some(
block =>
(matchFlavours(block.model, ['affine:list']) &&
(matchFlavours(block.model, [ListBlockModel]) &&
block.model.children.length > 0) ||
(block instanceof ParagraphBlockComponent &&
block.model.type.startsWith('h') &&
@@ -2,9 +2,17 @@ import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph';
import {
addNoteAtPoint,
getSurfaceBlock,
SurfaceBlockModel,
} from '@blocksuite/affine-block-surface';
import { DropIndicator } from '@blocksuite/affine-components/drop-indicator';
import type { EmbedCardStyle, NoteBlockModel } from '@blocksuite/affine-model';
import {
AttachmentBlockModel,
BookmarkBlockModel,
DatabaseBlockModel,
type EmbedCardStyle,
ListBlockModel,
NoteBlockModel,
} from '@blocksuite/affine-model';
import {
BLOCK_CHILDREN_CONTAINER_PADDING_LEFT,
EMBED_CARD_HEIGHT,
@@ -153,17 +161,17 @@ export class DragEventWatcher {
!snapshot ||
snapshot.content.length === 0 ||
!dragPayload?.from ||
matchFlavours(model, ['affine:database'])
matchFlavours(model, [DatabaseBlockModel])
)
return null;
const isDropOnNoteBlock = matchFlavours(model, ['affine:note']);
const isDropOnNoteBlock = matchFlavours(model, [NoteBlockModel]);
const edge = dropPayload.edge;
const scale = this.widget.scale.peek();
let result: DropResult;
if (edge === 'right' && matchFlavours(dropBlock.model, ['affine:list'])) {
if (edge === 'right' && matchFlavours(dropBlock.model, [ListBlockModel])) {
const domRect = getRectByBlockComponent(dropBlock);
const placement = 'in';
const rect = Rect.fromLWTH(
@@ -365,7 +373,7 @@ export class DragEventWatcher {
result.placement === 'in' ? model : this.std.store.getParent(model);
if (!parent) return;
if (matchFlavours(parent, ['affine:surface'])) {
if (matchFlavours(parent, [SurfaceBlockModel])) {
return;
}
@@ -375,7 +383,7 @@ export class DragEventWatcher {
: parent.children.indexOf(model) +
(result.placement === 'before' ? 0 : 1);
if (matchFlavours(parent, ['affine:note'])) {
if (matchFlavours(parent, [NoteBlockModel])) {
const [first] = snapshot.content;
if (first.flavour === 'affine:note') {
if (parent.id !== first.id) {
@@ -677,7 +685,7 @@ export class DragEventWatcher {
})
);
if (matchFlavours(view.model, ['affine:attachment', 'affine:bookmark'])) {
if (matchFlavours(view.model, [AttachmentBlockModel, BookmarkBlockModel])) {
cleanups.push(this._makeDraggable(view));
}
@@ -14,7 +14,7 @@ import {
NoteBlockModel,
NoteDisplayMode,
type RootBlockModel,
type SurfaceRefBlockModel,
SurfaceRefBlockModel,
} from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import {
@@ -195,7 +195,7 @@ export class EdgelessAutoConnectWidget extends WidgetComponent<RootBlockModel> {
const pageVisibleBlocks = new Map<AutoConnectElement, number>();
const notes = service.doc.root?.children.filter(child =>
matchFlavours(child, ['affine:note'])
matchFlavours(child, [NoteBlockModel])
);
const edgelessOnlyNotesSet = new Set<NoteBlockModel>();
@@ -209,7 +209,7 @@ export class EdgelessAutoConnectWidget extends WidgetComponent<RootBlockModel> {
}
note.children.forEach(model => {
if (matchFlavours(model, ['affine:surface-ref'])) {
if (matchFlavours(model, [SurfaceRefBlockModel])) {
const reference = this._crud.getElementById(model.reference);
if (!isAutoConnectElement(reference)) return;
@@ -1,4 +1,13 @@
import {
AttachmentBlockModel,
BookmarkBlockModel,
CodeBlockModel,
DatabaseBlockModel,
ImageBlockModel,
SurfaceRefBlockModel,
} from '@blocksuite/affine-model';
import { getSelectionRectsCommand } from '@blocksuite/affine-shared/commands';
import { EMBED_BLOCK_MODEL_LIST } from '@blocksuite/affine-shared/consts';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import {
BlockSelection,
@@ -67,16 +76,15 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
private get _config(): DocRemoteSelectionConfig {
return {
blockSelectionBackgroundTransparent: block => {
return (
matchFlavours(block, [
'affine:code',
'affine:database',
'affine:image',
'affine:attachment',
'affine:bookmark',
'affine:surface-ref',
]) || /affine:embed-*/.test(block.flavour)
);
return matchFlavours(block, [
CodeBlockModel,
DatabaseBlockModel,
ImageBlockModel,
AttachmentBlockModel,
BookmarkBlockModel,
SurfaceRefBlockModel,
...EMBED_BLOCK_MODEL_LIST,
]);
},
};
}
@@ -3,7 +3,9 @@ import {
SurfaceElementModel,
} from '@blocksuite/affine-block-surface';
import {
FrameBlockModel,
GroupElementModel,
ImageBlockModel,
type RootBlockModel,
} from '@blocksuite/affine-model';
import { FetchUtils } from '@blocksuite/affine-shared/adapters';
@@ -456,7 +458,7 @@ export class ExportManager {
edgeless?.service.gfx.getElementsByBound(bound, { type: 'block' }) ??
[];
for (const block of blocks) {
if (matchFlavours(block, ['affine:image'])) {
if (matchFlavours(block, [ImageBlockModel])) {
if (!block.sourceId) return;
const blob = await block.doc.blobSync.get(block.sourceId);
@@ -493,7 +495,7 @@ export class ExportManager {
);
}
if (matchFlavours(block, ['affine:frame'])) {
if (matchFlavours(block, [FrameBlockModel])) {
// TODO(@L-Sun): use children of frame instead of bound
const blocksInsideFrame = getBlocksInFrameBound(this.doc, block, false);
const frameBound = Bound.deserialize(block.xywh);
@@ -11,6 +11,7 @@ import {
BookmarkStyles,
DEFAULT_NOTE_HEIGHT,
DEFAULT_NOTE_WIDTH,
FrameBlockModel,
MAX_IMAGE_WIDTH,
ReferenceInfoSchema,
} from '@blocksuite/affine-model';
@@ -994,7 +995,7 @@ export class EdgelessClipboardController extends PageClipboard {
for (const nodeElement of nodeElements) {
await _drawTopLevelBlock(nodeElement);
if (matchFlavours(nodeElement, ['affine:frame'])) {
if (matchFlavours(nodeElement, [FrameBlockModel])) {
const blocksInsideFrame: BlockSuite.EdgelessBlockModelType[] = [];
this.edgeless.service.frame
.getElementsInFrameBound(nodeElement, false)
@@ -9,6 +9,7 @@ import {
GroupElementModel,
LayoutType,
MindmapElementModel,
NoteBlockModel,
NoteDisplayMode,
type ShapeElementModel,
} from '@blocksuite/affine-model';
@@ -134,7 +135,7 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
selection.selectedElements.length === 1 &&
selection.firstElement instanceof GfxBlockElementModel &&
matchFlavours(selection.firstElement as GfxBlockElementModel, [
'affine:note',
NoteBlockModel,
])
) {
rootComponent.slots.toggleNoteSlicer.emit();
@@ -259,7 +260,7 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
block =>
block.group === null &&
!(
matchFlavours(block, ['affine:note']) &&
matchFlavours(block, [NoteBlockModel]) &&
block.displayMode === NoteDisplayMode.DocOnly
)
)
@@ -7,7 +7,7 @@ import {
normalizeWheelDeltaY,
} from '@blocksuite/affine-block-surface';
import {
type NoteBlockModel,
NoteBlockModel,
NoteDisplayMode,
type RootBlockModel,
type ShapeElementModel,
@@ -350,7 +350,7 @@ export class EdgelessRootBlockComponent extends BlockComponent<
const primaryMode = std.get(DocModeProvider).getPrimaryMode(this.doc.id);
const note = this.model.children.find(
(child): child is NoteBlockModel =>
matchFlavours(child, ['affine:note']) &&
matchFlavours(child, [NoteBlockModel]) &&
child.displayMode !== NoteDisplayMode.EdgelessOnly
);
@@ -5,6 +5,7 @@ import {
promptDocTitle,
} from '@blocksuite/affine-block-embed';
import { ParagraphBlockComponent } from '@blocksuite/affine-block-paragraph';
import { NoteBlockModel, ParagraphBlockModel } from '@blocksuite/affine-model';
import {
draftSelectedModelsCommand,
getSelectedModelsCommand,
@@ -37,7 +38,7 @@ export class PageKeyboardManager {
const model = block.model;
if (
matchFlavours(model, ['affine:paragraph']) &&
matchFlavours(model, [ParagraphBlockModel]) &&
model.type.startsWith('h') &&
model.collapsed
) {
@@ -133,7 +134,7 @@ export class PageKeyboardManager {
const selectedModels = ctx.selectedModels?.filter(
block =>
!block.flavour.startsWith('affine:embed-') &&
matchFlavours(doc.getParent(block), ['affine:note'])
matchFlavours(doc.getParent(block), [NoteBlockModel])
);
const draftedModels = ctx.draftedModels;
@@ -1,6 +1,12 @@
import { focusTextModel } from '@blocksuite/affine-components/rich-text';
import type { NoteBlockModel, RootBlockModel } from '@blocksuite/affine-model';
import { NoteDisplayMode } from '@blocksuite/affine-model';
import {
CodeBlockModel,
ListBlockModel,
NoteBlockModel,
NoteDisplayMode,
ParagraphBlockModel,
type RootBlockModel,
} from '@blocksuite/affine-model';
import { PageViewportService } from '@blocksuite/affine-shared/services';
import type { Viewport } from '@blocksuite/affine-shared/types';
import {
@@ -122,7 +128,11 @@ export class PageRootBlockComponent extends BlockComponent<
focusFirstParagraph = (): { id: string; created: boolean } => {
const defaultNote = this._getDefaultNoteBlock();
const firstText = defaultNote?.children.find(block =>
matchFlavours(block, ['affine:paragraph', 'affine:list', 'affine:code'])
matchFlavours(block, [
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
])
);
if (firstText) {
focusTextModel(this.std, firstText.id);
@@ -238,9 +248,8 @@ export class PageRootBlockComponent extends BlockComponent<
'Mod-a': () => {
const blocks = this.model.children
.filter(model => {
if (matchFlavours(model, ['affine:note'])) {
const note = model as NoteBlockModel;
if (note.displayMode === NoteDisplayMode.EdgelessOnly)
if (matchFlavours(model, [NoteBlockModel])) {
if (model.displayMode === NoteDisplayMode.EdgelessOnly)
return false;
return true;
@@ -385,12 +394,11 @@ export class PageRootBlockComponent extends BlockComponent<
.slice()
.reverse()
.find(child => {
const isNote = matchFlavours(child, ['affine:note']);
const isNote = matchFlavours(child, [NoteBlockModel]);
if (!isNote) return false;
const note = child as NoteBlockModel;
const displayOnDoc =
!!note.displayMode &&
note.displayMode !== NoteDisplayMode.EdgelessOnly;
!!child.displayMode &&
child.displayMode !== NoteDisplayMode.EdgelessOnly;
return displayOnDoc;
});
if (!lastNote) {
@@ -402,7 +410,9 @@ export class PageRootBlockComponent extends BlockComponent<
const last = lastNote.children.at(-1);
if (
!last ||
!(matchFlavours(last, ['affine:paragraph']) && last.text.length === 0)
!(
matchFlavours(last, [ParagraphBlockModel]) && last.text.length === 0
)
) {
if (readonly) return;
const paragraphId = this.doc.addBlock(
@@ -442,7 +452,7 @@ export class PageRootBlockComponent extends BlockComponent<
override firstUpdated() {
this._initViewportResizeEffect();
const noteModels = this.model.children.filter(model =>
matchFlavours(model, ['affine:note'])
matchFlavours(model, [NoteBlockModel])
);
noteModels.forEach(note => {
this.disposables.add(
@@ -463,7 +473,7 @@ export class PageRootBlockComponent extends BlockComponent<
)}`;
const children = this.renderChildren(this.model, child => {
const isNote = matchFlavours(child, ['affine:note']);
const isNote = matchFlavours(child, [NoteBlockModel]);
const note = child as NoteBlockModel;
const displayOnEdgeless =
!!note.displayMode && note.displayMode === NoteDisplayMode.EdgelessOnly;
@@ -19,6 +19,7 @@ import {
DEFAULT_NOTE_HEIGHT,
DefaultTheme,
type FrameBlockModel,
NoteBlockModel,
NoteDisplayMode,
resolveColor,
} from '@blocksuite/affine-model';
@@ -92,7 +93,7 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
const rootModel = this.edgeless.doc.root;
const notes = rootModel.children.filter(
model =>
matchFlavours(model, ['affine:note']) &&
matchFlavours(model, [NoteBlockModel]) &&
model.displayMode !== NoteDisplayMode.EdgelessOnly
);
const lastNote = notes[notes.length - 1];
@@ -6,7 +6,11 @@ import {
import { toast } from '@blocksuite/affine-components/toast';
import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
import type { GroupElementModel } from '@blocksuite/affine-model';
import { DEFAULT_NOTE_HEIGHT, NoteDisplayMode } from '@blocksuite/affine-model';
import {
DEFAULT_NOTE_HEIGHT,
NoteBlockModel,
NoteDisplayMode,
} from '@blocksuite/affine-model';
import { matchFlavours } from '@blocksuite/affine-shared/utils';
import {
deserializeXYWH,
@@ -27,7 +31,7 @@ export class EdgelessChangeGroupButton extends WithDisposable(LitElement) {
const rootModel = this.edgeless.doc.root;
const notes = rootModel.children.filter(
model =>
matchFlavours(model, ['affine:note']) &&
matchFlavours(model, [NoteBlockModel]) &&
model.displayMode !== NoteDisplayMode.EdgelessOnly
);
const lastNote = notes[notes.length - 1];
@@ -24,7 +24,7 @@ import {
import {
type ColorScheme,
DefaultTheme,
type NoteBlockModel,
NoteBlockModel,
NoteDisplayMode,
resolveColor,
type StrokeStyle,
@@ -158,7 +158,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
this._pageBlockEnabled &&
this.notes.length === 1 &&
this.notes[0].parent?.children.find(child =>
matchFlavours(child, ['affine:note'])
matchFlavours(child, [NoteBlockModel])
) === this.notes[0]
);
}
@@ -340,7 +340,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
const isFirstNote =
onlyOne &&
note.parent?.children.find(child =>
matchFlavours(child, ['affine:note'])
matchFlavours(child, [NoteBlockModel])
) === note;
const theme = this.edgeless.std.get(ThemeProvider).theme;
const buttons = [
@@ -10,6 +10,12 @@ import {
getMoreMenuConfig,
type MenuItemGroup,
} from '@blocksuite/affine-components/toolbar';
import {
CodeBlockModel,
ImageBlockModel,
ListBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import {
getSelectedBlocksCommand,
getTextSelectionCommand,
@@ -355,10 +361,10 @@ export class AffineFormatBarWidget extends WidgetComponent {
const selectedBlock = this._selectedBlocks[0];
if (
!matchFlavours(selectedBlock.model, [
'affine:paragraph',
'affine:list',
'affine:code',
'affine:image',
ParagraphBlockModel,
ListBlockModel,
CodeBlockModel,
ImageBlockModel,
])
) {
return false;
@@ -35,7 +35,7 @@ export interface LinkedWidgetConfig {
* [[ -> @
*/
convertTriggerKey: boolean;
ignoreBlockTypes: (keyof BlockSuite.BlockModels)[];
ignoreBlockTypes: string[];
ignoreSelector: string;
getMenus: (
query: string,
@@ -1,4 +1,4 @@
import type { RootBlockModel } from '@blocksuite/affine-model';
import { NoteBlockModel, RootBlockModel } from '@blocksuite/affine-model';
import {
autoScroll,
getScrollContainer,
@@ -445,7 +445,7 @@ function isDragArea(e: PointerEventState) {
const el = e.raw.target;
assertInstanceOf(el, Element);
const block = el.closest<BlockComponent>(`[${BLOCK_ID_ATTR}]`);
return block && matchFlavours(block.model, ['affine:page', 'affine:note']);
return block && matchFlavours(block.model, [RootBlockModel, NoteBlockModel]);
}
declare global {
@@ -84,7 +84,7 @@ import {
export type SlashMenuConfig = {
triggerKeys: string[];
ignoreBlockTypes: BlockSuite.Flavour[];
ignoreBlockTypes: string[];
items: SlashMenuItem[];
maxHeight: number;
tooltipTimeout: number;
@@ -2,10 +2,7 @@ import {
type AffineInlineEditor,
getInlineEditorByModel,
} from '@blocksuite/affine-components/rich-text';
import {
getCurrentNativeRange,
matchFlavours,
} from '@blocksuite/affine-shared/utils';
import { getCurrentNativeRange } from '@blocksuite/affine-shared/utils';
import type { UIEventStateContext } from '@blocksuite/block-std';
import { TextSelection, WidgetComponent } from '@blocksuite/block-std';
import {
@@ -155,7 +152,7 @@ export class AffineSlashMenuWidget extends WidgetComponent {
const model = this.host.doc.getBlock(textSelection.blockId)?.model;
if (!model) return;
if (matchFlavours(model, this.config.ignoreBlockTypes)) return;
if (this.config.ignoreBlockTypes.includes(model.flavour)) return;
const inlineRange = inlineEditor.getInlineRange();
if (!inlineRange) return;
@@ -1,4 +1,4 @@
import { defineBlockSchema, type SchemaToModel } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export const RootBlockSchema = defineBlockSchema({
flavour: 'test:page',
@@ -15,7 +15,9 @@ export const RootBlockSchema = defineBlockSchema({
},
});
export type RootBlockModel = SchemaToModel<typeof RootBlockSchema>;
export class RootBlockModel extends BlockModel<
ReturnType<(typeof RootBlockSchema)['model']['props']>
> {}
export const NoteBlockSchema = defineBlockSchema({
flavour: 'test:note',
@@ -28,7 +30,9 @@ export const NoteBlockSchema = defineBlockSchema({
},
});
export type NoteBlockModel = SchemaToModel<typeof NoteBlockSchema>;
export class NoteBlockModel extends BlockModel<
ReturnType<(typeof NoteBlockSchema)['model']['props']>
> {}
export const HeadingBlockSchema = defineBlockSchema({
flavour: 'test:heading',
@@ -43,7 +47,9 @@ export const HeadingBlockSchema = defineBlockSchema({
},
});
export type HeadingBlockModel = SchemaToModel<typeof HeadingBlockSchema>;
export class HeadingBlockModel extends BlockModel<
ReturnType<(typeof HeadingBlockSchema)['model']['props']>
> {}
declare global {
namespace BlockSuite {
@@ -4,9 +4,9 @@ import * as Y from 'yjs';
import {
Block,
BlockModel,
defineBlockSchema,
internalPrimitives,
type SchemaToModel,
} from '../model/block/index.js';
import type { YBlock } from '../model/block/types.js';
import { Schema } from '../schema/index.js';
@@ -54,9 +54,15 @@ const flatTableSchema = defineBlockSchema({
isFlatData: true,
},
});
type RootModel = SchemaToModel<typeof pageSchema>;
type TableModel = SchemaToModel<typeof tableSchema>;
type FlatTableModel = SchemaToModel<typeof flatTableSchema>;
class RootModel extends BlockModel<
ReturnType<(typeof pageSchema)['model']['props']>
> {}
class TableModel extends BlockModel<
ReturnType<(typeof tableSchema)['model']['props']>
> {}
class FlatTableModel extends BlockModel<
ReturnType<(typeof flatTableSchema)['model']['props']>
> {}
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
@@ -1,4 +1,4 @@
import { defineBlockSchema, type SchemaToModel } from '../model/index.js';
import { BlockModel, defineBlockSchema } from '../model/index.js';
export const RootBlockSchema = defineBlockSchema({
flavour: 'affine:page',
@@ -14,7 +14,9 @@ export const RootBlockSchema = defineBlockSchema({
},
});
export type RootBlockModel = SchemaToModel<typeof RootBlockSchema>;
export class RootBlockModel extends BlockModel<
ReturnType<(typeof RootBlockSchema)['model']['props']>
> {}
export const NoteBlockSchema = defineBlockSchema({
flavour: 'affine:note',
@@ -2,8 +2,8 @@ import { expect, test } from 'vitest';
import * as Y from 'yjs';
import { MemoryBlobCRUD } from '../adapter/index.js';
import type { BlockModel } from '../model/block/block-model.js';
import { defineBlockSchema, type SchemaToModel } from '../model/block/zod.js';
import { BlockModel } from '../model/block/block-model.js';
import { defineBlockSchema } from '../model/block/zod.js';
import { Text } from '../reactive/index.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js';
@@ -39,7 +39,9 @@ const docSchema = defineBlockSchema({
},
});
type RootBlockModel = SchemaToModel<typeof docSchema>;
class RootBlockModel extends BlockModel<
ReturnType<(typeof docSchema)['model']['props']>
> {}
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
@@ -20,9 +20,7 @@ type SignaledProps<Props> = Props & {
* myBlock.foo = 'bar';
* ```
*/
function MagicProps(): {
new <Props>(): Props;
} {
function MagicProps(): { new <Props>(): Props } {
return class {} as never;
}
@@ -51,18 +51,6 @@ export type PropsGetter<Props> = (
internalPrimitives: InternalPrimitives
) => Props;
export type SchemaToModel<
Schema extends {
model: {
props: PropsGetter<object>;
flavour: string;
};
},
> = BlockModel<ReturnType<Schema['model']['props']>> &
ReturnType<Schema['model']['props']> & {
flavour: Schema['model']['flavour'];
};
export function defineBlockSchema<
Flavour extends string,
Role extends RoleType,
@@ -451,20 +451,6 @@ export class Store {
}
}
addBlock<Key extends BlockSuite.Flavour>(
flavour: Key,
blockProps?: BlockSuite.ModelProps<BlockSuite.BlockModels[Key]>,
parent?: BlockModel | string | null,
parentIndex?: number
): string;
addBlock(
flavour: never,
blockProps?: Partial<BlockProps & Omit<BlockProps, 'flavour'>>,
parent?: BlockModel | string | null,
parentIndex?: number
): string;
addBlock(
flavour: string,
blockProps: Partial<BlockProps & Omit<BlockProps, 'flavour'>> = {},
@@ -1,9 +1,9 @@
import { effects } from '@blocksuite/affine-block-note/effects';
import { ShadowlessElement, SurfaceSelection } from '@blocksuite/block-std';
import type { NoteBlockModel } from '@blocksuite/blocks';
import {
changeNoteDisplayMode,
matchFlavours,
NoteBlockModel,
NoteDisplayMode,
} from '@blocksuite/blocks';
import {
@@ -155,7 +155,7 @@ export class OutlinePanelBody extends SignalWatcher(
const noteIndex = new Map<NoteBlockModel, number>();
children.forEach((block, index) => {
if (matchFlavours(block, ['affine:note'])) {
if (matchFlavours(block, [NoteBlockModel])) {
noteIndex.set(block, index);
}
});
@@ -171,7 +171,7 @@ export class OutlinePanelBody extends SignalWatcher(
if (targetIndex === null) return;
const removeSelectedNoteFilter = (block: BlockModel) =>
!matchFlavours(block, ['affine:note']) || !selected.includes(block);
!matchFlavours(block, [NoteBlockModel]) || !selected.includes(block);
const leftPart = children
.slice(0, targetIndex)
@@ -200,7 +200,7 @@ export class OutlinePanelBody extends SignalWatcher(
private _selectNote(e: SelectEvent) {
const { selected, id, multiselect } = e.detail;
const note = this.doc.getBlock(id)?.model;
if (!note || !matchFlavours(note, ['affine:note'])) return;
if (!note || !matchFlavours(note, [NoteBlockModel])) return;
let selectedNotes = this._selectedNotes$.peek();
@@ -231,7 +231,7 @@ export class OutlinePanelBody extends SignalWatcher(
.filter(SurfaceSelection)
.map(({ blockId }) => doc.getBlock(blockId)?.model)
.filter(model => {
return !!model && matchFlavours(model, ['affine:note']);
return !!model && matchFlavours(model, [NoteBlockModel]);
});
const preSelected = this._selectedNotes$.peek();
@@ -122,7 +122,7 @@ export class OutlineBlockPreview extends SignalWatcher(
const showPreviewIcon = this._context.showIcons$.value;
switch (block.flavour as keyof BlockSuite.BlockModels) {
switch (block.flavour) {
case 'affine:page':
assertType<RootBlockModel>(block);
return block.title.length > 0
@@ -1,6 +1,11 @@
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
import { matchFlavours, NoteDisplayMode } from '@blocksuite/blocks';
import {
matchFlavours,
NoteDisplayMode,
ParagraphBlockModel,
RootBlockModel,
} from '@blocksuite/blocks';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
@@ -129,11 +134,11 @@ export class MobileOutlineMenu extends SignalWatcher(
renderItem = (item: BlockModel) => {
let className = '';
let text = '';
if (matchFlavours(item, ['affine:page'])) {
if (matchFlavours(item, [RootBlockModel])) {
className = 'title';
text = item.title$.value.toString();
} else if (
matchFlavours(item, ['affine:paragraph']) &&
matchFlavours(item, [ParagraphBlockModel]) &&
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(item.type$.value)
) {
className = item.type$.value;
@@ -1,10 +1,10 @@
import {
BlocksUtils,
matchFlavours,
type NoteBlockModel,
NoteBlockModel,
NoteDisplayMode,
type ParagraphBlockModel,
type RootBlockModel,
ParagraphBlockModel,
RootBlockModel,
} from '@blocksuite/blocks';
import type { BlockModel, Store } from '@blocksuite/store';
@@ -24,7 +24,7 @@ export function getNotesFromDoc(
const notes: NoteBlockModel[] = [];
rootModel.children.forEach(block => {
if (!matchFlavours(block, ['affine:note'])) return;
if (!matchFlavours(block, [NoteBlockModel])) return;
if (modes.includes(block.displayMode$.value)) {
notes.push(block);
@@ -35,14 +35,14 @@ export function getNotesFromDoc(
}
export function isRootBlock(block: BlockModel): block is RootBlockModel {
return BlocksUtils.matchFlavours(block, ['affine:page']);
return BlocksUtils.matchFlavours(block, [RootBlockModel]);
}
export function isHeadingBlock(
block: BlockModel
): block is ParagraphBlockModel {
return (
BlocksUtils.matchFlavours(block, ['affine:paragraph']) &&
BlocksUtils.matchFlavours(block, [ParagraphBlockModel]) &&
headingKeys.has(block.type$.value)
);
}
@@ -17,6 +17,7 @@ import {
getSelectedBlocksCommand,
NoteDisplayMode,
NotificationProvider,
ParagraphBlockModel,
RefNodeSlotsProvider,
TelemetryProvider,
} from '@blocksuite/affine/blocks';
@@ -166,7 +167,7 @@ function addAIChatBlock(
const y = viewportCenter.y - height / 2;
const bound = new Bound(x, y, width, height);
const aiChatBlockId = doc.addBlock(
'affine:embed-ai-chat' as keyof BlockSuite.BlockModels,
'affine:embed-ai-chat',
{
xywh: bound.serialize(),
messages: JSON.stringify(messages),
@@ -231,7 +232,7 @@ const REPLACE_SELECTION = {
if (currentTextSelection) {
const { doc } = host;
const block = doc.getBlock(currentTextSelection.blockId);
if (matchFlavours(block?.model ?? null, ['affine:paragraph'])) {
if (matchFlavours(block?.model ?? null, [ParagraphBlockModel])) {
block?.model.text?.replace(
currentTextSelection.from.index,
currentTextSelection.from.length,
@@ -2,8 +2,12 @@ import type { Chain, InitCommandCtx } from '@blocksuite/affine/block-std';
import {
type AIItemGroupConfig,
type AISubItemConfig,
CodeBlockModel,
getSelectedModelsCommand,
ImageBlockModel,
ListBlockModel,
matchFlavours,
ParagraphBlockModel,
} from '@blocksuite/affine/blocks';
import { actionToHandler } from '../actions/doc-handler';
@@ -103,7 +107,7 @@ const textBlockShowWhen = (chain: Chain<InitCommandCtx>) => {
if (!selectedModels || selectedModels.length === 0) return false;
return selectedModels.some(model =>
matchFlavours(model, ['affine:paragraph', 'affine:list'])
matchFlavours(model, [ParagraphBlockModel, ListBlockModel])
);
};
@@ -117,7 +121,7 @@ const codeBlockShowWhen = (chain: Chain<InitCommandCtx>) => {
if (!selectedModels || selectedModels.length > 1) return false;
const model = selectedModels[0];
return matchFlavours(model, ['affine:code']);
return matchFlavours(model, [CodeBlockModel]);
};
const imageBlockShowWhen = (chain: Chain<InitCommandCtx>) => {
@@ -130,7 +134,7 @@ const imageBlockShowWhen = (chain: Chain<InitCommandCtx>) => {
if (!selectedModels || selectedModels.length > 1) return false;
const model = selectedModels[0];
return matchFlavours(model, ['affine:image']);
return matchFlavours(model, [ImageBlockModel]);
};
const EditAIGroup: AIItemGroupConfig = {
@@ -278,7 +282,7 @@ const GenerateWithAIGroup: AIItemGroupConfig = {
return selectedModels.every(
model =>
matchFlavours(model, ['affine:paragraph', 'affine:list']) &&
matchFlavours(model, [ParagraphBlockModel, ListBlockModel]) &&
!model.type.startsWith('h')
);
},
@@ -7,6 +7,7 @@ import type {
} from '@blocksuite/affine/blocks';
import {
BlocksUtils,
CodeBlockModel,
EdgelessTextBlockModel,
EmbedSyncedDocModel,
ImageBlockModel,
@@ -485,7 +486,7 @@ export function noteWithCodeBlockShowWen(
return (
selected[0] instanceof NoteBlockModel &&
selected[0].children.length === 1 &&
BlocksUtils.matchFlavours(selected[0].children[0], ['affine:code'])
BlocksUtils.matchFlavours(selected[0].children[0], [CodeBlockModel])
);
}
@@ -527,7 +527,7 @@ async function responseToCreateSlides(host: EditorHost, ctx: AIContext) {
await job.insertTemplate(content);
}
getSurfaceElementFromEditor(host).refresh();
getSurfaceElementFromEditor(host)?.refresh();
} catch (error) {
console.error('Error creating slides:', error);
}
@@ -7,6 +7,7 @@ import {
ImageBlockModel,
isInsideEdgelessEditor,
matchFlavours,
NoteBlockModel,
NoteDisplayMode,
} from '@blocksuite/affine/blocks';
import { assertExists, Bound } from '@blocksuite/affine/global/utils';
@@ -115,7 +116,7 @@ function createNewNote(host: EditorHost): AIItemConfig {
// set the viewport to show the new note block and original note block
const newNote = doc.getBlock(noteBlockId)?.model;
if (!newNote || !matchFlavours(newNote, ['affine:note'])) return;
if (!newNote || !matchFlavours(newNote, [NoteBlockModel])) return;
const newNoteBound = Bound.deserialize(newNote.xywh);
const bounds = [bound, newNoteBound];
service.gfx.fitToScreen({
@@ -175,7 +175,7 @@ export class AIChatBlockPeekView extends LitElement {
const edgelessService = this._rootService as EdgelessRootService;
const bound = calcChildBound(this.parentModel, edgelessService);
const aiChatBlockId = edgelessService.crud.addBlock(
'affine:embed-ai-chat' as keyof BlockSuite.BlockModels,
'affine:embed-ai-chat',
{
xywh: bound.serialize(),
messages: JSON.stringify(messages),
@@ -51,7 +51,7 @@ export const PPTBuilder = (host: EditorHost) => {
);
}
await job.insertTemplate(content);
getSurfaceElementFromEditor(host).refresh();
getSurfaceElementFromEditor(host)?.refresh();
};
return {
@@ -5,7 +5,10 @@ import {
type EdgelessRootService,
matchFlavours,
MindmapElementModel,
NoteBlockModel,
RootBlockModel,
type ShapeElementModel,
SurfaceBlockModel,
} from '@blocksuite/affine/blocks';
export function mindMapToMarkdown(mindmap: MindmapElementModel) {
@@ -62,10 +65,10 @@ export function getEdgelessCopilotWidget(
export function findNoteBlockModel(blockElement: BlockComponent) {
let curBlock = blockElement;
while (curBlock) {
if (matchFlavours(curBlock.model, ['affine:note'])) {
if (matchFlavours(curBlock.model, [NoteBlockModel])) {
return curBlock.model;
}
if (matchFlavours(curBlock.model, ['affine:page', 'affine:surface'])) {
if (matchFlavours(curBlock.model, [RootBlockModel, SurfaceBlockModel])) {
return null;
}
if (!curBlock.parentComponent) {
@@ -1,11 +1,12 @@
import type { EditorHost } from '@blocksuite/affine/block-std';
import {
BlocksUtils,
DatabaseBlockModel,
DocModeProvider,
embedSyncedDocMiddleware,
getImageSelectionsCommand,
getSelectedBlocksCommand,
type ImageBlockModel,
ImageBlockModel,
isInsideEdgelessEditor,
MarkdownAdapter,
type NoteBlockModel,
@@ -163,7 +164,7 @@ export async function extractMarkdownFromDoc(
const blockModels = getNoteBlockModels(doc);
const textModels = blockModels.filter(
model =>
!BlocksUtils.matchFlavours(model, ['affine:image', 'affine:database'])
!BlocksUtils.matchFlavours(model, [ImageBlockModel, DatabaseBlockModel])
);
const drafts = textModels.map(toDraftModel);
const slice = Slice.fromModels(doc, drafts);
@@ -3,6 +3,7 @@ import type { GfxModel } from '@blocksuite/affine/block-std/gfx';
import {
BlocksUtils,
type CopilotTool,
DatabaseBlockModel,
EdgelessRootService,
type FrameBlockModel,
getBlockSelectionsCommand,
@@ -13,7 +14,6 @@ import {
ImageBlockModel,
type SurfaceBlockComponent,
} from '@blocksuite/affine/blocks';
import { assertExists } from '@blocksuite/affine/global/utils';
import {
type BlockModel,
type DraftModel,
@@ -134,7 +134,7 @@ export async function getTextContentFromBlockModels(
// Currently only filter out images and databases
const selectedTextModels = models.filter(
model =>
!BlocksUtils.matchFlavours(model, ['affine:image', 'affine:database'])
!BlocksUtils.matchFlavours(model, [ImageBlockModel, DatabaseBlockModel])
);
const drafts = selectedTextModels.map(toDraftModel);
drafts.forEach(draft => traverse(draft, drafts));
@@ -147,13 +147,13 @@ export async function getSelectedTextContent(
type: 'markdown' | 'plain-text' = 'markdown'
) {
const selectedModels = getSelectedModels(editorHost);
assertExists(selectedModels);
if (!selectedModels) return '';
return getTextContentFromBlockModels(editorHost, selectedModels, type);
}
export async function selectAboveBlocks(editorHost: EditorHost, num = 10) {
let selectedModels = getSelectedModels(editorHost);
assertExists(selectedModels);
if (!selectedModels) return '';
const lastLeafModel = selectedModels[selectedModels.length - 1];
@@ -163,8 +163,7 @@ export async function selectAboveBlocks(editorHost: EditorHost, num = 10) {
lastRootModel = noteModel;
noteModel = editorHost.doc.getParent(noteModel);
}
assertExists(noteModel);
assertExists(lastRootModel);
if (!noteModel || !lastRootModel) return '';
const endIndex = noteModel.children.indexOf(lastRootModel) + 1;
const startIndex = Math.max(0, endIndex - num);
@@ -212,13 +211,13 @@ export const stopPropagation = (e: Event) => {
export function getSurfaceElementFromEditor(editor: EditorHost) {
const { doc } = editor;
const surfaceModel = doc.getBlockByFlavour('affine:surface')[0];
assertExists(surfaceModel);
if (!surfaceModel) return null;
const surfaceId = surfaceModel.id;
const surfaceElement = editor.querySelector(
`affine-surface[data-block-id="${surfaceId}"]`
) as SurfaceBlockComponent;
assertExists(surfaceElement);
if (!surfaceElement) return null;
return surfaceElement;
}
@@ -11,7 +11,7 @@ import { track } from '@affine/track';
import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx';
import {
matchFlavours,
type NoteBlockModel,
NoteBlockModel,
NoteDisplayMode,
} from '@blocksuite/affine/blocks';
import { Bound } from '@blocksuite/affine/global/utils';
@@ -176,7 +176,7 @@ export const EdgelessNoteHeader = ({ note }: { note: NoteBlockModel }) => {
const isFirstVisibleNote =
note.parent?.children.find(
child =>
matchFlavours(child, ['affine:note']) &&
matchFlavours(child, [NoteBlockModel]) &&
child.displayMode === NoteDisplayMode.DocAndEdgeless
) === note;