Compare commits

..

2 Commits

Author SHA1 Message Date
zzj3720
9bee2cb0fa fix(editor): improve string conversion logic for checkbox property
- Add a FALSE_VALUES set containing various falsy string representations

- Support Chinese negation terms like "否", "不", "错", etc.

- Optimize the implementation of cellFromString method
2025-02-26 00:11:36 +08:00
zzj3720
1addd17d64 fix(editor): table block supports parsing rich text 2025-02-25 18:52:13 +08:00
207 changed files with 1494 additions and 2470 deletions

7
Cargo.lock generated
View File

@@ -69,7 +69,6 @@ dependencies = [
"core-foundation",
"coreaudio-rs",
"dispatch2",
"libc",
"napi",
"napi-build",
"napi-derive",
@@ -3111,6 +3110,7 @@ version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [
"indexmap",
"itoa",
"memchr",
"ryu",
@@ -3912,13 +3912,14 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.24.7"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75"
checksum = "1a802c93485fb6781d27e27cb5927f6b00ff8d26b56c70af87267be7e99def97"
dependencies = [
"cc",
"regex",
"regex-syntax 0.8.5",
"serde_json",
"streaming-iterator",
"tree-sitter-language",
]

View File

@@ -24,7 +24,6 @@ dispatch2 = "0.2"
dotenvy = "0.15"
file-format = { version = "0.26", features = ["reader"] }
homedir = "0.3"
libc = "0.2"
mimalloc = "0.1"
napi = { version = "3.0.0-alpha.12", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi-build = { version = "2" }

View File

@@ -5,11 +5,7 @@ import {
type InsertToPosition,
} from '@blocksuite/affine-shared/utils';
import type { DataViewDataType } from '@blocksuite/data-view';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
type Props = {
title: string;
@@ -97,6 +93,3 @@ export const DataViewBlockSchema = defineBlockSchema({
return new DataViewBlockModel();
},
});
export const DataViewBlockSchemaExtension =
BlockSchemaExtension(DataViewBlockSchema);

View File

@@ -9,7 +9,6 @@ import {
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import { findAncestorModel } from '@blocksuite/affine-shared/utils';
import type { BlockService } from '@blocksuite/block-std';
import type { GfxCompatibleProps } from '@blocksuite/block-std/gfx';
import type { BlockModel } from '@blocksuite/store';
@@ -58,15 +57,7 @@ export class EmbedBlockComponent<
) {
this.style.display = 'block';
const insideNote = findAncestorModel(
this.model,
m => m.flavour === 'affine:note'
);
if (
!insideNote &&
this.std.get(DocModeProvider).getEditorMode() === 'edgeless'
) {
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
this.style.minWidth = `${EMBED_CARD_MIN_WIDTH}px`;
}
}

View File

@@ -2,7 +2,6 @@ import { css, html } from 'lit';
export const styles = css`
.affine-embed-github-block {
container: affine-embed-github-block / inline-size;
box-sizing: border-box;
display: flex;
width: 100%;
@@ -25,7 +24,6 @@ export const styles = css`
padding: 12px;
border-radius: var(--1, 0px);
opacity: var(--add, 1);
overflow: hidden;
}
.affine-embed-github-content-title {
@@ -378,15 +376,6 @@ export const styles = css`
display: none;
}
}
@container affine-embed-github-block (width < 375px) {
.affine-embed-github-content {
width: 100%;
}
.affine-embed-github-banner {
display: none;
}
}
`;
export const GithubIcon = html`<svg

View File

@@ -26,9 +26,9 @@ import {
} from '@blocksuite/affine-shared/utils';
import {
BlockSelection,
BlockServiceWatcher,
BlockStdScope,
type EditorHost,
LifeCycleWatcher,
} from '@blocksuite/block-std';
import {
GfxControllerIdentifier,
@@ -124,31 +124,27 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
this.std.getOptional(EditorSettingProvider) ??
signal(GeneralSettingSchema.parse({}));
class EmbedSyncedDocWatcher extends LifeCycleWatcher {
static override key = 'embed-synced-doc-watcher';
class EmbedSyncedDocWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:embed-synced-doc';
override mounted(): void {
const { view } = this.std;
view.viewUpdated.on(payload => {
if (
payload.type !== 'block' ||
payload.view.model.flavour !== 'affine:embed-synced-doc'
) {
return;
}
const nextComponent = payload.view as EmbedSyncedDocBlockComponent;
if (payload.method === 'add') {
override mounted() {
const disposableGroup = this.blockService.disposables;
const slots = this.blockService.specSlots;
disposableGroup.add(
slots.viewConnected.on(({ component }) => {
const nextComponent = component as EmbedSyncedDocBlockComponent;
nextComponent.depth = nextDepth;
currentDisposables.add(() => {
nextComponent.depth = 0;
});
return;
}
if (payload.method === 'delete') {
})
);
disposableGroup.add(
slots.viewDisconnected.on(({ component }) => {
const nextComponent = component as EmbedSyncedDocBlockComponent;
nextComponent.depth = 0;
return;
}
});
})
);
}
}
@@ -235,7 +231,6 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
[theme]: true,
surface: false,
selected: this.selected$.value,
'show-hover-border': true,
})}
@click=${this._handleClick}
style=${containerStyleMap}

View File

@@ -57,13 +57,10 @@ export const blockStyles = css`
}
.affine-embed-synced-doc-container {
border: 1px solid transparent;
border: 1px solid var(--affine-border-color);
border-radius: 8px;
overflow: hidden;
}
.affine-embed-synced-doc-container.show-hover-border:hover {
border-color: var(--affine-border-color);
}
.affine-embed-synced-doc-container.page {
display: block;
width: 100%;
@@ -154,12 +151,7 @@ export const blockStyles = css`
}
.affine-embed-synced-doc-container.surface {
border-color: var(--affine-border-color);
background: var(--affine-background-primary-color);
affine-preview-root {
padding: 0 24px;
}
}
.affine-embed-synced-doc-container

View File

@@ -64,6 +64,7 @@ import {
BlockSnapshotSchema,
fromJSON,
type SliceSnapshot,
Transformer,
} from '@blocksuite/store';
import DOMPurify from 'dompurify';
import * as Y from 'yjs';
@@ -372,7 +373,15 @@ export class EdgelessClipboardController extends PageClipboard {
if (mayBeSurfaceDataJson !== undefined) {
const elementsRawData = JSON.parse(mayBeSurfaceDataJson);
const { snapshot, blobs } = elementsRawData;
const job = this.std.store.getTransformer();
const job = new Transformer({
schema: this.std.workspace.schema,
blobCRUD: this.std.workspace.blobSync,
docCRUD: {
create: (id: string) => this.std.workspace.createDoc({ id }),
get: (id: string) => this.std.workspace.getDoc(id),
delete: (id: string) => this.std.workspace.removeDoc(id),
},
});
const map = job.assetsManager.getAssets();
decodeClipboardBlobs(blobs, map);
for (const blobId of map.keys()) {
@@ -1368,7 +1377,15 @@ export async function prepareClipboardData(
selectedAll: GfxModel[],
std: BlockStdScope
) {
const job = std.store.getTransformer();
const job = new Transformer({
schema: std.workspace.schema,
blobCRUD: std.workspace.blobSync,
docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }),
get: (id: string) => std.workspace.getDoc(id),
delete: (id: string) => std.workspace.removeDoc(id),
},
});
const selected = await Promise.all(
selectedAll.map(async selected => {
const data = serializeElement(selected, selectedAll, job);

View File

@@ -1,9 +1,9 @@
import type { FrameBlockModel } from '@blocksuite/affine-model';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import {
BlockServiceWatcher,
BlockStdScope,
type EditorHost,
LifeCycleWatcher,
ShadowlessElement,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
@@ -117,26 +117,22 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
private _initSpec() {
const refreshViewport = this._refreshViewport.bind(this);
class FramePreviewWatcher extends LifeCycleWatcher {
static override key = 'frame-preview-watcher';
class FramePreviewWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
override mounted() {
const { view } = this.std;
view.viewUpdated.on(payload => {
if (
payload.type !== 'block' ||
payload.method !== 'add' ||
payload.view.model.flavour !== 'affine:page'
) {
return;
}
const edgelessBlock =
payload.view as EdgelessRootPreviewBlockComponent;
edgelessBlock.editorViewportSelector = 'frame-preview-viewport';
edgelessBlock.service.viewport.sizeUpdated.once(() => {
refreshViewport();
});
});
const blockService = this.blockService;
blockService.disposables.add(
blockService.specSlots.viewConnected.on(({ component }) => {
const edgelessBlock =
component as EdgelessRootPreviewBlockComponent;
edgelessBlock.editorViewportSelector = 'frame-preview-viewport';
edgelessBlock.service.viewport.sizeUpdated.once(() => {
refreshViewport();
});
})
);
}
}
this._previewSpec.extend([FramePreviewWatcher]);

View File

@@ -1,9 +1,5 @@
import type { Color, ColorScheme, Palette } from '@blocksuite/affine-model';
import {
DefaultTheme,
isTransparent,
resolveColor,
} from '@blocksuite/affine-model';
import { isTransparent, resolveColor } from '@blocksuite/affine-model';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { ColorEvent } from '@blocksuite/affine-shared/utils';
import { css, html, LitElement, nothing, svg, type TemplateResult } from 'lit';
@@ -257,7 +253,7 @@ export class EdgelessColorPanel extends LitElement {
accessor openColorPicker!: (e: MouseEvent) => void;
@property({ type: Array })
accessor palettes: readonly Palette[] = DefaultTheme.Palettes;
accessor palettes: readonly Palette[] = [];
@property({ attribute: false })
accessor theme!: ColorScheme;

View File

@@ -1,4 +1,8 @@
import { type ColorScheme, type StrokeStyle } from '@blocksuite/affine-model';
import {
type ColorScheme,
DefaultTheme,
type StrokeStyle,
} from '@blocksuite/affine-model';
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import { css, html, LitElement } from 'lit';
@@ -40,6 +44,7 @@ export class StrokeStylePanel extends WithDisposable(LitElement) {
aria-label="Border colors"
.value=${this.strokeColor}
.theme=${this.theme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${this.hollowCircle}
@select=${(e: ColorEvent) => this.setStrokeColor(e)}
>

View File

@@ -65,7 +65,7 @@ export class EdgelessBrushMenu extends EdgelessToolbarToolMixin(
class="one-way"
.value=${this._props$.value.color}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorShortPalettes}
.palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker')}

View File

@@ -133,7 +133,7 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
class="one-way"
.value=${stroke}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorShortPalettes}
.palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker')}

View File

@@ -75,10 +75,9 @@ export class EdgelessShapeMenu extends SignalWatcher(
const filled = !isTransparent(value);
const fillColor = value;
const strokeColor = filled
? DefaultTheme.StrokeColorShortPalettes.find(
palette => palette.key === key
)?.value
: DefaultTheme.StrokeColorShortMap.Grey;
? DefaultTheme.StrokeColorPalettes.find(palette => palette.key === key)
?.value
: DefaultTheme.StrokeColorMap.Grey;
const { shapeName } = this._props$.value;
this.edgeless.std
@@ -174,7 +173,7 @@ export class EdgelessShapeMenu extends SignalWatcher(
class="one-way"
.value=${fillColor}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.FillColorShortPalettes}
.palettes=${DefaultTheme.FillColorPalettes}
.hasTransparent=${!this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker')}

View File

@@ -33,7 +33,7 @@ export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
class="one-way"
.value=${this.color}
.theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorShortPalettes}
.palettes=${DefaultTheme.StrokeColorPalettes}
@select=${(e: ColorEvent) => this.onChange({ color: e.detail })}
></edgeless-color-panel>
</div>

View File

@@ -2,14 +2,11 @@ import { autoConnectWidget } from '@blocksuite/affine-widget-edgeless-auto-conne
import { frameTitleWidget } from '@blocksuite/affine-widget-frame-title';
import { edgelessRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection';
import {
BlockServiceWatcher,
BlockViewExtension,
LifeCycleWatcher,
WidgetViewExtension,
} from '@blocksuite/block-std';
import {
GfxControllerIdentifier,
ToolController,
} from '@blocksuite/block-std/gfx';
import { ToolController } from '@blocksuite/block-std/gfx';
import type { ExtensionType } from '@blocksuite/store';
import { literal, unsafeStatic } from 'lit/static-html.js';
@@ -59,12 +56,17 @@ export const edgelessToolbarWidget = WidgetViewExtension(
literal`${unsafeStatic(EDGELESS_TOOLBAR_WIDGET)}`
);
class EdgelessLocker extends LifeCycleWatcher {
static override key = 'edgeless-locker';
class EdgelessLocker extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';
override mounted() {
const { viewport } = this.std.get(GfxControllerIdentifier);
viewport.locked = true;
const service = this.blockService;
service.disposables.add(
service.specSlots.viewConnected.on(({ service }) => {
// Does not allow the user to move and zoom.
(service as EdgelessRootService).locked = true;
})
);
}
}

View File

@@ -16,7 +16,7 @@ import {
type DocSnapshot,
DocSnapshotSchema,
type SnapshotNode,
type Transformer,
Transformer,
} from '@blocksuite/store';
import type * as Y from 'yjs';
/**
@@ -90,7 +90,16 @@ export class TemplateJob {
type: TemplateType;
constructor({ model, type, middlewares }: TemplateJobConfig) {
this.job = model.doc.getTransformer();
this.job = new Transformer({
schema: model.doc.workspace.schema,
blobCRUD: model.doc.workspace.blobSync,
docCRUD: {
create: (id: string) => model.doc.workspace.createDoc({ id }),
get: (id: string) => model.doc.workspace.getDoc(id),
delete: (id: string) => model.doc.workspace.removeDoc(id),
},
middlewares: [],
});
this.model = model;
this.type = TEMPLATE_TYPES.includes(type as TemplateType)
? (type as TemplateType)
@@ -311,7 +320,8 @@ export class TemplateJob {
from: Record<string, Record<string, unknown>>,
to: Y.Map<Y.Map<unknown>>
) {
const schema = this.model.doc.schema.get('affine:surface');
const schema =
this.model.doc.workspace.schema.flavourSchemaMap.get('affine:surface');
const surfaceTransformer = schema?.transformer?.(
new Map()
) as SurfaceBlockTransformer;

View File

@@ -19,7 +19,7 @@ import {
isGfxGroupCompatibleModel,
type SerializedElement,
} from '@blocksuite/block-std/gfx';
import type { BlockSnapshot, Transformer } from '@blocksuite/store';
import { type BlockSnapshot, Transformer } from '@blocksuite/store';
/**
* return all elements in the tree of the elements
@@ -40,7 +40,15 @@ export function getSortedCloneElements(elements: GfxModel[]) {
export function prepareCloneData(elements: GfxModel[], std: BlockStdScope) {
elements = sortEdgelessElements(elements);
const job = std.store.getTransformer();
const job = new Transformer({
schema: std.workspace.schema,
blobCRUD: std.workspace.blobSync,
docCRUD: {
create: (id: string) => std.workspace.createDoc({ id }),
get: (id: string) => std.workspace.getDoc(id),
delete: (id: string) => std.workspace.removeDoc(id),
},
});
const res = elements.map(element => {
const data = serializeElement(element, elements, job);
return data;

View File

@@ -10,6 +10,7 @@ export class PreviewRootBlockComponent extends BlockComponent {
static override styles = css`
affine-preview-root {
display: block;
padding: 0 24px;
}
`;

View File

@@ -8,21 +8,19 @@ import {
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import type { Schema, Store, Workspace } from '@blocksuite/store';
import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js';
type ImportHTMLToDocOptions = {
collection: Workspace;
schema: Schema;
html: string;
fileName?: string;
};
type ImportHTMLZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -43,10 +41,19 @@ function getProvider() {
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = doc.getTransformer([
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
]);
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
],
});
const snapshot = job.docToSnapshot(doc);
const adapter = new HtmlAdapter(job, provider);
if (!snapshot) {
@@ -80,20 +87,18 @@ async function exportDoc(doc: Store) {
*
* @param options - The import options.
* @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.html - The HTML content to import.
* @param options.fileName - Optional filename for the imported doc.
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails.
*/
async function importHTMLToDoc({
collection,
schema,
html,
fileName,
}: ImportHTMLToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -122,15 +127,10 @@ async function importHTMLToDoc({
*
* @param options - The import options.
* @param options.collection - The target doc collection.
* @param options.schema - The schema of the target doc collection.
* @param options.imported - The zip file as a Blob.
* @returns A Promise that resolves to an array of IDs of the newly created docs.
*/
async function importHTMLZip({
collection,
schema,
imported,
}: ImportHTMLZipOptions) {
async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
const provider = getProvider();
const unzip = new Unzip();
await unzip.load(imported);
@@ -161,7 +161,7 @@ async function importHTMLZip({
htmlBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),

View File

@@ -9,7 +9,7 @@ import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { assertExists, sha } from '@blocksuite/global/utils';
import type { Schema, Store, Workspace } from '@blocksuite/store';
import type { Store, Workspace } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import { createAssetsArchive, download, Unzip } from './utils.js';
@@ -31,14 +31,12 @@ type ImportMarkdownToBlockOptions = {
type ImportMarkdownToDocOptions = {
collection: Workspace;
schema: Schema;
markdown: string;
fileName?: string;
};
type ImportMarkdownZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -49,10 +47,19 @@ type ImportMarkdownZipOptions = {
*/
async function exportDoc(doc: Store) {
const provider = getProvider();
const job = doc.getTransformer([
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
]);
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
docLinkBaseURLMiddleware(doc.workspace.id),
titleMiddleware(doc.workspace.meta.docMetas),
],
});
const snapshot = job.docToSnapshot(doc);
const adapter = new MarkdownAdapter(job, provider);
@@ -100,10 +107,19 @@ async function importMarkdownToBlock({
blockId,
}: ImportMarkdownToBlockOptions) {
const provider = getProvider();
const job = doc.getTransformer([
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware(doc.workspace.id),
]);
const job = new Transformer({
schema: doc.schema,
blobCRUD: doc.blobSync,
docCRUD: {
create: (id: string) => doc.workspace.createDoc({ id }),
get: (id: string) => doc.workspace.getDoc(id),
delete: (id: string) => doc.workspace.removeDoc(id),
},
middlewares: [
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware(doc.workspace.id),
],
});
const adapter = new MarkdownAdapter(job, provider);
const snapshot = await adapter.toSliceSnapshot({
file: markdown,
@@ -127,20 +143,18 @@ async function importMarkdownToBlock({
* Imports Markdown content into a new doc within a collection.
* @param options Object containing import options
* @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.markdown The Markdown content to import
* @param options.fileName Optional filename for the imported doc
* @returns A Promise that resolves to the ID of the newly created doc, or undefined if import fails
*/
async function importMarkdownToDoc({
collection,
schema,
markdown,
fileName,
}: ImportMarkdownToDocOptions) {
const provider = getProvider();
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -168,13 +182,11 @@ async function importMarkdownToDoc({
* Imports a zip file containing Markdown files and assets into a collection.
* @param options Object containing import options
* @param options.collection The target doc collection
* @param options.schema The schema of the target doc collection
* @param options.imported The zip file as a Blob
* @returns A Promise that resolves to an array of IDs of the newly created docs
*/
async function importMarkdownZip({
collection,
schema,
imported,
}: ImportMarkdownZipOptions) {
const provider = getProvider();
@@ -207,7 +219,7 @@ async function importMarkdownZip({
markdownBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),

View File

@@ -3,18 +3,12 @@ import { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { Container } from '@blocksuite/global/di';
import { sha } from '@blocksuite/global/utils';
import {
extMimeMap,
type Schema,
Transformer,
type Workspace,
} from '@blocksuite/store';
import { extMimeMap, Transformer, type Workspace } from '@blocksuite/store';
import { Unzip } from './utils.js';
type ImportNotionZipOptions = {
collection: Workspace;
schema: Schema;
imported: Blob;
};
@@ -32,7 +26,6 @@ function getProvider() {
*
* @param options - The options for importing.
* @param options.collection - The BlockSuite document collection.
* @param options.schema - The schema of the BlockSuite document collection.
* @param options.imported - The imported zip file as a Blob.
*
* @returns A promise that resolves to an object containing:
@@ -43,7 +36,6 @@ function getProvider() {
*/
async function importNotionZip({
collection,
schema,
imported,
}: ImportNotionZipOptions) {
const provider = getProvider();
@@ -125,7 +117,7 @@ async function importNotionZip({
}
const pagePromises = Array.from(pagePaths).map(async path => {
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),

View File

@@ -3,19 +3,15 @@ import {
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
import { sha } from '@blocksuite/global/utils';
import type { DocSnapshot, Schema, Store, Workspace } from '@blocksuite/store';
import type { DocSnapshot, Store, Workspace } from '@blocksuite/store';
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
import { download, Unzip, Zip } from '../transformers/utils.js';
async function exportDocs(
collection: Workspace,
schema: Schema,
docs: Store[]
) {
async function exportDocs(collection: Workspace, docs: Store[]) {
const zip = new Zip();
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
@@ -74,11 +70,7 @@ async function exportDocs(
return download(downloadBlob, `${collection.id}.bs.zip`);
}
async function importDocs(
collection: Workspace,
schema: Schema,
imported: Blob
) {
async function importDocs(collection: Workspace, imported: Blob) {
const unzip = new Unzip();
await unzip.load(imported);
@@ -106,7 +98,7 @@ async function importDocs(
}
const job = new Transformer({
schema,
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),

View File

@@ -134,12 +134,13 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="color"
.label="${'Color'}"
.label=${'Color'}
.pick=${this.pickColor}
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -158,6 +159,7 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
<edgeless-color-panel
.value=${selectedColor}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setBrushColor}
>
</edgeless-color-panel>

View File

@@ -373,12 +373,13 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="stroke-color"
.label="${'Stroke style'}"
.label=${'Stroke style'}
.pick=${this.pickColor}
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${true}
>
<div

View File

@@ -13,6 +13,7 @@ import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
import {
type ColorScheme,
DEFAULT_NOTE_HEIGHT,
DefaultTheme,
type FrameBlockModel,
NoteBlockModel,
NoteDisplayMode,
@@ -200,12 +201,13 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="background"
.label="${'Background'}"
.label=${'Background'}
.pick=${this.pickColor}
.color=${background}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -227,6 +229,7 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
<edgeless-color-panel
.value=${background}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setFrameBackground}
>
</edgeless-color-panel>

View File

@@ -338,6 +338,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -361,6 +362,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
aria-label="Fill colors"
.value=${selectedFillColor}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
@select=${this._setShapeFillColor}
>
</edgeless-color-panel>
@@ -388,6 +390,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${DefaultTheme.Palettes}
.hollowCircle=${true}
>
<div
@@ -450,8 +453,8 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
() => html`
<editor-icon-button
aria-label="Add text"
.tooltip="${'Add text'}"
.iconSize="${'20px'}"
.tooltip=${'Add text'}
.iconSize=${'20px'}
@click=${this._addText}
>
${AddTextIcon()}
@@ -462,7 +465,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
'menu',
() => html`
<edgeless-change-text-menu
.elementType="${'shape'}"
.elementType=${'shape'}
.elements=${elements}
.edgeless=${this.edgeless}
></edgeless-change-text-menu>

View File

@@ -344,10 +344,6 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
matchFontFaces.length === 1 &&
matchFontFaces[0].style === selectedFontStyle &&
matchFontFaces[0].weight === selectedFontWeight;
const palettes =
this.elementType === 'shape'
? DefaultTheme.ShapeTextColorPalettes
: DefaultTheme.Palettes;
return join(
[
@@ -393,14 +389,14 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
return html`
<edgeless-color-picker-button
class="text-color"
.label="${'Text color'}"
.label=${'Text color'}
.pick=${this.pickColor}
.isText=${true}
.color=${selectedColor}
.colors=${colors}
.colorType=${type}
.theme=${colorScheme}
.palettes=${palettes}
.palettes=${DefaultTheme.Palettes}
>
</edgeless-color-picker-button>
`;
@@ -422,7 +418,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
<edgeless-color-panel
.value=${selectedColor}
.theme=${colorScheme}
.palettes=${palettes}
.palettes=${DefaultTheme.Palettes}
@select=${this._setTextColor}
></edgeless-color-panel>
</editor-menu-button>

View File

@@ -233,7 +233,6 @@ export function createNewDocMenuGroup(
};
showImportModal({
collection: doc.workspace,
schema: doc.schema,
onSuccess,
onFail,
});

View File

@@ -8,7 +8,7 @@ import {
} from '@blocksuite/affine-components/icons';
import { openFileOrFiles } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import type { Schema, Workspace } from '@blocksuite/store';
import type { Workspace } from '@blocksuite/store';
import { html, LitElement, type PropertyValues } from 'lit';
import { query, state } from 'lit/decorators.js';
@@ -31,7 +31,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
constructor(
private readonly collection: Workspace,
private readonly schema: Schema,
private readonly onSuccess?: OnSuccessHandler,
private readonly onFail?: OnFailHandler,
private readonly abortController = new AbortController()
@@ -64,7 +63,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
}
const pageId = await HtmlTransformer.importHTMLToDoc({
collection: this.collection,
schema: this.schema,
html: text,
fileName,
});
@@ -95,7 +93,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
}
const pageId = await MarkdownTransformer.importMarkdownToDoc({
collection: this.collection,
schema: this.schema,
markdown: text,
fileName,
});
@@ -120,7 +117,6 @@ export class ImportDoc extends WithDisposable(LitElement) {
const { entryId, pageIds, isWorkspaceFile, hasMarkdown } =
await NotionHtmlTransformer.importNotionZip({
collection: this.collection,
schema: this.schema,
imported: file,
});
needLoading && this.abortController.abort();

View File

@@ -1,4 +1,4 @@
import type { Schema, Workspace } from '@blocksuite/store';
import type { Workspace } from '@blocksuite/store';
import {
ImportDoc,
@@ -7,14 +7,12 @@ import {
} from './import-doc.js';
export function showImportModal({
schema,
collection,
onSuccess,
onFail,
container = document.body,
abortController = new AbortController(),
}: {
schema: Schema;
collection: Workspace;
onSuccess?: OnSuccessHandler;
onFail?: OnFailHandler;
@@ -24,7 +22,6 @@ export function showImportModal({
}) {
const importDoc = new ImportDoc(
collection,
schema,
onSuccess,
onFail,
abortController

View File

@@ -20,8 +20,10 @@ import { choose } from 'lit/directives/choose.js';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
import { RootBlockConfigExtension } from '../../root-config.js';
import {
type PageRootBlockComponent,
RootBlockConfigExtension,
} from '../../index.js';
import {
type AFFINE_LINKED_DOC_WIDGET,
getMenus,

View File

@@ -423,9 +423,8 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
override mounted() {
const disposable = this.std.view.viewUpdated.on(payload => {
if (payload.type !== 'block') return;
if (
payload.method === 'add' &&
payload.type === 'add' &&
matchModels(payload.view.model, [RootBlockModel])
) {
disposable.dispose();

View File

@@ -50,11 +50,7 @@ export {
} from './adapters/index.js';
export type { SurfaceContext } from './surface-block.js';
export { SurfaceBlockComponent } from './surface-block.js';
export {
SurfaceBlockModel,
SurfaceBlockSchema,
SurfaceBlockSchemaExtension,
} from './surface-model.js';
export { SurfaceBlockModel, SurfaceBlockSchema } from './surface-model.js';
export type { SurfaceBlockService } from './surface-service.js';
export {
EdgelessSurfaceBlockSpec,

View File

@@ -5,7 +5,7 @@ import type {
import type { SurfaceBlockProps } from '@blocksuite/block-std/gfx';
import { SurfaceBlockModel as BaseSurfaceModel } from '@blocksuite/block-std/gfx';
import { DisposableGroup } from '@blocksuite/global/utils';
import { BlockSchemaExtension, defineBlockSchema } from '@blocksuite/store';
import { defineBlockSchema } from '@blocksuite/store';
import * as Y from 'yjs';
import { elementsCtorMap } from './element-model/index.js';
@@ -36,9 +36,6 @@ export const SurfaceBlockSchema = defineBlockSchema({
toModel: () => new SurfaceBlockModel(),
});
export const SurfaceBlockSchemaExtension =
BlockSchemaExtension(SurfaceBlockSchema);
export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void;
export class SurfaceBlockModel extends BaseSurfaceModel {

View File

@@ -1,5 +1,5 @@
import type { ColorScheme, Palette } from '@blocksuite/affine-model';
import { DefaultTheme, resolveColor } from '@blocksuite/affine-model';
import { resolveColor } from '@blocksuite/affine-model';
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import { html, LitElement } from 'lit';
@@ -188,7 +188,7 @@ export class EdgelessColorPickerButton extends WithDisposable(LitElement) {
accessor menuButton!: EditorMenuButton;
@property({ attribute: false })
accessor palettes: Palette[] = DefaultTheme.Palettes;
accessor palettes: Palette[] = [];
@property({ attribute: false })
accessor pick!: (event: PickColorEvent) => void;

View File

@@ -36,22 +36,20 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
cursor: pointer;
}
.footnote-node {
.footnote-content-default {
display: inline-block;
background: ${unsafeCSSVarV2('block/footnote/numberBgHover')};
color: ${unsafeCSSVarV2('button/pureWhiteText')};
width: 14px;
height: 14px;
line-height: 14px;
font-size: 10px;
font-weight: 400;
border-radius: 50%;
text-align: center;
text-overflow: ellipsis;
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
transition: background 0.3s ease-in-out;
}
.footnote-content-default {
display: inline-block;
background: ${unsafeCSSVarV2('block/footnote/numberBgHover')};
color: ${unsafeCSSVarV2('button/pureWhiteText')};
width: 14px;
height: 14px;
line-height: 14px;
font-size: 10px;
font-weight: 400;
border-radius: 50%;
text-align: center;
text-overflow: ellipsis;
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
transition: background 0.3s ease-in-out;
}
.footnote-node.hover-effect {

View File

@@ -13,11 +13,8 @@ export class ToggleButton extends WithDisposable(ShadowlessElement) {
.toggle-icon {
display: flex;
align-items: start;
justify-content: start;
margin-top: 0.45em;
position: absolute;
width: 16px;
height: 16px;
top: calc((1em - 16px) / 2 + 5px);
left: 0;
transform: translateX(-100%);
border-radius: 4px;
@@ -25,7 +22,6 @@ export class ToggleButton extends WithDisposable(ShadowlessElement) {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.toggle-icon:hover {
background: var(--affine-hover-color);
}

View File

@@ -3,11 +3,7 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import type { EmbedCardStyle } from '../../utils/index.js';
import { AttachmentBlockTransformer } from './attachment-transformer.js';
@@ -90,10 +86,6 @@ export const AttachmentBlockSchema = defineBlockSchema({
toModel: () => new AttachmentBlockModel(),
});
export const AttachmentBlockSchemaExtension = BlockSchemaExtension(
AttachmentBlockSchema
);
export class AttachmentBlockModel
extends GfxCompatible<AttachmentBlockProps>(BlockModel)
implements GfxElementGeometry {}

View File

@@ -3,11 +3,7 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import type { EmbedCardStyle, LinkPreviewData } from '../../utils/index.js';
@@ -58,9 +54,6 @@ export const BookmarkBlockSchema = defineBlockSchema({
toModel: () => new BookmarkBlockModel(),
});
export const BookmarkBlockSchemaExtension =
BlockSchemaExtension(BookmarkBlockSchema);
export class BookmarkBlockModel
extends GfxCompatible<BookmarkBlockProps>(BlockModel)
implements GfxElementGeometry {}

View File

@@ -1,9 +1,4 @@
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
interface CodeBlockProps {
text: Text;
@@ -35,8 +30,6 @@ export const CodeBlockSchema = defineBlockSchema({
toModel: () => new CodeBlockModel(),
});
export const CodeBlockSchemaExtension = BlockSchemaExtension(CodeBlockSchema);
export class CodeBlockModel extends BlockModel<CodeBlockProps> {
override text!: Text;
}

View File

@@ -1,9 +1,5 @@
import type { Text } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import type { Column, SerializedCells, ViewBasicDataType } from './types.js';
@@ -32,6 +28,3 @@ export const DatabaseBlockSchema = defineBlockSchema({
},
toModel: () => new DatabaseBlockModel(),
});
export const DatabaseBlockSchemaExtension =
BlockSchemaExtension(DatabaseBlockSchema);

View File

@@ -1,8 +1,4 @@
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export const DividerBlockSchema = defineBlockSchema({
flavour: 'affine:divider',
@@ -19,6 +15,3 @@ type Props = {
};
export class DividerBlockModel extends BlockModel<Props> {}
export const DividerBlockSchemaExtension =
BlockSchemaExtension(DividerBlockSchema);

View File

@@ -3,11 +3,7 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import { z } from 'zod';
import {
@@ -80,10 +76,6 @@ export const EdgelessTextBlockSchema = defineBlockSchema({
},
});
export const EdgelessTextBlockSchemaExtension = BlockSchemaExtension(
EdgelessTextBlockSchema
);
export class EdgelessTextBlockModel
extends GfxCompatible<EdgelessTextProps>(BlockModel)
implements GfxElementGeometry {}

View File

@@ -1,5 +1,3 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedFigmaBlockProps,
@@ -22,7 +20,3 @@ export const EmbedFigmaBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedFigmaModel(),
props: (): EmbedFigmaBlockProps => defaultEmbedFigmaProps,
});
export const EmbedFigmaBlockSchemaExtension = BlockSchemaExtension(
EmbedFigmaBlockSchema
);

View File

@@ -1,5 +1,3 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedGithubBlockProps,
@@ -31,7 +29,3 @@ export const EmbedGithubBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedGithubModel(),
props: (): EmbedGithubBlockProps => defaultEmbedGithubProps,
});
export const EmbedGithubBlockSchemaExtension = BlockSchemaExtension(
EmbedGithubBlockSchema
);

View File

@@ -1,5 +1,3 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedHtmlBlockProps,
@@ -20,6 +18,3 @@ export const EmbedHtmlBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedHtmlModel(),
props: (): EmbedHtmlBlockProps => defaultEmbedHtmlProps,
});
export const EmbedHtmlBlockSchemaExtension =
BlockSchemaExtension(EmbedHtmlBlockSchema);

View File

@@ -1,5 +1,3 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedLinkedDocBlockProps,
@@ -22,7 +20,3 @@ export const EmbedLinkedDocBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedLinkedDocModel(),
props: (): EmbedLinkedDocBlockProps => defaultEmbedLinkedDocBlockProps,
});
export const EmbedLinkedDocBlockSchemaExtension = BlockSchemaExtension(
EmbedLinkedDocBlockSchema
);

View File

@@ -1,5 +1,3 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedLoomBlockProps,
@@ -24,6 +22,3 @@ export const EmbedLoomBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedLoomModel(),
props: (): EmbedLoomBlockProps => defaultEmbedLoomProps,
});
export const EmbedLoomBlockSchemaExtension =
BlockSchemaExtension(EmbedLoomBlockSchema);

View File

@@ -1,5 +1,3 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedSyncedDocBlockProps,
@@ -23,7 +21,3 @@ export const EmbedSyncedDocBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedSyncedDocModel(),
props: (): EmbedSyncedDocBlockProps => defaultEmbedSyncedDocBlockProps,
});
export const EmbedSyncedDocBlockSchemaExtension = BlockSchemaExtension(
EmbedSyncedDocBlockSchema
);

View File

@@ -1,5 +1,3 @@
import { BlockSchemaExtension } from '@blocksuite/store';
import { createEmbedBlockSchema } from '../../../utils/index.js';
import {
type EmbedYoutubeBlockProps,
@@ -27,7 +25,3 @@ export const EmbedYoutubeBlockSchema = createEmbedBlockSchema({
toModel: () => new EmbedYoutubeModel(),
props: (): EmbedYoutubeBlockProps => defaultEmbedYoutubeProps,
});
export const EmbedYoutubeBlockSchemaExtension = BlockSchemaExtension(
EmbedYoutubeBlockSchema
);

View File

@@ -15,12 +15,7 @@ import {
hasDescendantElementImpl,
} from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
import { z } from 'zod';
import { type Color, ColorSchema, DefaultTheme } from '../../themes/index.js';
@@ -62,8 +57,6 @@ export const FrameBlockSchema = defineBlockSchema({
},
});
export const FrameBlockSchemaExtension = BlockSchemaExtension(FrameBlockSchema);
export class FrameBlockModel
extends GfxCompatible<FrameBlockProps>(BlockModel)
implements GfxElementGeometry, GfxGroupCompatibleInterface

View File

@@ -3,11 +3,7 @@ import type {
GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import { ImageBlockTransformer } from './image-transformer.js';
@@ -44,8 +40,6 @@ export const ImageBlockSchema = defineBlockSchema({
toModel: () => new ImageBlockModel(),
});
export const ImageBlockSchemaExtension = BlockSchemaExtension(ImageBlockSchema);
export class ImageBlockModel
extends GfxCompatible<ImageBlockProps>(BlockModel)
implements GfxElementGeometry {}

View File

@@ -3,11 +3,7 @@ import {
GfxCompatible,
type GfxElementGeometry,
} from '@blocksuite/block-std/gfx';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export type LatexProps = {
latex: string;
@@ -38,8 +34,6 @@ export const LatexBlockSchema = defineBlockSchema({
},
});
export const LatexBlockSchemaExtension = BlockSchemaExtension(LatexBlockSchema);
export class LatexBlockModel
extends GfxCompatible<LatexProps>(BlockModel)
implements GfxElementGeometry {}

View File

@@ -1,9 +1,5 @@
import type { Text } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} 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';
@@ -42,8 +38,6 @@ export const ListBlockSchema = defineBlockSchema({
toModel: () => new ListBlockModel(),
});
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
export class ListBlockModel extends BlockModel<ListProps> {
override text!: Text;
}

View File

@@ -4,11 +4,7 @@ import type {
} from '@blocksuite/block-std/gfx';
import { GfxCompatible } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
import { z } from 'zod';
import {
@@ -25,7 +21,6 @@ import {
StrokeStyleSchema,
} from '../../consts/note';
import { type Color, ColorSchema, DefaultTheme } from '../../themes';
import { TableModelFlavour } from '../table';
export const NoteZodSchema = z
.object({
@@ -52,6 +47,7 @@ export const NoteZodSchema = z
},
},
});
import { TableModelFlavour } from '../table';
export const NoteBlockSchema = defineBlockSchema({
flavour: 'affine:note',
@@ -96,7 +92,6 @@ export const NoteBlockSchema = defineBlockSchema({
},
});
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
export type NoteProps = {
background: Color;
displayMode: NoteDisplayMode;

View File

@@ -1,9 +1,4 @@
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
type Text,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
export type ParagraphType =
| 'text'
@@ -42,9 +37,6 @@ export const ParagraphBlockSchema = defineBlockSchema({
toModel: () => new ParagraphBlockModel(),
});
export const ParagraphBlockSchemaExtension =
BlockSchemaExtension(ParagraphBlockSchema);
export class ParagraphBlockModel extends BlockModel<ParagraphProps> {
override text!: Text;

View File

@@ -1,9 +1,5 @@
import type { Text } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export type RootBlockProps = {
title: Text;
@@ -55,5 +51,3 @@ export const RootBlockSchema = defineBlockSchema({
},
toModel: () => new RootBlockModel(),
});
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);

View File

@@ -1,8 +1,4 @@
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export type SurfaceRefProps = {
reference: string;
@@ -25,8 +21,4 @@ export const SurfaceRefBlockSchema = defineBlockSchema({
toModel: () => new SurfaceRefBlockModel(),
});
export const SurfaceRefBlockSchemaExtension = BlockSchemaExtension(
SurfaceRefBlockSchema
);
export class SurfaceRefBlockModel extends BlockModel<SurfaceRefProps> {}

View File

@@ -1,10 +1,6 @@
import type { DeltaInsert } from '@blocksuite/inline';
import type { Text } from '@blocksuite/store';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export type TableCell = {
text: Text;
@@ -60,5 +56,3 @@ export const TableBlockSchema = defineBlockSchema({
},
toModel: () => new TableBlockModel(),
});
export const TableBlockSchemaExtension = BlockSchemaExtension(TableBlockSchema);

View File

@@ -23,7 +23,7 @@ export const LINE_WIDTHS = [
];
/**
* Use `DefaultTheme.StrokeColorShortMap` instead.
* Use `DefaultTheme.StrokeColorMap` instead.
*
* @deprecated
*/
@@ -44,7 +44,7 @@ export enum LineColor {
export const LineColorMap = createEnumMap(LineColor);
/**
* Use `DefaultTheme.StrokeColorShortPalettes` instead.
* Use `DefaultTheme.StrokeColorPalettes` instead.
*
* @deprecated
*/

View File

@@ -77,11 +77,11 @@ export abstract class MindmapStyleGetter {
export class StyleOne extends MindmapStyleGetter {
private readonly _colorOrders = [
DefaultTheme.StrokeColorShortMap.Purple,
DefaultTheme.StrokeColorShortMap.Magenta,
DefaultTheme.StrokeColorShortMap.Orange,
DefaultTheme.StrokeColorShortMap.Yellow,
DefaultTheme.StrokeColorShortMap.Green,
DefaultTheme.StrokeColorMap.Purple,
DefaultTheme.StrokeColorMap.Magenta,
DefaultTheme.StrokeColorMap.Orange,
DefaultTheme.StrokeColorMap.Yellow,
DefaultTheme.StrokeColorMap.Green,
'#7ae2d5',
];
@@ -188,9 +188,9 @@ export const styleOne = new StyleOne();
export class StyleTwo extends MindmapStyleGetter {
private readonly _colorOrders = [
DefaultTheme.StrokeColorShortMap.Blue,
DefaultTheme.StrokeColorMap.Blue,
'#7ae2d5',
DefaultTheme.StrokeColorShortMap.Yellow,
DefaultTheme.StrokeColorMap.Yellow,
];
readonly root = {
@@ -207,7 +207,7 @@ export class StyleTwo extends MindmapStyleGetter {
color: DefaultTheme.pureBlack,
filled: true,
fillColor: DefaultTheme.StrokeColorShortMap.Yellow,
fillColor: DefaultTheme.StrokeColorMap.Yellow,
padding: [11, 22] as [number, number],
@@ -298,8 +298,8 @@ export const styleTwo = new StyleTwo();
export class StyleThree extends MindmapStyleGetter {
private readonly _strokeColor = [
DefaultTheme.StrokeColorShortMap.Yellow,
DefaultTheme.StrokeColorShortMap.Green,
DefaultTheme.StrokeColorMap.Yellow,
DefaultTheme.StrokeColorMap.Green,
'#5cc7ba',
];
@@ -317,7 +317,7 @@ export class StyleThree extends MindmapStyleGetter {
color: DefaultTheme.pureBlack,
filled: true,
fillColor: DefaultTheme.StrokeColorShortMap.Yellow,
fillColor: DefaultTheme.StrokeColorMap.Yellow,
padding: [10, 22] as [number, number],
@@ -407,12 +407,12 @@ export const styleThree = new StyleThree();
export class StyleFour extends MindmapStyleGetter {
private readonly _colors = [
DefaultTheme.StrokeColorShortMap.Purple,
DefaultTheme.StrokeColorShortMap.Magenta,
DefaultTheme.StrokeColorShortMap.Orange,
DefaultTheme.StrokeColorShortMap.Yellow,
DefaultTheme.StrokeColorShortMap.Green,
DefaultTheme.StrokeColorShortMap.Blue,
DefaultTheme.StrokeColorMap.Purple,
DefaultTheme.StrokeColorMap.Magenta,
DefaultTheme.StrokeColorMap.Orange,
DefaultTheme.StrokeColorMap.Yellow,
DefaultTheme.StrokeColorMap.Green,
DefaultTheme.StrokeColorMap.Blue,
];
readonly root = {

View File

@@ -72,44 +72,15 @@ const NoteBackgroundColorPalettes: Palette[] = [
...buildPalettes(NoteBackgroundColorMap),
] as const;
const StrokeColorShortMap = { ...Medium, Black, White } as const;
const StrokeColorMap = { ...Medium, Black, White } as const;
const StrokeColorShortPalettes: Palette[] = [
...buildPalettes(StrokeColorShortMap),
const StrokeColorPalettes: Palette[] = [
...buildPalettes(StrokeColorMap),
] as const;
const FillColorShortMap = { ...Medium, Black, White } as const;
const FillColorMap = { ...Medium, Black, White } as const;
const FillColorShortPalettes: Palette[] = [
...buildPalettes(FillColorShortMap),
] as const;
const ShapeTextColorShortMap = {
...Medium,
Black: pureBlack,
White: pureWhite,
} as const;
const ShapeTextColorShortPalettes: Palette[] = [
...buildPalettes({ ...ShapeTextColorShortMap }),
] as const;
const ShapeTextColorPalettes: Palette[] = [
// Light
...buildPalettes(Light, 'Light'),
{ key: 'Transparent', value: Transparent },
// Medium
...buildPalettes(Medium, 'Medium'),
{ key: 'White', value: pureWhite },
// Heavy
...buildPalettes(Heavy, 'Heavy'),
{ key: 'Black', value: pureBlack },
] as const;
const FillColorPalettes: Palette[] = [...buildPalettes(FillColorMap)] as const;
export const DefaultTheme: Theme = {
pureBlack,
@@ -118,19 +89,18 @@ export const DefaultTheme: Theme = {
white: White,
transparent: Transparent,
textColor: Medium.Blue,
shapeTextColor: pureBlack,
// Custom button should be selected by default,
// add transparent `ff` to distinguish `#000000`.
shapeTextColor: '#000000ff',
shapeStrokeColor: Medium.Yellow,
shapeFillColor: Medium.Yellow,
connectorColor: Medium.Grey,
noteBackgrounColor: NoteBackgroundColorMap.White,
Palettes,
ShapeTextColorPalettes,
StrokeColorMap,
StrokeColorPalettes,
FillColorMap,
FillColorPalettes,
NoteBackgroundColorMap,
NoteBackgroundColorPalettes,
StrokeColorShortMap,
StrokeColorShortPalettes,
FillColorShortMap,
FillColorShortPalettes,
ShapeTextColorShortMap,
ShapeTextColorShortPalettes,
} as const;

View File

@@ -21,20 +21,16 @@ export const ThemeSchema = z.object({
shapeFillColor: ColorSchema,
connectorColor: ColorSchema,
noteBackgrounColor: ColorSchema,
// Universal color palettes
// Universal color palette
Palettes: z.array(PaletteSchema),
ShapeTextColorPalettes: z.array(PaletteSchema),
StrokeColorMap: z.record(z.string(), ColorSchema),
// Usually used in global toolbar and editor preview
StrokeColorPalettes: z.array(PaletteSchema),
FillColorMap: z.record(z.string(), ColorSchema),
// Usually used in global toolbar and editor preview
FillColorPalettes: z.array(PaletteSchema),
NoteBackgroundColorMap: z.record(z.string(), ColorSchema),
NoteBackgroundColorPalettes: z.array(PaletteSchema),
// Usually used in global toolbar and editor preview
StrokeColorShortMap: z.record(z.string(), ColorSchema),
StrokeColorShortPalettes: z.array(PaletteSchema),
FillColorShortMap: z.record(z.string(), ColorSchema),
FillColorShortPalettes: z.array(PaletteSchema),
ShapeTextColorShortMap: z.record(z.string(), ColorSchema),
ShapeTextColorShortPalettes: z.array(PaletteSchema),
});
export type Theme = z.infer<typeof ThemeSchema>;

View File

@@ -37,7 +37,7 @@ export class DNDAPIExtension extends Extension {
const { docId, flavour = 'affine:embed-linked-doc', blockId } = options;
const slice = Slice.fromModels(this.std.store, []);
const job = this.std.store.getTransformer();
const job = this.std.getTransformer();
const snapshot = job.sliceToSnapshot(slice);
if (!snapshot) {
console.error('Failed to convert slice to snapshot');

View File

@@ -1,13 +1,8 @@
import type { EditorHost } from '@blocksuite/block-std';
import { type Viewport } from '@blocksuite/block-std/gfx';
import { Pane } from 'tweakpane';
import { getSentenceRects, segmentSentences } from './text-utils.js';
import type {
ParagraphLayout,
RenderingState,
ViewportLayout,
} from './types.js';
import type { ParagraphLayout, ViewportLayout } from './types.js';
import type { ViewportTurboRendererExtension } from './viewport-renderer.js';
export function syncCanvasSize(canvas: HTMLCanvasElement, host: HTMLElement) {
@@ -24,7 +19,7 @@ export function syncCanvasSize(canvas: HTMLCanvasElement, host: HTMLElement) {
}
export function getViewportLayout(
host: EditorHost,
host: HTMLElement,
viewport: Viewport
): ViewportLayout {
const paragraphBlocks = host.querySelectorAll(
@@ -103,15 +98,15 @@ export function initTweakpane(
paneElement.style.right = '10px';
paneElement.style.width = '250px';
debugPane.title = 'Viewport Turbo Renderer';
debugPane
.addBinding({ paused: false }, 'paused', {
label: 'Paused',
})
.on('change', ({ value }) => {
renderer.state = value ? 'paused' : 'monitoring';
});
debugPane.addButton({ title: 'Invalidate' }).on('click', () => {
renderer.invalidate();
});
}
export function debugLog(message: string, state: RenderingState) {
console.log(
`%c[ViewportTurboRenderer]%c ${message} | state=${state}`,
'color: #4285f4; font-weight: bold;',
'color: inherit;'
);
}

View File

@@ -8,7 +8,6 @@ type WorkerMessagePaint = {
height: number;
dpr: number;
zoom: number;
version: number;
};
};
@@ -64,7 +63,7 @@ class LayoutPainter {
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
paint(layout: ViewportLayout, version: number) {
paint(layout: ViewportLayout) {
const { canvas, ctx } = this;
if (!canvas || !ctx) return;
if (layout.rect.w === 0 || layout.rect.h === 0) {
@@ -104,10 +103,7 @@ class LayoutPainter {
});
const bitmap = canvas.transferToImageBitmap();
self.postMessage(
{ type: 'bitmapPainted', bitmap, version },
{ transfer: [bitmap] }
);
self.postMessage({ type: 'bitmapPainted', bitmap }, { transfer: [bitmap] });
}
}
@@ -131,9 +127,9 @@ self.onmessage = async (e: MessageEvent<WorkerMessage>) => {
switch (type) {
case 'paintLayout': {
const { layout, width, height, dpr, zoom, version } = data;
const { layout, width, height, dpr, zoom } = data;
painter.setSize(width, height, dpr, zoom);
painter.paint(layout, version);
painter.paint(layout);
break;
}
}

View File

@@ -32,12 +32,3 @@ export interface TextRect {
rect: Rect;
text: string;
}
/**
* Represents the rendering state of the ViewportTurboRenderer
* - inactive: Renderer is not active
* - pending: Bitmap is invalid or not yet available, falling back to DOM rendering
* - rendering: Currently rendering to a bitmap (async operation in progress)
* - ready: Bitmap is valid and rendered, DOM elements can be safely removed
*/
export type RenderingState = 'inactive' | 'pending' | 'rendering' | 'ready';

View File

@@ -4,20 +4,17 @@ import {
LifeCycleWatcherIdentifier,
StdIdentifier,
} from '@blocksuite/block-std';
import {
GfxControllerIdentifier,
type GfxViewportElement,
} from '@blocksuite/block-std/gfx';
import type { Container, ServiceIdentifier } from '@blocksuite/global/di';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { type Container, type ServiceIdentifier } from '@blocksuite/global/di';
import { debounce, DisposableGroup } from '@blocksuite/global/utils';
import { type Pane } from 'tweakpane';
import {
debugLog,
getViewportLayout,
initTweakpane,
syncCanvasSize,
} from './dom-utils.js';
import type { RenderingState, ViewportLayout } from './types.js';
import { type ViewportLayout } from './types.js';
export const ViewportTurboRendererIdentifier = LifeCycleWatcherIdentifier(
'ViewportTurboRenderer'
@@ -28,14 +25,12 @@ interface Tile {
zoom: number;
}
const zoomThreshold = 1; // With high enough zoom, fallback to DOM rendering
const debounceTime = 1000; // During this period, fallback to DOM
const debug = false; // Toggle for debug logs
// With high enough zoom, fallback to DOM rendering
const zoomThreshold = 1;
export class ViewportTurboRendererExtension extends LifeCycleWatcher {
state: RenderingState = 'inactive';
state: 'monitoring' | 'paused' = 'paused';
disposables = new DisposableGroup();
private layoutVersion = 0;
static override setup(di: Container) {
di.addImpl(ViewportTurboRendererIdentifier, this, [StdIdentifier]);
@@ -43,16 +38,15 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
public readonly canvas: HTMLCanvasElement = document.createElement('canvas');
private readonly worker: Worker;
private layoutCacheData: ViewportLayout | null = null;
private layoutCache: ViewportLayout | null = null;
private tile: Tile | null = null;
private viewportElement: GfxViewportElement | null = null;
private debugPane: Pane | null = null;
constructor(std: BlockStdScope) {
super(std);
this.worker = new Worker(new URL('./painter.worker.ts', import.meta.url), {
type: 'module',
});
this.debugLog('Initialized ViewportTurboRenderer');
}
override mounted() {
@@ -62,14 +56,9 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
initTweakpane(this, mountPoint as HTMLElement);
}
this.viewport.elementReady.once(element => {
this.viewportElement = element;
this.viewport.elementReady.once(() => {
syncCanvasSize(this.canvas, this.std.host);
this.setState('pending');
this.disposables.add(
this.viewport.sizeUpdated.on(() => this.handleResize())
);
this.state = 'monitoring';
this.disposables.add(
this.viewport.viewportUpdated.on(() => {
this.refresh().catch(console.error);
@@ -77,91 +66,70 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
);
});
const debouncedRefresh = debounce(
() => {
this.refresh().catch(console.error);
},
1000, // During this period, fallback to DOM
{ leading: false, trailing: true }
);
this.disposables.add(
this.std.store.slots.blockUpdated.on(() => {
this.invalidate();
this.debouncedRefresh();
debouncedRefresh();
})
);
}
override unmounted() {
this.debugLog('Unmounting renderer');
this.clearTile();
this.clearOptimizedBlocks();
if (this.debugPane) {
this.debugPane.dispose();
this.debugPane = null;
}
this.worker.terminate();
this.canvas.remove();
this.disposables.dispose();
this.setState('inactive');
}
get viewport() {
return this.std.get(GfxControllerIdentifier).viewport;
}
get layoutCache() {
if (this.layoutCacheData) return this.layoutCacheData;
const layout = getViewportLayout(this.std.host, this.viewport);
this.debugLog('Layout cache updated');
return (this.layoutCacheData = layout);
}
async refresh() {
if (this.state === 'inactive') return;
if (this.state === 'paused') return;
this.clearCanvas();
// -> pending
if (this.viewport.zoom > zoomThreshold) {
this.debugLog('Zoom above threshold, falling back to DOM rendering');
this.setState('pending');
this.toggleOptimization(false);
this.clearOptimizedBlocks();
}
// -> ready
else if (this.canUseBitmapCache()) {
this.debugLog('Using cached bitmap');
this.setState('ready');
this.drawCachedBitmap(this.layoutCache);
this.updateOptimizedBlocks();
}
// -> rendering
else {
this.setState('rendering');
this.toggleOptimization(false);
await this.paintLayout(this.layoutCache);
this.drawCachedBitmap(this.layoutCache);
this.updateOptimizedBlocks();
return;
} else if (this.canUseBitmapCache()) {
this.drawCachedBitmap(this.layoutCache!);
} else {
if (!this.layoutCache) {
this.updateLayoutCache();
}
const layout = this.layoutCache!;
await this.paintLayout(layout);
this.drawCachedBitmap(layout);
}
}
debouncedRefresh = debounce(
() => {
this.refresh().catch(console.error);
},
debounceTime,
{ leading: false, trailing: true }
);
invalidate() {
this.layoutVersion++;
this.layoutCacheData = null;
this.layoutCache = null;
this.clearTile();
this.clearCanvas();
this.clearOptimizedBlocks();
this.setState('pending');
this.debugLog(`Invalidated renderer (layoutVersion=${this.layoutVersion})`);
this.clearCanvas(); // Should clear immediately after content updates
}
private debugLog(message: string) {
if (!debug) return;
debugLog(message, this.state);
private updateLayoutCache() {
const layout = getViewportLayout(this.std.host, this.viewport);
this.layoutCache = layout;
}
private clearTile() {
if (!this.tile) return;
this.tile.bitmap.close();
this.tile = null;
this.debugLog('Tile cleared');
if (this.tile) {
this.tile.bitmap.close();
this.tile = null;
}
}
private async paintLayout(layout: ViewportLayout): Promise<void> {
@@ -169,9 +137,6 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
if (!this.worker) return;
const dpr = window.devicePixelRatio;
const currentVersion = this.layoutVersion;
this.debugLog(`Requesting bitmap painting (version=${currentVersion})`);
this.worker.postMessage({
type: 'paintLayout',
data: {
@@ -180,37 +145,25 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
height: layout.rect.h,
dpr,
zoom: this.viewport.zoom,
version: currentVersion,
},
});
this.worker.onmessage = (e: MessageEvent) => {
if (e.data.type === 'bitmapPainted') {
if (e.data.version === this.layoutVersion) {
this.debugLog(
`Bitmap painted successfully (version=${e.data.version})`
);
this.handlePaintedBitmap(e.data.bitmap, resolve);
} else {
this.debugLog(
`Received outdated bitmap (got=${e.data.version}, current=${this.layoutVersion})`
);
e.data.bitmap.close();
this.setState('pending');
resolve();
}
this.handlePaintedBitmap(e.data.bitmap, resolve);
}
};
});
}
private handlePaintedBitmap(bitmap: ImageBitmap, resolve: () => void) {
this.clearTile();
if (this.tile) {
this.tile.bitmap.close();
}
this.tile = {
bitmap,
zoom: this.viewport.zoom,
};
this.setState('ready');
resolve();
}
@@ -224,17 +177,10 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
const ctx = this.canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.debugLog('Canvas cleared');
}
private drawCachedBitmap(layout: ViewportLayout) {
if (!this.tile) {
this.debugLog('No cached bitmap available, requesting refresh');
this.debouncedRefresh();
return;
}
const bitmap = this.tile.bitmap;
const bitmap = this.tile!.bitmap;
const ctx = this.canvas.getContext('2d');
if (!ctx) return;
@@ -251,61 +197,5 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
layout.rect.w * window.devicePixelRatio * this.viewport.zoom,
layout.rect.h * window.devicePixelRatio * this.viewport.zoom
);
this.debugLog('Bitmap drawn to canvas');
}
setState(newState: RenderingState) {
if (this.state === newState) return;
this.debugLog(`State change: ${this.state} -> ${newState}`);
this.state = newState;
}
canOptimize(): boolean {
const isReady = this.state === 'ready';
const isBelowZoomThreshold = this.viewport.zoom <= zoomThreshold;
const result = isReady && isBelowZoomThreshold;
return result;
}
private updateOptimizedBlocks() {
requestAnimationFrame(() => {
if (!this.viewportElement || !this.layoutCache) return;
if (!this.canOptimize()) return;
if (this.state !== 'ready') {
this.debugLog('Unexpected state updating optimized blocks');
console.warn('Unexpected state', this.tile, this.layoutCache);
return;
}
this.toggleOptimization(true);
const blockElements = this.viewportElement.getModelsInViewport();
const blockIds = Array.from(blockElements).map(model => model.id);
this.viewportElement.updateOptimizedBlocks(blockIds, true);
this.debugLog(`Optimized ${blockIds.length} blocks`);
});
}
private clearOptimizedBlocks() {
if (!this.viewportElement) return;
this.viewportElement.clearOptimizedBlocks();
this.debugLog('Cleared optimized blocks');
}
private toggleOptimization(value: boolean) {
if (
this.viewportElement &&
this.viewportElement.enableOptimization !== value
) {
this.viewportElement.enableOptimization = value;
this.debugLog(`${value ? 'Enabled' : 'Disabled'} optimization`);
}
}
private handleResize() {
this.debugLog('Container resized, syncing canvas size');
syncCanvasSize(this.canvas, this.std.host);
this.invalidate();
this.debouncedRefresh();
}
}

View File

@@ -145,8 +145,6 @@ export class PreviewHelper {
}
this.std.view.viewUpdated.on(payload => {
if (payload.type !== 'block') return;
if (payload.view.model.flavour === 'affine:page') {
const gfx = this.std.get(GfxControllerIdentifier);

View File

@@ -1354,7 +1354,7 @@ export class DragEventWatcher {
middlewares.push(gfxBlocksFilter(selectedIds, std));
}
return std.store.getTransformer(middlewares);
return std.getTransformer(middlewares);
}
private _isDropOnCurrentEditor(std?: BlockStdScope) {
@@ -1625,13 +1625,10 @@ export class DragEventWatcher {
disposables.add(
std.view.viewUpdated.on(payload => {
if (payload.type !== 'block') {
return;
}
if (payload.method === 'add') {
if (payload.type === 'add') {
this._makeDropTarget(payload.view);
} else if (
payload.method === 'delete' &&
payload.type === 'delete' &&
this.dropTargetCleanUps.has(payload.id)
) {
this.dropTargetCleanUps.get(payload.id)!.forEach(clean => clean());

View File

@@ -250,7 +250,6 @@ export class EdgelessRemoteSelectionWidget extends WidgetComponent<RootBlockMode
${MultiCursorDuotoneIcon({
width: '24px',
height: '24px',
style: `fill: ${_remoteColorManager.get(id)}; stroke: ${_remoteColorManager.get(id)};`,
})}
<div
class="remote-username"

View File

@@ -4,11 +4,8 @@ import type { SliceSnapshot } from '@blocksuite/store';
import { describe, expect, test } from 'vitest';
import { createJob } from '../utils/create-job.js';
import { getProvider } from '../utils/get-provider.js';
import { nanoidReplacement } from '../utils/nanoid-replacement.js';
getProvider();
describe('notion-text to snapshot', () => {
test('basic', () => {
const notionText =

View File

@@ -11,37 +11,39 @@ import {
type Cell,
type Column,
type DatabaseBlockModel,
DatabaseBlockSchemaExtension,
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
DatabaseBlockSchema,
NoteBlockSchema,
ParagraphBlockSchema,
RootBlockSchema,
} from '@blocksuite/affine-model';
import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets';
import type { BlockModel, Store } from '@blocksuite/store';
import { Text } from '@blocksuite/store';
import { Schema, Text } from '@blocksuite/store';
import {
createAutoIncrementIdGenerator,
TestWorkspace,
} from '@blocksuite/store/test';
import { beforeEach, describe, expect, test } from 'vitest';
const extensions = [
RootBlockSchemaExtension,
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
DatabaseBlockSchemaExtension,
const AffineSchemas = [
RootBlockSchema,
NoteBlockSchema,
ParagraphBlockSchema,
DatabaseBlockSchema,
];
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
return { id: 'test-collection', idGenerator };
const schema = new Schema();
schema.register(AffineSchemas);
return { id: 'test-collection', idGenerator, schema };
}
function createTestDoc(docId = 'doc0') {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: docId, extensions });
const doc = collection.createDoc({ id: docId });
doc.load();
return doc;
}

View File

@@ -1,5 +1,5 @@
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import {
Schema,
Transformer,
@@ -26,8 +26,8 @@ export function createJob(middlewares?: TransformerMiddleware[]) {
const testMiddlewares = middlewares ?? [];
testMiddlewares.push(defaultImageProxyMiddleware);
const schema = new Schema().register(AffineSchemas);
const docCollection = new TestWorkspace();
docCollection.storeExtensions = SpecProvider._.getSpec('store').value;
const docCollection = new TestWorkspace({ schema });
docCollection.storeExtensions = [FeatureFlagService];
docCollection.meta.initialize();
return new Transformer({
schema,

View File

@@ -2,12 +2,15 @@ import { AttachmentBlockSpec } from '@blocksuite/affine-block-attachment';
import { BookmarkBlockSpec } from '@blocksuite/affine-block-bookmark';
import { CodeBlockSpec } from '@blocksuite/affine-block-code';
import { DataViewBlockSpec } from '@blocksuite/affine-block-data-view';
import { DatabaseBlockSpec } from '@blocksuite/affine-block-database';
import {
DatabaseBlockSpec,
DatabaseSelectionExtension,
} from '@blocksuite/affine-block-database';
import { DividerBlockSpec } from '@blocksuite/affine-block-divider';
import { EdgelessTextBlockSpec } from '@blocksuite/affine-block-edgeless-text';
import { EmbedExtensions } from '@blocksuite/affine-block-embed';
import { FrameBlockSpec } from '@blocksuite/affine-block-frame';
import { ImageBlockSpec } from '@blocksuite/affine-block-image';
import { ImageBlockSpec, ImageStoreSpec } from '@blocksuite/affine-block-image';
import { LatexBlockSpec } from '@blocksuite/affine-block-latex';
import { ListBlockSpec } from '@blocksuite/affine-block-list';
import {
@@ -23,19 +26,43 @@ import {
EdgelessSurfaceRefBlockSpec,
PageSurfaceRefBlockSpec,
} from '@blocksuite/affine-block-surface-ref';
import { TableBlockSpec } from '@blocksuite/affine-block-table';
import {
TableBlockSpec,
TableSelectionExtension,
} from '@blocksuite/affine-block-table';
import {
RefNodeSlotsExtension,
RichTextExtensions,
} from '@blocksuite/affine-components/rich-text';
import {
HighlightSelectionExtension,
ImageSelectionExtension,
} from '@blocksuite/affine-shared/selection';
import {
DefaultOpenDocExtension,
DocDisplayMetaService,
EditPropsStore,
FeatureFlagService,
FileSizeLimitService,
FontLoaderService,
LinkPreviewerService,
} from '@blocksuite/affine-shared/services';
import {
BlockSelectionExtension,
CursorSelectionExtension,
SurfaceSelectionExtension,
TextSelectionExtension,
} from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import {
AdapterFactoryExtensions,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
} from '../adapters/extension.js';
export const CommonBlockSpecs: ExtensionType[] = [
DocDisplayMetaService,
RefNodeSlotsExtension,
@@ -55,6 +82,7 @@ export const CommonBlockSpecs: ExtensionType[] = [
ParagraphBlockSpec,
DefaultOpenDocExtension,
FontLoaderService,
AdapterFactoryExtensions,
].flat();
export const PageFirstPartyBlockSpecs: ExtensionType[] = [
@@ -73,3 +101,24 @@ export const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [
FrameBlockSpec,
EdgelessTextBlockSpec,
].flat();
export const StoreExtensions: ExtensionType[] = [
BlockSelectionExtension,
TextSelectionExtension,
SurfaceSelectionExtension,
CursorSelectionExtension,
HighlightSelectionExtension,
ImageSelectionExtension,
DatabaseSelectionExtension,
TableSelectionExtension,
FeatureFlagService,
LinkPreviewerService,
FileSizeLimitService,
ImageStoreSpec,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
].flat();

View File

@@ -1,4 +1,3 @@
export * from './common.js';
export * from './editor-specs.js';
export * from './preview-specs.js';
export * from './store.js';

View File

@@ -1,5 +1,6 @@
import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { StoreExtensions } from './common.js';
import {
EdgelessEditorBlockSpecs,
PageEditorBlockSpecs,
@@ -8,7 +9,6 @@ import {
PreviewEdgelessEditorBlockSpecs,
PreviewPageEditorBlockSpecs,
} from './preview-specs.js';
import { StoreExtensions } from './store.js';
export function registerSpecs() {
SpecProvider._.addSpec('store', StoreExtensions);

View File

@@ -1,101 +0,0 @@
import { DataViewBlockSchemaExtension } from '@blocksuite/affine-block-data-view';
import { DatabaseSelectionExtension } from '@blocksuite/affine-block-database';
import { ImageStoreSpec } from '@blocksuite/affine-block-image';
import { SurfaceBlockSchemaExtension } from '@blocksuite/affine-block-surface';
import { TableSelectionExtension } from '@blocksuite/affine-block-table';
import {
AttachmentBlockSchemaExtension,
BookmarkBlockSchemaExtension,
CodeBlockSchemaExtension,
DatabaseBlockSchemaExtension,
DividerBlockSchemaExtension,
EdgelessTextBlockSchemaExtension,
EmbedFigmaBlockSchemaExtension,
EmbedGithubBlockSchemaExtension,
EmbedHtmlBlockSchemaExtension,
EmbedLinkedDocBlockSchemaExtension,
EmbedLoomBlockSchemaExtension,
EmbedSyncedDocBlockSchemaExtension,
EmbedYoutubeBlockSchemaExtension,
FrameBlockSchemaExtension,
ImageBlockSchemaExtension,
LatexBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
SurfaceRefBlockSchemaExtension,
TableBlockSchemaExtension,
} from '@blocksuite/affine-model';
import {
HighlightSelectionExtension,
ImageSelectionExtension,
} from '@blocksuite/affine-shared/selection';
import {
FeatureFlagService,
FileSizeLimitService,
LinkPreviewerService,
} from '@blocksuite/affine-shared/services';
import {
BlockSelectionExtension,
CursorSelectionExtension,
SurfaceSelectionExtension,
TextSelectionExtension,
} from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import {
AdapterFactoryExtensions,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
} from '../adapters/extension.js';
export const StoreExtensions: ExtensionType[] = [
CodeBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
DividerBlockSchemaExtension,
ImageBlockSchemaExtension,
SurfaceBlockSchemaExtension,
BookmarkBlockSchemaExtension,
FrameBlockSchemaExtension,
DatabaseBlockSchemaExtension,
SurfaceRefBlockSchemaExtension,
DataViewBlockSchemaExtension,
AttachmentBlockSchemaExtension,
EmbedSyncedDocBlockSchemaExtension,
EmbedLinkedDocBlockSchemaExtension,
EmbedHtmlBlockSchemaExtension,
EmbedGithubBlockSchemaExtension,
EmbedFigmaBlockSchemaExtension,
EmbedLoomBlockSchemaExtension,
EmbedYoutubeBlockSchemaExtension,
EdgelessTextBlockSchemaExtension,
LatexBlockSchemaExtension,
TableBlockSchemaExtension,
BlockSelectionExtension,
TextSelectionExtension,
SurfaceSelectionExtension,
CursorSelectionExtension,
HighlightSelectionExtension,
ImageSelectionExtension,
DatabaseSelectionExtension,
TableSelectionExtension,
FeatureFlagService,
LinkPreviewerService,
FileSizeLimitService,
ImageStoreSpec,
HtmlAdapterExtension,
MarkdownAdapterExtension,
NotionHtmlAdapterExtension,
PlainTextAdapterExtension,
AdapterFactoryExtensions,
].flat();

View File

@@ -1,3 +1,4 @@
import { Schema } from '@blocksuite/store';
import {
createAutoIncrementIdGenerator,
TestWorkspace,
@@ -8,23 +9,19 @@ import { effects } from '../effects.js';
import { TestEditorContainer } from './test-editor.js';
import {
type HeadingBlockModel,
HeadingBlockSchemaExtension,
NoteBlockSchemaExtension,
RootBlockSchemaExtension,
HeadingBlockSchema,
NoteBlockSchema,
RootBlockSchema,
} from './test-schema.js';
import { testSpecs } from './test-spec.js';
effects();
const extensions = [
RootBlockSchemaExtension,
NoteBlockSchemaExtension,
HeadingBlockSchemaExtension,
];
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
return { id: 'test-collection', idGenerator };
const schema = new Schema();
schema.register([RootBlockSchema, NoteBlockSchema, HeadingBlockSchema]);
return { id: 'test-collection', idGenerator, schema };
}
function wait(time: number) {
@@ -36,7 +33,7 @@ describe('editor host', () => {
const collection = new TestWorkspace(createTestOptions());
collection.meta.initialize();
const doc = collection.createDoc({ id: 'home', extensions });
const doc = collection.createDoc({ id: 'home' });
doc.load();
const rootId = doc.addBlock('test:page');
const noteId = doc.addBlock('test:note', {}, rootId);

View File

@@ -1,8 +1,4 @@
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
export const RootBlockSchema = defineBlockSchema({
flavour: 'test:page',
@@ -19,8 +15,6 @@ export const RootBlockSchema = defineBlockSchema({
},
});
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
export class RootBlockModel extends BlockModel<
ReturnType<(typeof RootBlockSchema)['model']['props']>
> {}
@@ -36,8 +30,6 @@ export const NoteBlockSchema = defineBlockSchema({
},
});
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
export class NoteBlockModel extends BlockModel<
ReturnType<(typeof NoteBlockSchema)['model']['props']>
> {}
@@ -55,9 +47,6 @@ export const HeadingBlockSchema = defineBlockSchema({
},
});
export const HeadingBlockSchemaExtension =
BlockSchemaExtension(HeadingBlockSchema);
export class HeadingBlockModel extends BlockModel<
ReturnType<(typeof HeadingBlockSchema)['model']['props']>
> {}

View File

@@ -285,7 +285,7 @@ export class Clipboard extends LifeCycleWatcher {
}
private _getJob() {
return this.std.store.getTransformer(this._jobMiddlewares);
return this.std.getTransformer(this._jobMiddlewares);
}
readFromClipboard(clipboardData: DataTransfer) {

View File

@@ -5,4 +5,5 @@ export * from './flavour.js';
export * from './keymap.js';
export * from './lifecycle-watcher.js';
export * from './service.js';
export * from './service-watcher.js';
export * from './widget-view-map.js';

View File

@@ -0,0 +1,47 @@
import type { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import {
BlockServiceIdentifier,
LifeCycleWatcherIdentifier,
StdIdentifier,
} from '../identifier.js';
import type { BlockStdScope } from '../scope/index.js';
import { LifeCycleWatcher } from './lifecycle-watcher.js';
import type { BlockService } from './service.js';
const idMap = new Map<string, number>();
/**
* @deprecated
* BlockServiceWatcher is deprecated. You should reconsider where to put your feature.
*
* BlockServiceWatcher is a legacy extension that is used to watch the slots registered on block service.
* However, we recommend using the new extension system.
*/
export abstract class BlockServiceWatcher extends LifeCycleWatcher {
static flavour: string;
constructor(
std: BlockStdScope,
readonly blockService: BlockService
) {
super(std);
}
static override setup(di: Container) {
if (!this.flavour) {
throw new BlockSuiteError(
ErrorCode.ValueNotExists,
'Flavour is not defined in the BlockServiceWatcher'
);
}
const id = idMap.get(this.flavour) ?? 0;
idMap.set(this.flavour, id + 1);
di.addImpl(
LifeCycleWatcherIdentifier(`${this.flavour}-watcher-${id}`),
this,
[StdIdentifier, BlockServiceIdentifier(this.flavour)]
);
}
}

View File

@@ -1,12 +1,6 @@
import {
assertType,
type Constructor,
DisposableGroup,
Slot,
} from '@blocksuite/global/utils';
import { assertType, type Constructor, Slot } from '@blocksuite/global/utils';
import type { Boxed } from '@blocksuite/store';
import { BlockModel, nanoid } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import * as Y from 'yjs';
import {
@@ -104,8 +98,6 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
oldValues: Record<string, unknown>;
}>();
private readonly _isEmpty$ = signal(false);
get elementModels() {
const models: GfxPrimitiveElementModel[] = [];
this._elementModels.forEach(model => models.push(model.model));
@@ -121,7 +113,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
}
override isEmpty(): boolean {
return this._isEmpty$.value;
return this._elementModels.size === 0 && this.children.length === 0;
}
constructor() {
@@ -378,7 +370,6 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
if (isGfxGroupCompatibleModel(payload.model)) {
this._groupLikeModels.set(payload.id, payload.model);
}
break;
case 'delete':
if (isGfxGroupCompatibleModel(payload.model)) {
@@ -391,7 +382,6 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
group.removeChild(payload.model as GfxModel);
}
}
break;
}
});
@@ -455,25 +445,6 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
});
}
private _watchChildrenChange() {
const updateIsEmpty = () => {
this._isEmpty$.value =
this._elementModels.size === 0 && this.children.length === 0;
};
const disposables = new DisposableGroup();
disposables.add(this.elementAdded.on(updateIsEmpty));
disposables.add(this.elementRemoved.on(updateIsEmpty));
this.doc.slots.blockUpdated.on(payload => {
if (['add', 'delete'].includes(payload.type)) {
updateIsEmpty();
}
});
this.deleted.on(() => {
disposables.dispose();
});
}
protected _extendElement(
ctorMap: Record<
string,
@@ -489,7 +460,6 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
protected _init() {
this._initElementModels();
this._watchGroupRelationChange();
this._watchChildrenChange();
}
getConstructor(type: string) {

View File

@@ -57,30 +57,27 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) {
}
`;
optimizedBlocks = new Set<string>();
private readonly _hideOutsideBlock = () => {
if (!this.host) return;
if (this.getModelsInViewport && this.host) {
const host = this.host;
const modelsInViewport = this.getModelsInViewport();
const { host, optimizedBlocks, enableOptimization } = this;
const modelsInViewport = this.getModelsInViewport();
modelsInViewport.forEach(model => {
const view = host.std.view.getBlock(model.id);
const canOptimize = optimizedBlocks.has(model.id) && enableOptimization;
const display = canOptimize ? 'none' : 'block';
setDisplay(view, display);
modelsInViewport.forEach(model => {
const view = host.std.view.getBlock(model.id);
setDisplay(view, 'block');
if (this._lastVisibleModels?.has(model)) {
this._lastVisibleModels!.delete(model);
}
});
if (this._lastVisibleModels?.has(model)) {
this._lastVisibleModels!.delete(model);
}
});
this._lastVisibleModels?.forEach(model => {
const view = host.std.view.getBlock(model.id);
setDisplay(view, 'none');
});
this._lastVisibleModels?.forEach(model => {
const view = host.std.view.getBlock(model.id);
setDisplay(view, 'none');
});
this._lastVisibleModels = modelsInViewport;
this._lastVisibleModels = modelsInViewport;
}
};
private _lastVisibleModels?: Set<GfxBlockElementModel>;
@@ -157,8 +154,7 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) {
};
@property({ attribute: false })
accessor getModelsInViewport: () => Set<GfxBlockElementModel> = () =>
new Set();
accessor getModelsInViewport: undefined | (() => Set<GfxBlockElementModel>);
@property({ attribute: false })
accessor host: undefined | EditorHost;
@@ -171,29 +167,4 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor viewport!: Viewport;
@property({ attribute: false })
accessor enableOptimization: boolean = false;
updateOptimizedBlocks(blockIds: string[], optimized: boolean): void {
let changed = false;
blockIds.forEach(id => {
if (optimized && !this.optimizedBlocks.has(id)) {
this.optimizedBlocks.add(id);
changed = true;
} else if (!optimized && this.optimizedBlocks.has(id)) {
this.optimizedBlocks.delete(id);
changed = true;
}
});
if (changed) this._refreshViewport();
}
clearOptimizedBlocks(): void {
if (this.optimizedBlocks.size === 0) return;
this.optimizedBlocks.clear();
this._refreshViewport();
}
}

View File

@@ -4,6 +4,8 @@ import {
type ExtensionType,
type Store,
StoreSelectionExtension,
Transformer,
type TransformerMiddleware,
} from '@blocksuite/store';
import { Clipboard } from '../clipboard/index.js';
@@ -138,6 +140,19 @@ export class BlockStdScope {
return this.getOptional(BlockViewIdentifier(flavour));
}
getTransformer(middlewares: TransformerMiddleware[] = []) {
return new Transformer({
schema: this.workspace.schema,
blobCRUD: this.workspace.blobSync,
docCRUD: {
create: (id: string) => this.workspace.createDoc({ id }),
get: (id: string) => this.workspace.getDoc(id),
delete: (id: string) => this.workspace.removeDoc(id),
},
middlewares,
});
}
mount() {
this._lifeCycleWatchers.forEach(watcher => {
watcher.mounted();

View File

@@ -3,20 +3,16 @@ import { Slot } from '@blocksuite/global/utils';
import { LifeCycleWatcher } from '../extension/index.js';
import type { BlockComponent, WidgetComponent } from './element/index.js';
type ViewUpdateMethod = 'delete' | 'add';
export type ViewUpdatePayload =
type ViewUpdatePayload =
| {
id: string;
method: ViewUpdateMethod;
type: 'block';
type: 'delete';
view: BlockComponent;
}
| {
id: string;
method: ViewUpdateMethod;
type: 'widget';
view: WidgetComponent;
type: 'add';
view: BlockComponent;
};
export class ViewStore extends LifeCycleWatcher {
@@ -46,8 +42,7 @@ export class ViewStore extends LifeCycleWatcher {
this._blockMap.delete(node.model.id);
this.viewUpdated.emit({
id: node.model.id,
method: 'delete',
type: 'block',
type: 'delete',
view: node,
});
};
@@ -56,12 +51,6 @@ export class ViewStore extends LifeCycleWatcher {
const id = node.dataset.widgetId as string;
const widgetIndex = `${node.model.id}|${id}`;
this._widgetMap.delete(widgetIndex);
this.viewUpdated.emit({
id: node.model.id,
method: 'delete',
type: 'widget',
view: node,
});
};
getBlock = (id: string): BlockComponent | null => {
@@ -83,8 +72,7 @@ export class ViewStore extends LifeCycleWatcher {
this._blockMap.set(node.model.id, node);
this.viewUpdated.emit({
id: node.model.id,
method: 'add',
type: 'block',
type: 'add',
view: node,
});
};
@@ -93,12 +81,6 @@ export class ViewStore extends LifeCycleWatcher {
const id = node.dataset.widgetId as string;
const widgetIndex = `${node.model.id}|${id}`;
this._widgetMap.set(widgetIndex, node);
this.viewUpdated.emit({
id: node.model.id,
method: 'add',
type: 'widget',
view: node,
});
};
walkThrough = (

View File

@@ -2,7 +2,6 @@ import { computed, effect } from '@preact/signals-core';
import { describe, expect, test, vi } from 'vitest';
import * as Y from 'yjs';
import { BlockSchemaExtension } from '../extension/schema.js';
import {
Block,
BlockModel,
@@ -10,6 +9,7 @@ import {
internalPrimitives,
} from '../model/block/index.js';
import type { YBlock } from '../model/block/types.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js';
@@ -27,7 +27,6 @@ const pageSchema = defineBlockSchema({
version: 1,
},
});
const pageSchemaExtension = BlockSchemaExtension(pageSchema);
const tableSchema = defineBlockSchema({
flavour: 'table',
@@ -40,7 +39,6 @@ const tableSchema = defineBlockSchema({
version: 1,
},
});
const tableSchemaExtension = BlockSchemaExtension(tableSchema);
const flatTableSchema = defineBlockSchema({
flavour: 'flat-table',
@@ -56,8 +54,6 @@ const flatTableSchema = defineBlockSchema({
isFlatData: true,
},
});
const flatTableSchemaExtension = BlockSchemaExtension(flatTableSchema);
class RootModel extends BlockModel<
ReturnType<(typeof pageSchema)['model']['props']>
> {}
@@ -70,7 +66,9 @@ class FlatTableModel extends BlockModel<
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
return { id: 'test-collection', idGenerator };
const schema = new Schema();
schema.register([pageSchema, tableSchema, flatTableSchema]);
return { id: 'test-collection', idGenerator, schema };
}
const defaultDocId = 'doc:home';
@@ -78,14 +76,7 @@ function createTestDoc(docId = defaultDocId) {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({
id: docId,
extensions: [
pageSchemaExtension,
tableSchemaExtension,
flatTableSchemaExtension,
],
});
const doc = collection.createDoc({ id: docId });
doc.load();
return doc;
}

View File

@@ -4,20 +4,29 @@ import type { Slot } from '@blocksuite/global/utils';
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs';
import type { BlockModel, DocMeta, Store } from '../index.js';
import type { BlockModel, BlockSchemaType, DocMeta, Store } from '../index.js';
import { Schema } from '../index.js';
import { Text } from '../reactive/text.js';
import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js';
import {
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
NoteBlockSchema,
ParagraphBlockSchema,
RootBlockSchema,
} from './test-schema.js';
import { assertExists } from './test-utils-dom.js';
export const BlockSchemas = [
ParagraphBlockSchema,
RootBlockSchema,
NoteBlockSchema,
] as BlockSchemaType[];
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
return { id: 'test-collection', idGenerator };
const schema = new Schema();
schema.register(BlockSchemas);
return { id: 'test-collection', idGenerator, schema };
}
const defaultDocId = 'doc:home';
@@ -49,20 +58,11 @@ function createRoot(doc: Store) {
return doc.root;
}
const extensions = [
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
];
function createTestDoc(docId = defaultDocId) {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({
id: docId,
extensions,
});
const doc = collection.createDoc({ id: docId });
doc.load();
return doc;
}
@@ -113,6 +113,13 @@ describe('basic', () => {
tags: [],
},
],
workspaceVersion: 2,
pageVersion: 2,
blockVersions: {
'affine:note': 1,
'affine:page': 2,
'affine:paragraph': 1,
},
},
spaces: {
[spaceId]: {
@@ -148,7 +155,6 @@ describe('basic', () => {
collection.meta.initialize();
const doc = collection.createDoc({
id: 'space:0',
extensions,
});
const readyCallback = vi.fn();
@@ -175,7 +181,6 @@ describe('basic', () => {
const collection2 = new TestWorkspace(options);
const doc = collection.createDoc({
id: 'space:0',
extensions,
});
doc.load(() => {
doc.addBlock('affine:page', {
@@ -204,9 +209,7 @@ describe('basic', () => {
// apply doc update
const update = encodeStateAsUpdate(doc.spaceDoc);
expect(collection2.docs.size).toBe(1);
const doc2 = collection2.getDoc('space:0', {
extensions,
});
const doc2 = collection2.getDoc('space:0');
assertExists(doc2);
applyUpdate(doc2.spaceDoc, update);
expect(serializCollection(collection2.doc)['spaces']).toEqual({

View File

@@ -2,28 +2,31 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
import * as Y from 'yjs';
import type { BlockModel, Store } from '../model/index.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js';
import {
DividerBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
DividerBlockSchema,
ListBlockSchema,
NoteBlockSchema,
ParagraphBlockSchema,
type RootBlockModel,
RootBlockSchemaExtension,
RootBlockSchema,
} from './test-schema.js';
const extensions = [
RootBlockSchemaExtension,
ParagraphBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
DividerBlockSchemaExtension,
const BlockSchemas = [
RootBlockSchema,
ParagraphBlockSchema,
ListBlockSchema,
NoteBlockSchema,
DividerBlockSchema,
];
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
return { id: 'test-collection', idGenerator };
const schema = new Schema();
schema.register(BlockSchemas);
return { id: 'test-collection', idGenerator, schema };
}
test('trigger props updated', () => {
@@ -31,7 +34,7 @@ test('trigger props updated', () => {
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: 'home', extensions });
const doc = collection.createDoc({ id: 'home' });
doc.load();
doc.addBlock('affine:page');
@@ -91,7 +94,7 @@ test('stash and pop', () => {
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: 'home', extensions });
const doc = collection.createDoc({ id: 'home' });
doc.load();
doc.addBlock('affine:page');
@@ -161,7 +164,7 @@ test('always get latest value in onChange', () => {
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: 'home', extensions });
const doc = collection.createDoc({ id: 'home' });
doc.load();
doc.addBlock('affine:page');
@@ -207,12 +210,11 @@ test('query', () => {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc1 = collection.createDoc({ id: 'home', extensions });
const doc1 = collection.createDoc({ id: 'home' });
doc1.load();
const doc2 = collection.getDoc('home', { extensions });
const doc2 = collection.getDoc('home');
const doc3 = collection.getDoc('home', {
extensions,
query: {
mode: 'loose',
match: [
@@ -245,10 +247,10 @@ test('local readonly', () => {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc1 = collection.createDoc({ id: 'home', extensions });
const doc1 = collection.createDoc({ id: 'home' });
doc1.load();
const doc2 = collection.getDoc('home', { readonly: true, extensions });
const doc3 = collection.getDoc('home', { readonly: false, extensions });
const doc2 = collection.getDoc('home', { readonly: true });
const doc3 = collection.getDoc('home', { readonly: false });
expect(doc1.readonly).toBeFalsy();
expect(doc2?.readonly).toBeTruthy();
@@ -274,7 +276,7 @@ describe('move blocks', () => {
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: 'home', extensions });
const doc = collection.createDoc({ id: 'home' });
doc.load();
const pageId = doc.addBlock('affine:page');
const page = doc.getBlock(pageId)!.model;

View File

@@ -1,23 +1,25 @@
import { literal } from 'lit/static-html.js';
import { describe, expect, it, vi } from 'vitest';
import { BlockSchemaExtension } from '../extension/schema.js';
import { defineBlockSchema } from '../model/block/zod.js';
// import some blocks
import { SchemaValidateError } from '../schema/error.js';
import { Schema } from '../schema/index.js';
import { createAutoIncrementIdGenerator } from '../test/index.js';
import { TestWorkspace } from '../test/test-workspace.js';
import {
DividerBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
ParagraphBlockSchemaExtension,
RootBlockSchemaExtension,
DividerBlockSchema,
ListBlockSchema,
NoteBlockSchema,
ParagraphBlockSchema,
RootBlockSchema,
} from './test-schema.js';
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
return { id: 'test-collection', idGenerator };
const schema = new Schema();
schema.register(BlockSchemas);
return { id: 'test-collection', idGenerator, schema };
}
const TestCustomNoteBlockSchema = defineBlockSchema({
@@ -33,10 +35,6 @@ const TestCustomNoteBlockSchema = defineBlockSchema({
},
});
const TestCustomNoteBlockSchemaExtension = BlockSchemaExtension(
TestCustomNoteBlockSchema
);
const TestInvalidNoteBlockSchema = defineBlockSchema({
flavour: 'affine:note-invalid-block-video',
props: internal => ({
@@ -50,18 +48,14 @@ const TestInvalidNoteBlockSchema = defineBlockSchema({
},
});
const TestInvalidNoteBlockSchemaExtension = BlockSchemaExtension(
TestInvalidNoteBlockSchema
);
const extensions = [
RootBlockSchemaExtension,
ParagraphBlockSchemaExtension,
ListBlockSchemaExtension,
NoteBlockSchemaExtension,
DividerBlockSchemaExtension,
TestCustomNoteBlockSchemaExtension,
TestInvalidNoteBlockSchemaExtension,
const BlockSchemas = [
RootBlockSchema,
ParagraphBlockSchema,
ListBlockSchema,
NoteBlockSchema,
DividerBlockSchema,
TestCustomNoteBlockSchema,
TestInvalidNoteBlockSchema,
];
const defaultDocId = 'doc0';
@@ -69,7 +63,7 @@ function createTestDoc(docId = defaultDocId) {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: docId, extensions });
const doc = collection.createDoc({ id: docId });
doc.load();
return doc;
}

View File

@@ -1,4 +1,3 @@
import { BlockSchemaExtension } from '../extension/schema.js';
import { BlockModel, defineBlockSchema } from '../model/index.js';
export const RootBlockSchema = defineBlockSchema({
@@ -15,8 +14,6 @@ export const RootBlockSchema = defineBlockSchema({
},
});
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
export class RootBlockModel extends BlockModel<
ReturnType<(typeof RootBlockSchema)['model']['props']>
> {}
@@ -45,8 +42,6 @@ export const NoteBlockSchema = defineBlockSchema({
},
});
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
export const ParagraphBlockSchema = defineBlockSchema({
flavour: 'affine:paragraph',
props: internal => ({
@@ -65,9 +60,6 @@ export const ParagraphBlockSchema = defineBlockSchema({
},
});
export const ParagraphBlockSchemaExtension =
BlockSchemaExtension(ParagraphBlockSchema);
export const ListBlockSchema = defineBlockSchema({
flavour: 'affine:list',
props: internal => ({
@@ -88,8 +80,6 @@ export const ListBlockSchema = defineBlockSchema({
},
});
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
export const DividerBlockSchema = defineBlockSchema({
flavour: 'affine:divider',
metadata: {
@@ -98,6 +88,3 @@ export const DividerBlockSchema = defineBlockSchema({
children: [],
},
});
export const DividerBlockSchemaExtension =
BlockSchemaExtension(DividerBlockSchema);

View File

@@ -2,10 +2,10 @@ import { expect, test } from 'vitest';
import * as Y from 'yjs';
import { MemoryBlobCRUD } from '../adapter/index.js';
import { BlockSchemaExtension } from '../extension/schema.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';
import { TestWorkspace } from '../test/test-workspace.js';
import { AssetsManager, BaseBlockTransformer } from '../transformer/index.js';
@@ -39,16 +39,15 @@ const docSchema = defineBlockSchema({
},
});
const docSchemaExtension = BlockSchemaExtension(docSchema);
class RootBlockModel extends BlockModel<
ReturnType<(typeof docSchema)['model']['props']>
> {}
const extensions = [docSchemaExtension];
function createTestOptions() {
const idGenerator = createAutoIncrementIdGenerator();
return { id: 'test-collection', idGenerator };
const schema = new Schema();
schema.register([docSchema]);
return { id: 'test-collection', idGenerator, schema };
}
const transformer = new BaseBlockTransformer(new Map());
@@ -59,7 +58,7 @@ test('model to snapshot', () => {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: 'home', extensions });
const doc = collection.createDoc({ id: 'home' });
doc.load();
doc.addBlock('page');
const rootModel = doc.root as RootBlockModel;
@@ -76,7 +75,7 @@ test('snapshot to model', async () => {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const doc = collection.createDoc({ id: 'home', extensions });
const doc = collection.createDoc({ id: 'home' });
doc.load();
doc.addBlock('page');
const rootModel = doc.root as RootBlockModel;

View File

@@ -1,4 +1,3 @@
export * from './extension';
export * from './schema';
export * from './selection';
export * from './store-extension';

View File

@@ -1,20 +0,0 @@
import { createIdentifier } from '@blocksuite/global/di';
import type { BlockSchemaType } from '../model/block/zod';
import type { ExtensionType } from './extension';
export const BlockSchemaIdentifier =
createIdentifier<BlockSchemaType>('BlockSchema');
export function BlockSchemaExtension(
blockSchema: BlockSchemaType
): ExtensionType {
return {
setup: di => {
di.addImpl(
BlockSchemaIdentifier(blockSchema.model.flavour),
() => blockSchema
);
},
};
}

View File

@@ -15,14 +15,10 @@ export function toDraftModel<Model extends BlockModel = BlockModel>(
origin: Model
): DraftModel<Model> {
const { id, version, flavour, role, keys, text, children } = origin;
const isFlatData = origin.schema.model.isFlatData;
const props = origin.keys.reduce((acc, key) => {
const target = isFlatData ? origin.props : origin;
const value = target[key as keyof typeof target];
return {
...acc,
[key]: value,
[key]: origin[key as keyof Model],
};
}, {} as ModelProps<Model>);

View File

@@ -1,6 +1,7 @@
import type { Slot } from '@blocksuite/global/utils';
import type * as Y from 'yjs';
import type { Schema } from '../schema/schema.js';
import type { AwarenessStore } from '../yjs/awareness.js';
import type { YBlock } from './block/types.js';
import type { Query } from './store/query.js';
@@ -17,6 +18,7 @@ export type YBlocks = Y.Map<YBlock>;
export interface Doc {
readonly id: string;
get meta(): DocMeta | undefined;
get schema(): Schema;
remove(): void;
load(initFn?: () => void): void;

View File

@@ -1,16 +1,11 @@
import { Container, type ServiceProvider } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { type Disposable, Slot } from '@blocksuite/global/utils';
import { computed, signal } from '@preact/signals-core';
import { signal } from '@preact/signals-core';
import type { ExtensionType } from '../../extension/extension.js';
import {
BlockSchemaIdentifier,
StoreSelectionExtension,
} from '../../extension/index.js';
import { Schema } from '../../schema/index.js';
import type { TransformerMiddleware } from '../../transformer/middleware.js';
import { Transformer } from '../../transformer/transformer.js';
import { StoreSelectionExtension } from '../../extension/index.js';
import type { Schema } from '../../schema/index.js';
import {
Block,
type BlockModel,
@@ -25,6 +20,7 @@ import { type Query, runQuery } from './query.js';
import { syncBlockProps } from './utils.js';
export type StoreOptions = {
schema: Schema;
doc: Doc;
id?: string;
readonly?: boolean;
@@ -59,10 +55,6 @@ export class Store {
private readonly _readonly = signal(false);
private readonly _isEmpty = computed(() => {
return this.root?.isEmpty() ?? true;
});
private readonly _schema: Schema;
readonly slots: Doc['slots'] & {
@@ -223,11 +215,7 @@ export class Store {
}
get isEmpty() {
return this._isEmpty.peek();
}
get isEmpty$() {
return this._isEmpty;
return this.root?.isEmpty() ?? true;
}
get loaded() {
@@ -302,7 +290,14 @@ export class Store {
return this._doc.withoutTransact.bind(this._doc);
}
constructor({ doc, readonly, query, provider, extensions }: StoreOptions) {
constructor({
schema,
doc,
readonly,
query,
provider,
extensions,
}: StoreOptions) {
const container = new Container();
container.addImpl(StoreIdentifier, () => this);
@@ -328,11 +323,8 @@ export class Store {
yBlockUpdated: this._doc.slots.yBlockUpdated,
};
this._schema = new Schema();
this._provider.getAll(BlockSchemaIdentifier).forEach(schema => {
this._schema.register([schema]);
});
this._crud = new DocCRUD(this._yBlocks, this._schema);
this._crud = new DocCRUD(this._yBlocks, doc.schema);
this._schema = schema;
if (readonly !== undefined) {
this._readonly.value = readonly;
}
@@ -717,17 +709,4 @@ export class Store {
get getOptional() {
return this.provider.getOptional.bind(this.provider);
}
getTransformer(middlewares: TransformerMiddleware[] = []) {
return new Transformer({
schema: this.schema,
blobCRUD: this.workspace.blobSync,
docCRUD: {
create: (id: string) => this.workspace.createDoc({ id }),
get: (id: string) => this.workspace.getDoc(id),
delete: (id: string) => this.workspace.removeDoc(id),
},
middlewares,
});
}
}

View File

@@ -1,5 +1,7 @@
import type { Slot } from '@blocksuite/global/utils';
import type { Workspace } from './workspace.js';
export type Tag = {
id: string;
value: string;
@@ -36,6 +38,8 @@ export interface WorkspaceMeta {
get name(): string | undefined;
setName(name: string): void;
hasVersion: boolean;
writeVersion(workspace: Workspace): void;
get docs(): unknown[] | undefined;
initialize(): void;

Some files were not shown because too many files have changed in this diff Show More