chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions
@@ -0,0 +1,101 @@
import type {
GfxCommonBlockProps,
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../utils/index.js';
import { AttachmentBlockTransformer } from './attachment-transformer.js';
/**
* When the attachment is uploading, the `sourceId` is `undefined`.
* And we can query the upload status by the `isAttachmentLoading` function.
*
* Other collaborators will see an error attachment block when the blob has not finished uploading.
* This issue can be resolve by sync the upload status through the awareness system in the future.
*
* When the attachment is uploaded, the `sourceId` is the id of the blob.
*
* If there are no `sourceId` and the `isAttachmentLoading` function returns `false`,
* it means that the attachment is failed to upload.
*/
/**
* @deprecated
*/
type BackwardCompatibleUndefined = undefined;
export const AttachmentBlockStyles: EmbedCardStyle[] = [
'cubeThick',
'horizontalThin',
'pdf',
] as const;
export type AttachmentBlockProps = {
name: string;
size: number;
/**
* MIME type
*/
type: string;
caption?: string;
// `loadingKey` was used to indicate whether the attachment is loading,
// which is currently unused but no breaking change is needed.
// The `loadingKey` and `sourceId` should not be existed at the same time.
// loadingKey?: string | null;
sourceId?: string;
/**
* Whether to show the attachment as an embed view.
*/
embed: boolean | BackwardCompatibleUndefined;
style?: (typeof AttachmentBlockStyles)[number];
} & Omit<GfxCommonBlockProps, 'scale'>;
export const defaultAttachmentProps: AttachmentBlockProps = {
name: '',
size: 0,
type: 'application/octet-stream',
sourceId: undefined,
caption: undefined,
embed: false,
style: AttachmentBlockStyles[1],
index: 'a0',
xywh: '[0,0,0,0]',
lockedBySelf: false,
rotate: 0,
};
export const AttachmentBlockSchema = defineBlockSchema({
flavour: 'affine:attachment',
props: (): AttachmentBlockProps => defaultAttachmentProps,
metadata: {
version: 1,
role: 'content',
parent: [
'affine:note',
'affine:surface',
'affine:edgeless-text',
'affine:paragraph',
'affine:list',
],
},
transformer: () => new AttachmentBlockTransformer(),
toModel: () => new AttachmentBlockModel(),
});
export class AttachmentBlockModel
extends GfxCompatible<AttachmentBlockProps>(BlockModel)
implements GfxElementGeometry {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:attachment': AttachmentBlockModel;
}
interface BlockModels {
'affine:attachment': AttachmentBlockModel;
}
}
}
@@ -0,0 +1,17 @@
import type { FromSnapshotPayload, SnapshotNode } from '@blocksuite/store';
import { BaseBlockTransformer } from '@blocksuite/store';
import type { AttachmentBlockProps } from './attachment-model.js';
export class AttachmentBlockTransformer extends BaseBlockTransformer<AttachmentBlockProps> {
override async fromSnapshot(
payload: FromSnapshotPayload
): Promise<SnapshotNode<AttachmentBlockProps>> {
const snapshotRet = await super.fromSnapshot(payload);
const sourceId = snapshotRet.props.sourceId;
if (!payload.assets.isEmpty() && sourceId)
await payload.assets.writeToBlob(sourceId);
return snapshotRet;
}
}
@@ -0,0 +1,2 @@
export * from './attachment-model.js';
export * from './attachment-transformer.js';
@@ -0,0 +1,70 @@
import type {
GfxCommonBlockProps,
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import type { EmbedCardStyle, LinkPreviewData } from '../../utils/index.js';
export const BookmarkStyles: EmbedCardStyle[] = [
'vertical',
'horizontal',
'list',
'cube',
] as const;
export type BookmarkBlockProps = {
style: (typeof BookmarkStyles)[number];
url: string;
caption: string | null;
} & LinkPreviewData &
Omit<GfxCommonBlockProps, 'scale'>;
const defaultBookmarkProps: BookmarkBlockProps = {
style: BookmarkStyles[1],
url: '',
caption: null,
description: null,
icon: null,
image: null,
title: null,
index: 'a0',
xywh: '[0,0,0,0]',
lockedBySelf: false,
rotate: 0,
};
export const BookmarkBlockSchema = defineBlockSchema({
flavour: 'affine:bookmark',
props: (): BookmarkBlockProps => defaultBookmarkProps,
metadata: {
version: 1,
role: 'content',
parent: [
'affine:note',
'affine:surface',
'affine:edgeless-text',
'affine:paragraph',
'affine:list',
],
},
toModel: () => new BookmarkBlockModel(),
});
export class BookmarkBlockModel
extends GfxCompatible<BookmarkBlockProps>(BlockModel)
implements GfxElementGeometry {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:bookmark': BookmarkBlockModel;
}
interface BlockModels {
'affine:bookmark': BookmarkBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './bookmark-model.js';
@@ -0,0 +1,44 @@
import {
defineBlockSchema,
type SchemaToModel,
type Text,
} from '@blocksuite/store';
interface CodeBlockProps {
text: Text;
language: string | null;
wrap: boolean;
caption: string;
}
export const CodeBlockSchema = defineBlockSchema({
flavour: 'affine:code',
props: internal =>
({
text: internal.Text(),
language: null,
wrap: false,
caption: '',
}) as CodeBlockProps,
metadata: {
version: 1,
role: 'content',
parent: [
'affine:note',
'affine:paragraph',
'affine:list',
'affine:edgeless-text',
],
children: [],
},
});
export type CodeBlockModel = SchemaToModel<typeof CodeBlockSchema>;
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:code': CodeBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './code-model.js';
@@ -0,0 +1,30 @@
import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import type { Column, SerializedCells, ViewBasicDataType } from './types.js';
export type DatabaseBlockProps = {
views: ViewBasicDataType[];
title: Text;
cells: SerializedCells;
columns: Array<Column>;
};
export class DatabaseBlockModel extends BlockModel<DatabaseBlockProps> {}
export const DatabaseBlockSchema = defineBlockSchema({
flavour: 'affine:database',
props: (internal): DatabaseBlockProps => ({
views: [],
title: internal.Text(),
cells: Object.create(null),
columns: [],
}),
metadata: {
role: 'hub',
version: 3,
parent: ['affine:note'],
children: ['affine:paragraph', 'affine:list'],
},
toModel: () => new DatabaseBlockModel(),
});
@@ -0,0 +1,2 @@
export * from './database-model.js';
export * from './types.js';
@@ -0,0 +1,21 @@
export interface Column<
Data extends Record<string, unknown> = Record<string, unknown>,
> {
id: string;
type: string;
name: string;
data: Data;
}
export type ColumnUpdater<T extends Column = Column> = (data: T) => Partial<T>;
export type Cell<ValueType = unknown> = {
columnId: Column['id'];
value: ValueType;
};
export type SerializedCells = Record<string, Record<string, Cell>>;
export type ViewBasicDataType = {
id: string;
name: string;
mode: string;
};
@@ -0,0 +1,20 @@
import { defineBlockSchema, type SchemaToModel } from '@blocksuite/store';
export const DividerBlockSchema = defineBlockSchema({
flavour: 'affine:divider',
metadata: {
version: 1,
role: 'content',
children: [],
},
});
export type DividerBlockModel = SchemaToModel<typeof DividerBlockSchema>;
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:divider': DividerBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './divider-model.js';
@@ -0,0 +1,74 @@
import type {
GfxCommonBlockProps,
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
FontFamily,
FontStyle,
FontWeight,
TextAlign,
type TextStyleProps,
} from '../../consts/index.js';
type EdgelessTextProps = {
hasMaxWidth: boolean;
} & Omit<TextStyleProps, 'fontSize'> &
GfxCommonBlockProps;
export const EdgelessTextBlockSchema = defineBlockSchema({
flavour: 'affine:edgeless-text',
props: (): EdgelessTextProps => ({
xywh: '[0,0,16,16]',
index: 'a0',
lockedBySelf: false,
color: '#000000',
fontFamily: FontFamily.Inter,
fontStyle: FontStyle.Normal,
fontWeight: FontWeight.Regular,
textAlign: TextAlign.Left,
scale: 1,
rotate: 0,
hasMaxWidth: false,
}),
metadata: {
version: 1,
role: 'hub',
parent: ['affine:surface'],
children: [
'affine:paragraph',
'affine:list',
'affine:code',
'affine:image',
'affine:bookmark',
'affine:attachment',
'affine:embed-!(synced-doc)',
'affine:latex',
],
},
toModel: () => {
return new EdgelessTextBlockModel();
},
});
export class EdgelessTextBlockModel
extends GfxCompatible<EdgelessTextProps>(BlockModel)
implements GfxElementGeometry {}
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:edgeless-text': EdgelessTextBlockModel;
}
interface EdgelessBlockModelMap {
'affine:edgeless-text': EdgelessTextBlockModel;
}
interface EdgelessTextModelMap {
'edgeless-text': EdgelessTextBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './edgeless-text-model.js';
@@ -0,0 +1,32 @@
import { BlockModel } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export type EmbedFigmaBlockUrlData = {
title: string | null;
description: string | null;
};
export const EmbedFigmaStyles: EmbedCardStyle[] = ['figma'] as const;
export type EmbedFigmaBlockProps = {
style: (typeof EmbedFigmaStyles)[number];
url: string;
caption: string | null;
} & EmbedFigmaBlockUrlData;
export class EmbedFigmaModel extends defineEmbedModel<EmbedFigmaBlockProps>(
BlockModel
) {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-figma': EmbedFigmaModel;
}
interface BlockModels {
'affine:embed-figma': EmbedFigmaModel;
}
}
}
@@ -0,0 +1,22 @@
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedFigmaBlockProps,
EmbedFigmaModel,
EmbedFigmaStyles,
} from './figma-model.js';
const defaultEmbedFigmaProps: EmbedFigmaBlockProps = {
style: EmbedFigmaStyles[0],
url: '',
caption: null,
title: null,
description: null,
};
export const EmbedFigmaBlockSchema = createEmbedBlockSchema({
name: 'figma',
version: 1,
toModel: () => new EmbedFigmaModel(),
props: (): EmbedFigmaBlockProps => defaultEmbedFigmaProps,
});
@@ -0,0 +1,2 @@
export * from './figma-model.js';
export * from './figma-schema.js';
@@ -0,0 +1,46 @@
import { BlockModel } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export type EmbedGithubBlockUrlData = {
image: string | null;
status: string | null;
statusReason: string | null;
title: string | null;
description: string | null;
createdAt: string | null;
assignees: string[] | null;
};
export const EmbedGithubStyles: EmbedCardStyle[] = [
'vertical',
'horizontal',
'list',
'cube',
] as const;
export type EmbedGithubBlockProps = {
style: (typeof EmbedGithubStyles)[number];
owner: string;
repo: string;
githubType: 'issue' | 'pr';
githubId: string;
url: string;
caption: string | null;
} & EmbedGithubBlockUrlData;
export class EmbedGithubModel extends defineEmbedModel<EmbedGithubBlockProps>(
BlockModel
) {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-github': EmbedGithubModel;
}
interface BlockModels {
'affine:embed-github': EmbedGithubModel;
}
}
}
@@ -0,0 +1,31 @@
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedGithubBlockProps,
EmbedGithubModel,
EmbedGithubStyles,
} from './github-model.js';
const defaultEmbedGithubProps: EmbedGithubBlockProps = {
style: EmbedGithubStyles[1],
owner: '',
repo: '',
githubType: 'issue',
githubId: '',
url: '',
caption: null,
image: null,
status: null,
statusReason: null,
title: null,
description: null,
createdAt: null,
assignees: null,
};
export const EmbedGithubBlockSchema = createEmbedBlockSchema({
name: 'github',
version: 1,
toModel: () => new EmbedGithubModel(),
props: (): EmbedGithubBlockProps => defaultEmbedGithubProps,
});
@@ -0,0 +1,2 @@
export * from './github-model.js';
export * from './github-schema.js';
@@ -0,0 +1,28 @@
import { BlockModel } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export const EmbedHtmlStyles: EmbedCardStyle[] = ['html'] as const;
export type EmbedHtmlBlockProps = {
style: (typeof EmbedHtmlStyles)[number];
caption: string | null;
html?: string;
design?: string;
};
export class EmbedHtmlModel extends defineEmbedModel<EmbedHtmlBlockProps>(
BlockModel
) {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-html': EmbedHtmlModel;
}
interface BlockModels {
'affine:embed-html': EmbedHtmlModel;
}
}
}
@@ -0,0 +1,20 @@
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedHtmlBlockProps,
EmbedHtmlModel,
EmbedHtmlStyles,
} from './html-model.js';
const defaultEmbedHtmlProps: EmbedHtmlBlockProps = {
style: EmbedHtmlStyles[0],
caption: null,
html: undefined,
design: undefined,
};
export const EmbedHtmlBlockSchema = createEmbedBlockSchema({
name: 'html',
version: 1,
toModel: () => new EmbedHtmlModel(),
props: (): EmbedHtmlBlockProps => defaultEmbedHtmlProps,
});
@@ -0,0 +1,2 @@
export * from './html-model.js';
export * from './html-schema.js';
@@ -0,0 +1,7 @@
export * from './figma/index.js';
export * from './github/index.js';
export * from './html/index.js';
export * from './linked-doc/index.js';
export * from './loom/index.js';
export * from './synced-doc/index.js';
export * from './youtube/index.js';
@@ -0,0 +1,2 @@
export * from './linked-doc-model.js';
export * from './linked-doc-schema.js';
@@ -0,0 +1,33 @@
import { BlockModel } from '@blocksuite/store';
import type { ReferenceInfo } from '../../../consts/doc.js';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export const EmbedLinkedDocStyles: EmbedCardStyle[] = [
'vertical',
'horizontal',
'list',
'cube',
'horizontalThin',
];
export type EmbedLinkedDocBlockProps = {
style: EmbedCardStyle;
caption: string | null;
} & ReferenceInfo;
export class EmbedLinkedDocModel extends defineEmbedModel<EmbedLinkedDocBlockProps>(
BlockModel
) {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-linked-doc': EmbedLinkedDocModel;
}
interface BlockModels {
'affine:embed-linked-doc': EmbedLinkedDocModel;
}
}
}
@@ -0,0 +1,22 @@
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedLinkedDocBlockProps,
EmbedLinkedDocModel,
EmbedLinkedDocStyles,
} from './linked-doc-model.js';
const defaultEmbedLinkedDocBlockProps: EmbedLinkedDocBlockProps = {
pageId: '',
style: EmbedLinkedDocStyles[1],
caption: null,
// title & description aliases
title: undefined,
description: undefined,
};
export const EmbedLinkedDocBlockSchema = createEmbedBlockSchema({
name: 'linked-doc',
version: 1,
toModel: () => new EmbedLinkedDocModel(),
props: (): EmbedLinkedDocBlockProps => defaultEmbedLinkedDocBlockProps,
});
@@ -0,0 +1,2 @@
export * from './loom-model.js';
export * from './loom-schema.js';
@@ -0,0 +1,34 @@
import { BlockModel } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export type EmbedLoomBlockUrlData = {
videoId: string | null;
image: string | null;
title: string | null;
description: string | null;
};
export const EmbedLoomStyles: EmbedCardStyle[] = ['video'] as const;
export type EmbedLoomBlockProps = {
style: (typeof EmbedLoomStyles)[number];
url: string;
caption: string | null;
} & EmbedLoomBlockUrlData;
export class EmbedLoomModel extends defineEmbedModel<EmbedLoomBlockProps>(
BlockModel
) {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-loom': EmbedLoomModel;
}
interface BlockModels {
'affine:embed-loom': EmbedLoomModel;
}
}
}
@@ -0,0 +1,24 @@
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedLoomBlockProps,
EmbedLoomModel,
EmbedLoomStyles,
} from './loom-model.js';
const defaultEmbedLoomProps: EmbedLoomBlockProps = {
style: EmbedLoomStyles[0],
url: '',
caption: null,
image: null,
title: null,
description: null,
videoId: null,
};
export const EmbedLoomBlockSchema = createEmbedBlockSchema({
name: 'loom',
version: 1,
toModel: () => new EmbedLoomModel(),
props: (): EmbedLoomBlockProps => defaultEmbedLoomProps,
});
@@ -0,0 +1,2 @@
export * from './synced-doc-model.js';
export * from './synced-doc-schema.js';
@@ -0,0 +1,28 @@
import { BlockModel } from '@blocksuite/store';
import type { ReferenceInfo } from '../../../consts/doc.js';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export const EmbedSyncedDocStyles: EmbedCardStyle[] = ['syncedDoc'];
export type EmbedSyncedDocBlockProps = {
style: EmbedCardStyle;
caption?: string | null;
scale?: number;
} & ReferenceInfo;
export class EmbedSyncedDocModel extends defineEmbedModel<EmbedSyncedDocBlockProps>(
BlockModel
) {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-synced-doc': EmbedSyncedDocModel;
}
interface BlockModels {
'affine:embed-synced-doc': EmbedSyncedDocModel;
}
}
}
@@ -0,0 +1,23 @@
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedSyncedDocBlockProps,
EmbedSyncedDocModel,
EmbedSyncedDocStyles,
} from './synced-doc-model.js';
export const defaultEmbedSyncedDocBlockProps: EmbedSyncedDocBlockProps = {
pageId: '',
style: EmbedSyncedDocStyles[0],
caption: undefined,
scale: undefined,
// title & description aliases
title: undefined,
description: undefined,
};
export const EmbedSyncedDocBlockSchema = createEmbedBlockSchema({
name: 'synced-doc',
version: 1,
toModel: () => new EmbedSyncedDocModel(),
props: (): EmbedSyncedDocBlockProps => defaultEmbedSyncedDocBlockProps,
});
@@ -0,0 +1,2 @@
export * from './youtube-model.js';
export * from './youtube-schema.js';
@@ -0,0 +1,37 @@
import { BlockModel } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../../utils/index.js';
import { defineEmbedModel } from '../../../utils/index.js';
export type EmbedYoutubeBlockUrlData = {
videoId: string | null;
image: string | null;
title: string | null;
description: string | null;
creator: string | null;
creatorUrl: string | null;
creatorImage: string | null;
};
export const EmbedYoutubeStyles: EmbedCardStyle[] = ['video'] as const;
export type EmbedYoutubeBlockProps = {
style: (typeof EmbedYoutubeStyles)[number];
url: string;
caption: string | null;
} & EmbedYoutubeBlockUrlData;
export class EmbedYoutubeModel extends defineEmbedModel<EmbedYoutubeBlockProps>(
BlockModel
) {}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:embed-youtube': EmbedYoutubeModel;
}
interface BlockModels {
'affine:embed-youtube': EmbedYoutubeModel;
}
}
}
@@ -0,0 +1,27 @@
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedYoutubeBlockProps,
EmbedYoutubeModel,
EmbedYoutubeStyles,
} from './youtube-model.js';
const defaultEmbedYoutubeProps: EmbedYoutubeBlockProps = {
style: EmbedYoutubeStyles[0],
url: '',
caption: null,
image: null,
title: null,
description: null,
creator: null,
creatorUrl: null,
creatorImage: null,
videoId: null,
};
export const EmbedYoutubeBlockSchema = createEmbedBlockSchema({
name: 'youtube',
version: 1,
toModel: () => new EmbedYoutubeModel(),
props: (): EmbedYoutubeBlockProps => defaultEmbedYoutubeProps,
});
@@ -0,0 +1,148 @@
import type {
GfxBlockElementModel,
GfxCompatibleProps,
GfxElementGeometry,
GfxGroupCompatibleInterface,
GfxModel,
PointTestOptions,
} from '@blocksuite/block-std/gfx';
import {
canSafeAddToContainer,
descendantElementsImpl,
generateKeyBetweenV2,
GfxCompatible,
gfxGroupCompatibleSymbol,
hasDescendantElementImpl,
} from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
import type { Color } from '../../consts/index.js';
export type FrameBlockProps = {
title: Text;
background: Color;
childElementIds?: Record<string, boolean>;
presentationIndex?: string;
} & GfxCompatibleProps;
export const FrameBlockSchema = defineBlockSchema({
flavour: 'affine:frame',
props: (internal): FrameBlockProps => ({
title: internal.Text(),
background: '--affine-palette-transparent',
xywh: `[0,0,100,100]`,
index: 'a0',
childElementIds: Object.create(null),
presentationIndex: generateKeyBetweenV2(null, null),
lockedBySelf: false,
}),
metadata: {
version: 1,
role: 'content',
parent: ['affine:surface'],
children: [],
},
toModel: () => {
return new FrameBlockModel();
},
});
export class FrameBlockModel
extends GfxCompatible<FrameBlockProps>(BlockModel)
implements GfxElementGeometry, GfxGroupCompatibleInterface
{
[gfxGroupCompatibleSymbol] = true as const;
get childElements() {
if (!this.surface) return [];
const elements: GfxModel[] = [];
for (const key of this.childIds) {
const element =
this.surface.getElementById(key) ||
(this.surface.doc.getBlockById(key) as GfxBlockElementModel);
element && elements.push(element);
}
return elements;
}
get childIds() {
return this.childElementIds ? Object.keys(this.childElementIds) : [];
}
get descendantElements(): GfxModel[] {
return descendantElementsImpl(this);
}
addChild(element: GfxModel) {
if (!canSafeAddToContainer(this, element)) return;
this.doc.transact(() => {
this.childElementIds = { ...this.childElementIds, [element.id]: true };
});
}
addChildren(elements: GfxModel[]): void {
elements = [...new Set(elements)].filter(element =>
canSafeAddToContainer(this, element)
);
const newChildren: Record<string, boolean> = {};
for (const element of elements) {
const id = typeof element === 'string' ? element : element.id;
newChildren[id] = true;
}
this.doc.transact(() => {
this.childElementIds = {
...this.childElementIds,
...newChildren,
};
});
}
override containsBound(bound: Bound): boolean {
return this.elementBound.contains(bound);
}
hasChild(element: GfxModel): boolean {
return this.childElementIds ? element.id in this.childElementIds : false;
}
hasDescendant(element: GfxModel): boolean {
return hasDescendantElementImpl(this, element);
}
override includesPoint(x: number, y: number, _: PointTestOptions): boolean {
const bound = Bound.deserialize(this.xywh);
return bound.isPointInBound([x, y]);
}
override intersectsBound(selectedBound: Bound): boolean {
const bound = Bound.deserialize(this.xywh);
return (
bound.isIntersectWithBound(selectedBound) || selectedBound.contains(bound)
);
}
removeChild(element: GfxModel): void {
this.doc.transact(() => {
this.childElementIds && delete this.childElementIds[element.id];
});
}
}
declare global {
namespace BlockSuite {
interface EdgelessBlockModelMap {
'affine:frame': FrameBlockModel;
}
interface BlockModels {
'affine:frame': FrameBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './frame-model.js';
@@ -0,0 +1,55 @@
import type {
GfxCommonBlockProps,
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import { ImageBlockTransformer } from './image-transformer.js';
export type ImageBlockProps = {
caption?: string;
sourceId?: string;
width?: number;
height?: number;
rotate: number;
size?: number;
} & Omit<GfxCommonBlockProps, 'scale'>;
const defaultImageProps: ImageBlockProps = {
caption: '',
sourceId: '',
width: 0,
height: 0,
index: 'a0',
xywh: '[0,0,0,0]',
lockedBySelf: false,
rotate: 0,
size: -1,
};
export const ImageBlockSchema = defineBlockSchema({
flavour: 'affine:image',
props: () => defaultImageProps,
metadata: {
version: 1,
role: 'content',
},
transformer: () => new ImageBlockTransformer(),
toModel: () => new ImageBlockModel(),
});
export class ImageBlockModel
extends GfxCompatible<ImageBlockProps>(BlockModel)
implements GfxElementGeometry {}
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:image': ImageBlockModel;
}
interface EdgelessBlockModelMap {
'affine:image': ImageBlockModel;
}
}
}
@@ -0,0 +1,17 @@
import type { FromSnapshotPayload, SnapshotNode } from '@blocksuite/store';
import { BaseBlockTransformer } from '@blocksuite/store';
import type { ImageBlockProps } from './image-model.js';
export class ImageBlockTransformer extends BaseBlockTransformer<ImageBlockProps> {
override async fromSnapshot(
payload: FromSnapshotPayload
): Promise<SnapshotNode<ImageBlockProps>> {
const snapshotRet = await super.fromSnapshot(payload);
const sourceId = snapshotRet.props.sourceId;
if (!payload.assets.isEmpty() && sourceId && !sourceId.startsWith('/'))
await payload.assets.writeToBlob(sourceId);
return snapshotRet;
}
}
@@ -0,0 +1,2 @@
export * from './image-model.js';
export * from './image-transformer.js';
@@ -0,0 +1,15 @@
export * from './attachment/index.js';
export * from './bookmark/index.js';
export * from './code/index.js';
export * from './database/index.js';
export * from './divider/index.js';
export * from './edgeless-text/index.js';
export * from './embed/index.js';
export * from './frame/index.js';
export * from './image/index.js';
export * from './latex/index.js';
export * from './list/index.js';
export * from './note/index.js';
export * from './paragraph/index.js';
export * from './root/index.js';
export * from './surface-ref/index.js';
@@ -0,0 +1 @@
export * from './latex-model.js';
@@ -0,0 +1,51 @@
import {
type GfxCommonBlockProps,
GfxCompatible,
type GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export type LatexProps = {
latex: string;
} & GfxCommonBlockProps;
export const LatexBlockSchema = defineBlockSchema({
flavour: 'affine:latex',
props: (): LatexProps => ({
xywh: '[0,0,16,16]',
index: 'a0',
lockedBySelf: false,
scale: 1,
rotate: 0,
latex: '',
}),
metadata: {
version: 1,
role: 'content',
parent: [
'affine:note',
'affine:edgeless-text',
'affine:paragraph',
'affine:list',
],
},
toModel: () => {
return new LatexBlockModel();
},
});
export class LatexBlockModel
extends GfxCompatible<LatexProps>(BlockModel)
implements GfxElementGeometry {}
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:latex': LatexBlockModel;
}
interface EdgelessBlockModelMap {
'affine:latex': LatexBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './list-model.js';
@@ -0,0 +1,48 @@
import type { SchemaToModel, Text } from '@blocksuite/store';
import { defineBlockSchema } from '@blocksuite/store';
// `toggle` type has been deprecated, do not use it
export type ListType = 'bulleted' | 'numbered' | 'todo' | 'toggle';
export interface ListProps {
type: ListType;
text: Text;
checked: boolean;
collapsed: boolean;
order: number | null;
}
export const ListBlockSchema = defineBlockSchema({
flavour: 'affine:list',
props: internal =>
({
type: 'bulleted',
text: internal.Text(),
checked: false,
collapsed: false,
// number type only for numbered list
order: null,
}) as ListProps,
metadata: {
version: 1,
role: 'content',
parent: [
'affine:note',
'affine:database',
'affine:list',
'affine:paragraph',
'affine:edgeless-text',
],
},
});
export type ListBlockModel = SchemaToModel<typeof ListBlockSchema>;
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:list': ListBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './note-model.js';
@@ -0,0 +1,126 @@
import type {
GfxCompatibleProps,
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import {
type Color,
DEFAULT_NOTE_BACKGROUND_COLOR,
DEFAULT_NOTE_BORDER_SIZE,
DEFAULT_NOTE_BORDER_STYLE,
DEFAULT_NOTE_CORNER,
DEFAULT_NOTE_HEIGHT,
DEFAULT_NOTE_SHADOW,
DEFAULT_NOTE_WIDTH,
NoteDisplayMode,
type StrokeStyle,
} from '../../consts/index.js';
export const NoteBlockSchema = defineBlockSchema({
flavour: 'affine:note',
props: (): NoteProps => ({
xywh: `[0,0,${DEFAULT_NOTE_WIDTH},${DEFAULT_NOTE_HEIGHT}]`,
background: DEFAULT_NOTE_BACKGROUND_COLOR,
index: 'a0',
lockedBySelf: false,
hidden: false,
displayMode: NoteDisplayMode.DocAndEdgeless,
edgeless: {
style: {
borderRadius: DEFAULT_NOTE_CORNER,
borderSize: DEFAULT_NOTE_BORDER_SIZE,
borderStyle: DEFAULT_NOTE_BORDER_STYLE,
shadowType: DEFAULT_NOTE_SHADOW,
},
},
}),
metadata: {
version: 1,
role: 'hub',
parent: ['affine:page'],
children: [
'affine:paragraph',
'affine:list',
'affine:code',
'affine:divider',
'affine:database',
'affine:data-view',
'affine:image',
'affine:bookmark',
'affine:attachment',
'affine:surface-ref',
'affine:embed-*',
'affine:latex',
],
},
toModel: () => {
return new NoteBlockModel();
},
});
export type NoteProps = {
background: Color;
displayMode: NoteDisplayMode;
edgeless: NoteEdgelessProps;
/**
* @deprecated
* use `displayMode` instead
* hidden:true -> displayMode:NoteDisplayMode.EdgelessOnly:
* means the note is visible only in the edgeless mode
* hidden:false -> displayMode:NoteDisplayMode.DocAndEdgeless:
* means the note is visible in the doc and edgeless mode
*/
hidden: boolean;
} & GfxCompatibleProps;
export type NoteEdgelessProps = {
style: {
borderRadius: number;
borderSize: number;
borderStyle: StrokeStyle;
shadowType: string;
};
collapse?: boolean;
collapsedHeight?: number;
scale?: number;
};
export class NoteBlockModel
extends GfxCompatible<NoteProps>(BlockModel)
implements GfxElementGeometry
{
private _isSelectable(): boolean {
return this.displayMode !== NoteDisplayMode.DocOnly;
}
override containsBound(bounds: Bound): boolean {
if (!this._isSelectable()) return false;
return super.containsBound(bounds);
}
override includesPoint(x: number, y: number): boolean {
if (!this._isSelectable()) return false;
const bound = Bound.deserialize(this.xywh);
return bound.isPointInBound([x, y], 0);
}
override intersectsBound(bound: Bound): boolean {
if (!this._isSelectable()) return false;
return super.intersectsBound(bound);
}
}
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:note': NoteBlockModel;
}
interface EdgelessBlockModelMap {
'affine:note': NoteBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './paragraph-model.js';
@@ -0,0 +1,52 @@
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
export type ParagraphType =
| 'text'
| 'quote'
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'h5'
| 'h6';
export type ParagraphProps = {
type: ParagraphType;
text: Text;
collapsed: boolean;
};
export const ParagraphBlockSchema = defineBlockSchema({
flavour: 'affine:paragraph',
props: (internal): ParagraphProps => ({
type: 'text',
text: internal.Text(),
collapsed: false,
}),
metadata: {
version: 1,
role: 'content',
parent: [
'affine:note',
'affine:database',
'affine:paragraph',
'affine:list',
'affine:edgeless-text',
],
},
toModel: () => new ParagraphBlockModel(),
});
export class ParagraphBlockModel extends BlockModel<ParagraphProps> {
override flavour!: 'affine:paragraph';
override text!: Text;
}
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:paragraph': ParagraphBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './root-block-model.js';
@@ -0,0 +1,45 @@
import type { Text } from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export type RootBlockProps = {
title: Text;
};
export class RootBlockModel extends BlockModel<RootBlockProps> {
constructor() {
super();
this.created.once(() => {
this.doc.slots.rootAdded.on(id => {
const model = this.doc.getBlockById(id);
if (model instanceof RootBlockModel) {
const newDocMeta = this.doc.collection.meta.getDocMeta(model.doc.id);
if (!newDocMeta || newDocMeta.title !== model.title.toString()) {
this.doc.collection.setDocMeta(model.doc.id, {
title: model.title.toString(),
});
}
}
});
});
}
}
export const RootBlockSchema = defineBlockSchema({
flavour: 'affine:page',
props: (internal): RootBlockProps => ({
title: internal.Text(),
}),
metadata: {
version: 2,
role: 'root',
},
toModel: () => new RootBlockModel(),
});
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:page': RootBlockModel;
}
}
}
@@ -0,0 +1 @@
export * from './surface-ref-model.js';
@@ -0,0 +1,31 @@
import { defineBlockSchema, type SchemaToModel } from '@blocksuite/store';
export type SurfaceRefProps = {
reference: string;
caption: string;
refFlavour: string;
};
export const SurfaceRefBlockSchema = defineBlockSchema({
flavour: 'affine:surface-ref',
props: () =>
({
reference: '',
caption: '',
}) as SurfaceRefProps,
metadata: {
version: 1,
role: 'content',
parent: ['affine:note', 'affine:paragraph', 'affine:list'],
},
});
export type SurfaceRefBlockModel = SchemaToModel<typeof SurfaceRefBlockSchema>;
declare global {
namespace BlockSuite {
interface BlockModels {
'affine:surface-ref': SurfaceRefBlockModel;
}
}
}