mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 16:44:56 +00:00
Compare commits
35 Commits
fix/checkb
...
v0.20.5-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b427a89c9a | ||
|
|
00398fc63a | ||
|
|
cbef681125 | ||
|
|
bf42a4ddb2 | ||
|
|
d57ef5c5b3 | ||
|
|
1fd3d618be | ||
|
|
7c8ba13aad | ||
|
|
b3821ad619 | ||
|
|
caa4dfedfc | ||
|
|
18dfad28d7 | ||
|
|
f43a848e18 | ||
|
|
8cec22cc64 | ||
|
|
be94f3fc17 | ||
|
|
e9484e8e15 | ||
|
|
f25266ec88 | ||
|
|
3252dd7a31 | ||
|
|
903d260880 | ||
|
|
2c79d7229f | ||
|
|
fd6d96a38e | ||
|
|
1c5e360d7e | ||
|
|
589622043c | ||
|
|
ce87dcf58e | ||
|
|
2732b96d00 | ||
|
|
0f8c837fbe | ||
|
|
c058f94e15 | ||
|
|
d25b216311 | ||
|
|
e1fd8f5d80 | ||
|
|
866b096304 | ||
|
|
e38e59d4e5 | ||
|
|
7dbc1e300d | ||
|
|
1a9bfeaa2c | ||
|
|
97cc814a22 | ||
|
|
d63f16da5e | ||
|
|
0e4a79959f | ||
|
|
f3911b1b5e |
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -69,6 +69,7 @@ dependencies = [
|
||||
"core-foundation",
|
||||
"coreaudio-rs",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
@@ -3110,7 +3111,6 @@ version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@@ -3912,14 +3912,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.25.1"
|
||||
version = "0.24.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a802c93485fb6781d27e27cb5927f6b00ff8d26b56c70af87267be7e99def97"
|
||||
checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"serde_json",
|
||||
"streaming-iterator",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
@@ -24,6 +24,7 @@ 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" }
|
||||
|
||||
@@ -5,7 +5,11 @@ import {
|
||||
type InsertToPosition,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { DataViewDataType } from '@blocksuite/data-view';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
@@ -93,3 +97,6 @@ export const DataViewBlockSchema = defineBlockSchema({
|
||||
return new DataViewBlockModel();
|
||||
},
|
||||
});
|
||||
|
||||
export const DataViewBlockSchemaExtension =
|
||||
BlockSchemaExtension(DataViewBlockSchema);
|
||||
|
||||
@@ -9,6 +9,7 @@ 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';
|
||||
@@ -57,7 +58,15 @@ export class EmbedBlockComponent<
|
||||
) {
|
||||
this.style.display = 'block';
|
||||
|
||||
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
||||
const insideNote = findAncestorModel(
|
||||
this.model,
|
||||
m => m.flavour === 'affine:note'
|
||||
);
|
||||
|
||||
if (
|
||||
!insideNote &&
|
||||
this.std.get(DocModeProvider).getEditorMode() === 'edgeless'
|
||||
) {
|
||||
this.style.minWidth = `${EMBED_CARD_MIN_WIDTH}px`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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%;
|
||||
@@ -24,6 +25,7 @@ export const styles = css`
|
||||
padding: 12px;
|
||||
border-radius: var(--1, 0px);
|
||||
opacity: var(--add, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.affine-embed-github-content-title {
|
||||
@@ -376,6 +378,15 @@ 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
|
||||
|
||||
@@ -26,9 +26,9 @@ import {
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
BlockSelection,
|
||||
BlockServiceWatcher,
|
||||
BlockStdScope,
|
||||
type EditorHost,
|
||||
LifeCycleWatcher,
|
||||
} from '@blocksuite/block-std';
|
||||
import {
|
||||
GfxControllerIdentifier,
|
||||
@@ -124,27 +124,31 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
this.std.getOptional(EditorSettingProvider) ??
|
||||
signal(GeneralSettingSchema.parse({}));
|
||||
|
||||
class EmbedSyncedDocWatcher extends BlockServiceWatcher {
|
||||
static override readonly flavour = 'affine:embed-synced-doc';
|
||||
class EmbedSyncedDocWatcher extends LifeCycleWatcher {
|
||||
static override key = 'embed-synced-doc-watcher';
|
||||
|
||||
override mounted() {
|
||||
const disposableGroup = this.blockService.disposables;
|
||||
const slots = this.blockService.specSlots;
|
||||
disposableGroup.add(
|
||||
slots.viewConnected.on(({ component }) => {
|
||||
const nextComponent = component as EmbedSyncedDocBlockComponent;
|
||||
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') {
|
||||
nextComponent.depth = nextDepth;
|
||||
currentDisposables.add(() => {
|
||||
nextComponent.depth = 0;
|
||||
});
|
||||
})
|
||||
);
|
||||
disposableGroup.add(
|
||||
slots.viewDisconnected.on(({ component }) => {
|
||||
const nextComponent = component as EmbedSyncedDocBlockComponent;
|
||||
return;
|
||||
}
|
||||
if (payload.method === 'delete') {
|
||||
nextComponent.depth = 0;
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +235,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
||||
[theme]: true,
|
||||
surface: false,
|
||||
selected: this.selected$.value,
|
||||
'show-hover-border': true,
|
||||
})}
|
||||
@click=${this._handleClick}
|
||||
style=${containerStyleMap}
|
||||
|
||||
@@ -57,10 +57,13 @@ export const blockStyles = css`
|
||||
}
|
||||
|
||||
.affine-embed-synced-doc-container {
|
||||
border: 1px solid var(--affine-border-color);
|
||||
border: 1px solid transparent;
|
||||
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%;
|
||||
@@ -151,7 +154,12 @@ 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
|
||||
|
||||
@@ -64,7 +64,6 @@ import {
|
||||
BlockSnapshotSchema,
|
||||
fromJSON,
|
||||
type SliceSnapshot,
|
||||
Transformer,
|
||||
} from '@blocksuite/store';
|
||||
import DOMPurify from 'dompurify';
|
||||
import * as Y from 'yjs';
|
||||
@@ -373,15 +372,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
if (mayBeSurfaceDataJson !== undefined) {
|
||||
const elementsRawData = JSON.parse(mayBeSurfaceDataJson);
|
||||
const { snapshot, blobs } = elementsRawData;
|
||||
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 job = this.std.store.getTransformer();
|
||||
const map = job.assetsManager.getAssets();
|
||||
decodeClipboardBlobs(blobs, map);
|
||||
for (const blobId of map.keys()) {
|
||||
@@ -1377,15 +1368,7 @@ export async function prepareClipboardData(
|
||||
selectedAll: GfxModel[],
|
||||
std: BlockStdScope
|
||||
) {
|
||||
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 job = std.store.getTransformer();
|
||||
const selected = await Promise.all(
|
||||
selectedAll.map(async selected => {
|
||||
const data = serializeElement(selected, selectedAll, job);
|
||||
|
||||
@@ -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,22 +117,26 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
private _initSpec() {
|
||||
const refreshViewport = this._refreshViewport.bind(this);
|
||||
class FramePreviewWatcher extends BlockServiceWatcher {
|
||||
static override readonly flavour = 'affine:page';
|
||||
class FramePreviewWatcher extends LifeCycleWatcher {
|
||||
static override key = 'frame-preview-watcher';
|
||||
|
||||
override mounted() {
|
||||
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();
|
||||
});
|
||||
})
|
||||
);
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
this._previewSpec.extend([FramePreviewWatcher]);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Color, ColorScheme, Palette } from '@blocksuite/affine-model';
|
||||
import { isTransparent, resolveColor } from '@blocksuite/affine-model';
|
||||
import {
|
||||
DefaultTheme,
|
||||
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';
|
||||
@@ -253,7 +257,7 @@ export class EdgelessColorPanel extends LitElement {
|
||||
accessor openColorPicker!: (e: MouseEvent) => void;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor palettes: readonly Palette[] = [];
|
||||
accessor palettes: readonly Palette[] = DefaultTheme.Palettes;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor theme!: ColorScheme;
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
type ColorScheme,
|
||||
DefaultTheme,
|
||||
type StrokeStyle,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { type ColorScheme, 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';
|
||||
@@ -44,7 +40,6 @@ 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)}
|
||||
>
|
||||
|
||||
@@ -65,7 +65,7 @@ export class EdgelessBrushMenu extends EdgelessToolbarToolMixin(
|
||||
class="one-way"
|
||||
.value=${this._props$.value.color}
|
||||
.theme=${this._theme$.value}
|
||||
.palettes=${DefaultTheme.StrokeColorPalettes}
|
||||
.palettes=${DefaultTheme.StrokeColorShortPalettes}
|
||||
.hasTransparent=${!this.edgeless.doc
|
||||
.get(FeatureFlagService)
|
||||
.getFlag('enable_color_picker')}
|
||||
|
||||
@@ -133,7 +133,7 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
|
||||
class="one-way"
|
||||
.value=${stroke}
|
||||
.theme=${this._theme$.value}
|
||||
.palettes=${DefaultTheme.StrokeColorPalettes}
|
||||
.palettes=${DefaultTheme.StrokeColorShortPalettes}
|
||||
.hasTransparent=${!this.edgeless.doc
|
||||
.get(FeatureFlagService)
|
||||
.getFlag('enable_color_picker')}
|
||||
|
||||
@@ -75,9 +75,10 @@ export class EdgelessShapeMenu extends SignalWatcher(
|
||||
const filled = !isTransparent(value);
|
||||
const fillColor = value;
|
||||
const strokeColor = filled
|
||||
? DefaultTheme.StrokeColorPalettes.find(palette => palette.key === key)
|
||||
?.value
|
||||
: DefaultTheme.StrokeColorMap.Grey;
|
||||
? DefaultTheme.StrokeColorShortPalettes.find(
|
||||
palette => palette.key === key
|
||||
)?.value
|
||||
: DefaultTheme.StrokeColorShortMap.Grey;
|
||||
|
||||
const { shapeName } = this._props$.value;
|
||||
this.edgeless.std
|
||||
@@ -173,7 +174,7 @@ export class EdgelessShapeMenu extends SignalWatcher(
|
||||
class="one-way"
|
||||
.value=${fillColor}
|
||||
.theme=${this._theme$.value}
|
||||
.palettes=${DefaultTheme.FillColorPalettes}
|
||||
.palettes=${DefaultTheme.FillColorShortPalettes}
|
||||
.hasTransparent=${!this.edgeless.doc
|
||||
.get(FeatureFlagService)
|
||||
.getFlag('enable_color_picker')}
|
||||
|
||||
@@ -33,7 +33,7 @@ export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
|
||||
class="one-way"
|
||||
.value=${this.color}
|
||||
.theme=${this._theme$.value}
|
||||
.palettes=${DefaultTheme.StrokeColorPalettes}
|
||||
.palettes=${DefaultTheme.StrokeColorShortPalettes}
|
||||
@select=${(e: ColorEvent) => this.onChange({ color: e.detail })}
|
||||
></edgeless-color-panel>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,14 @@ 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 { ToolController } from '@blocksuite/block-std/gfx';
|
||||
import {
|
||||
GfxControllerIdentifier,
|
||||
ToolController,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
@@ -56,17 +59,12 @@ export const edgelessToolbarWidget = WidgetViewExtension(
|
||||
literal`${unsafeStatic(EDGELESS_TOOLBAR_WIDGET)}`
|
||||
);
|
||||
|
||||
class EdgelessLocker extends BlockServiceWatcher {
|
||||
static override readonly flavour = 'affine:page';
|
||||
class EdgelessLocker extends LifeCycleWatcher {
|
||||
static override key = 'edgeless-locker';
|
||||
|
||||
override mounted() {
|
||||
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;
|
||||
})
|
||||
);
|
||||
const { viewport } = this.std.get(GfxControllerIdentifier);
|
||||
viewport.locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
type DocSnapshot,
|
||||
DocSnapshotSchema,
|
||||
type SnapshotNode,
|
||||
Transformer,
|
||||
type Transformer,
|
||||
} from '@blocksuite/store';
|
||||
import type * as Y from 'yjs';
|
||||
/**
|
||||
@@ -90,16 +90,7 @@ export class TemplateJob {
|
||||
type: TemplateType;
|
||||
|
||||
constructor({ model, type, middlewares }: TemplateJobConfig) {
|
||||
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.job = model.doc.getTransformer();
|
||||
this.model = model;
|
||||
this.type = TEMPLATE_TYPES.includes(type as TemplateType)
|
||||
? (type as TemplateType)
|
||||
@@ -320,8 +311,7 @@ export class TemplateJob {
|
||||
from: Record<string, Record<string, unknown>>,
|
||||
to: Y.Map<Y.Map<unknown>>
|
||||
) {
|
||||
const schema =
|
||||
this.model.doc.workspace.schema.flavourSchemaMap.get('affine:surface');
|
||||
const schema = this.model.doc.schema.get('affine:surface');
|
||||
const surfaceTransformer = schema?.transformer?.(
|
||||
new Map()
|
||||
) as SurfaceBlockTransformer;
|
||||
|
||||
@@ -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,15 +40,7 @@ export function getSortedCloneElements(elements: GfxModel[]) {
|
||||
|
||||
export function prepareCloneData(elements: GfxModel[], std: BlockStdScope) {
|
||||
elements = sortEdgelessElements(elements);
|
||||
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 job = std.store.getTransformer();
|
||||
const res = elements.map(element => {
|
||||
const data = serializeElement(element, elements, job);
|
||||
return data;
|
||||
|
||||
@@ -10,7 +10,6 @@ export class PreviewRootBlockComponent extends BlockComponent {
|
||||
static override styles = css`
|
||||
affine-preview-root {
|
||||
display: block;
|
||||
padding: 0 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -8,19 +8,21 @@ import {
|
||||
import { SpecProvider } from '@blocksuite/affine-shared/utils';
|
||||
import { Container } from '@blocksuite/global/di';
|
||||
import { sha } from '@blocksuite/global/utils';
|
||||
import type { Store, Workspace } from '@blocksuite/store';
|
||||
import type { Schema, 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;
|
||||
};
|
||||
|
||||
@@ -41,19 +43,10 @@ function getProvider() {
|
||||
*/
|
||||
async function exportDoc(doc: Store) {
|
||||
const provider = getProvider();
|
||||
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 job = doc.getTransformer([
|
||||
docLinkBaseURLMiddleware(doc.workspace.id),
|
||||
titleMiddleware(doc.workspace.meta.docMetas),
|
||||
]);
|
||||
const snapshot = job.docToSnapshot(doc);
|
||||
const adapter = new HtmlAdapter(job, provider);
|
||||
if (!snapshot) {
|
||||
@@ -87,18 +80,20 @@ 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: collection.schema,
|
||||
schema,
|
||||
blobCRUD: collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => collection.createDoc({ id }),
|
||||
@@ -127,10 +122,15 @@ 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, imported }: ImportHTMLZipOptions) {
|
||||
async function importHTMLZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
}: ImportHTMLZipOptions) {
|
||||
const provider = getProvider();
|
||||
const unzip = new Unzip();
|
||||
await unzip.load(imported);
|
||||
@@ -161,7 +161,7 @@ async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
|
||||
htmlBlobs.map(async ([fileName, blob]) => {
|
||||
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
|
||||
const job = new Transformer({
|
||||
schema: collection.schema,
|
||||
schema,
|
||||
blobCRUD: collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => collection.createDoc({ id }),
|
||||
|
||||
@@ -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 { Store, Workspace } from '@blocksuite/store';
|
||||
import type { Schema, Store, Workspace } from '@blocksuite/store';
|
||||
import { extMimeMap, Transformer } from '@blocksuite/store';
|
||||
|
||||
import { createAssetsArchive, download, Unzip } from './utils.js';
|
||||
@@ -31,12 +31,14 @@ type ImportMarkdownToBlockOptions = {
|
||||
|
||||
type ImportMarkdownToDocOptions = {
|
||||
collection: Workspace;
|
||||
schema: Schema;
|
||||
markdown: string;
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
type ImportMarkdownZipOptions = {
|
||||
collection: Workspace;
|
||||
schema: Schema;
|
||||
imported: Blob;
|
||||
};
|
||||
|
||||
@@ -47,19 +49,10 @@ type ImportMarkdownZipOptions = {
|
||||
*/
|
||||
async function exportDoc(doc: Store) {
|
||||
const provider = getProvider();
|
||||
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 job = doc.getTransformer([
|
||||
docLinkBaseURLMiddleware(doc.workspace.id),
|
||||
titleMiddleware(doc.workspace.meta.docMetas),
|
||||
]);
|
||||
const snapshot = job.docToSnapshot(doc);
|
||||
|
||||
const adapter = new MarkdownAdapter(job, provider);
|
||||
@@ -107,19 +100,10 @@ async function importMarkdownToBlock({
|
||||
blockId,
|
||||
}: ImportMarkdownToBlockOptions) {
|
||||
const provider = getProvider();
|
||||
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 job = doc.getTransformer([
|
||||
defaultImageProxyMiddleware,
|
||||
docLinkBaseURLMiddleware(doc.workspace.id),
|
||||
]);
|
||||
const adapter = new MarkdownAdapter(job, provider);
|
||||
const snapshot = await adapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
@@ -143,18 +127,20 @@ 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: collection.schema,
|
||||
schema,
|
||||
blobCRUD: collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => collection.createDoc({ id }),
|
||||
@@ -182,11 +168,13 @@ 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();
|
||||
@@ -219,7 +207,7 @@ async function importMarkdownZip({
|
||||
markdownBlobs.map(async ([fileName, blob]) => {
|
||||
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
|
||||
const job = new Transformer({
|
||||
schema: collection.schema,
|
||||
schema,
|
||||
blobCRUD: collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => collection.createDoc({ id }),
|
||||
|
||||
@@ -3,12 +3,18 @@ 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, Transformer, type Workspace } from '@blocksuite/store';
|
||||
import {
|
||||
extMimeMap,
|
||||
type Schema,
|
||||
Transformer,
|
||||
type Workspace,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
import { Unzip } from './utils.js';
|
||||
|
||||
type ImportNotionZipOptions = {
|
||||
collection: Workspace;
|
||||
schema: Schema;
|
||||
imported: Blob;
|
||||
};
|
||||
|
||||
@@ -26,6 +32,7 @@ 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:
|
||||
@@ -36,6 +43,7 @@ function getProvider() {
|
||||
*/
|
||||
async function importNotionZip({
|
||||
collection,
|
||||
schema,
|
||||
imported,
|
||||
}: ImportNotionZipOptions) {
|
||||
const provider = getProvider();
|
||||
@@ -117,7 +125,7 @@ async function importNotionZip({
|
||||
}
|
||||
const pagePromises = Array.from(pagePaths).map(async path => {
|
||||
const job = new Transformer({
|
||||
schema: collection.schema,
|
||||
schema,
|
||||
blobCRUD: collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => collection.createDoc({ id }),
|
||||
|
||||
@@ -3,15 +3,19 @@ import {
|
||||
titleMiddleware,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { sha } from '@blocksuite/global/utils';
|
||||
import type { DocSnapshot, Store, Workspace } from '@blocksuite/store';
|
||||
import type { DocSnapshot, Schema, 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, docs: Store[]) {
|
||||
async function exportDocs(
|
||||
collection: Workspace,
|
||||
schema: Schema,
|
||||
docs: Store[]
|
||||
) {
|
||||
const zip = new Zip();
|
||||
const job = new Transformer({
|
||||
schema: collection.schema,
|
||||
schema,
|
||||
blobCRUD: collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => collection.createDoc({ id }),
|
||||
@@ -70,7 +74,11 @@ async function exportDocs(collection: Workspace, docs: Store[]) {
|
||||
return download(downloadBlob, `${collection.id}.bs.zip`);
|
||||
}
|
||||
|
||||
async function importDocs(collection: Workspace, imported: Blob) {
|
||||
async function importDocs(
|
||||
collection: Workspace,
|
||||
schema: Schema,
|
||||
imported: Blob
|
||||
) {
|
||||
const unzip = new Unzip();
|
||||
await unzip.load(imported);
|
||||
|
||||
@@ -98,7 +106,7 @@ async function importDocs(collection: Workspace, imported: Blob) {
|
||||
}
|
||||
|
||||
const job = new Transformer({
|
||||
schema: collection.schema,
|
||||
schema,
|
||||
blobCRUD: collection.blobSync,
|
||||
docCRUD: {
|
||||
create: (id: string) => collection.createDoc({ id }),
|
||||
|
||||
@@ -134,13 +134,12 @@ 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>
|
||||
`;
|
||||
@@ -159,7 +158,6 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
|
||||
<edgeless-color-panel
|
||||
.value=${selectedColor}
|
||||
.theme=${colorScheme}
|
||||
.palettes=${DefaultTheme.Palettes}
|
||||
@select=${this._setBrushColor}
|
||||
>
|
||||
</edgeless-color-panel>
|
||||
|
||||
@@ -373,13 +373,12 @@ 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
|
||||
|
||||
@@ -13,7 +13,6 @@ import { renderToolbarSeparator } from '@blocksuite/affine-components/toolbar';
|
||||
import {
|
||||
type ColorScheme,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DefaultTheme,
|
||||
type FrameBlockModel,
|
||||
NoteBlockModel,
|
||||
NoteDisplayMode,
|
||||
@@ -201,13 +200,12 @@ 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>
|
||||
`;
|
||||
@@ -229,7 +227,6 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
|
||||
<edgeless-color-panel
|
||||
.value=${background}
|
||||
.theme=${colorScheme}
|
||||
.palettes=${DefaultTheme.Palettes}
|
||||
@select=${this._setFrameBackground}
|
||||
>
|
||||
</edgeless-color-panel>
|
||||
|
||||
@@ -338,7 +338,6 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
|
||||
.colors=${colors}
|
||||
.colorType=${type}
|
||||
.theme=${colorScheme}
|
||||
.palettes=${DefaultTheme.Palettes}
|
||||
>
|
||||
</edgeless-color-picker-button>
|
||||
`;
|
||||
@@ -362,7 +361,6 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
|
||||
aria-label="Fill colors"
|
||||
.value=${selectedFillColor}
|
||||
.theme=${colorScheme}
|
||||
.palettes=${DefaultTheme.Palettes}
|
||||
@select=${this._setShapeFillColor}
|
||||
>
|
||||
</edgeless-color-panel>
|
||||
@@ -390,7 +388,6 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
|
||||
.colors=${colors}
|
||||
.colorType=${type}
|
||||
.theme=${colorScheme}
|
||||
.palettes=${DefaultTheme.Palettes}
|
||||
.hollowCircle=${true}
|
||||
>
|
||||
<div
|
||||
@@ -453,8 +450,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()}
|
||||
@@ -465,7 +462,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>
|
||||
|
||||
@@ -344,6 +344,10 @@ 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(
|
||||
[
|
||||
@@ -389,14 +393,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=${DefaultTheme.Palettes}
|
||||
.palettes=${palettes}
|
||||
>
|
||||
</edgeless-color-picker-button>
|
||||
`;
|
||||
@@ -418,7 +422,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
|
||||
<edgeless-color-panel
|
||||
.value=${selectedColor}
|
||||
.theme=${colorScheme}
|
||||
.palettes=${DefaultTheme.Palettes}
|
||||
.palettes=${palettes}
|
||||
@select=${this._setTextColor}
|
||||
></edgeless-color-panel>
|
||||
</editor-menu-button>
|
||||
|
||||
@@ -233,6 +233,7 @@ export function createNewDocMenuGroup(
|
||||
};
|
||||
showImportModal({
|
||||
collection: doc.workspace,
|
||||
schema: doc.schema,
|
||||
onSuccess,
|
||||
onFail,
|
||||
});
|
||||
|
||||
@@ -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 { Workspace } from '@blocksuite/store';
|
||||
import type { Schema, Workspace } from '@blocksuite/store';
|
||||
import { html, LitElement, type PropertyValues } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
|
||||
@@ -31,6 +31,7 @@ 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()
|
||||
@@ -63,6 +64,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
|
||||
}
|
||||
const pageId = await HtmlTransformer.importHTMLToDoc({
|
||||
collection: this.collection,
|
||||
schema: this.schema,
|
||||
html: text,
|
||||
fileName,
|
||||
});
|
||||
@@ -93,6 +95,7 @@ export class ImportDoc extends WithDisposable(LitElement) {
|
||||
}
|
||||
const pageId = await MarkdownTransformer.importMarkdownToDoc({
|
||||
collection: this.collection,
|
||||
schema: this.schema,
|
||||
markdown: text,
|
||||
fileName,
|
||||
});
|
||||
@@ -117,6 +120,7 @@ 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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import type { Schema, Workspace } from '@blocksuite/store';
|
||||
|
||||
import {
|
||||
ImportDoc,
|
||||
@@ -7,12 +7,14 @@ 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;
|
||||
@@ -22,6 +24,7 @@ export function showImportModal({
|
||||
}) {
|
||||
const importDoc = new ImportDoc(
|
||||
collection,
|
||||
schema,
|
||||
onSuccess,
|
||||
onFail,
|
||||
abortController
|
||||
|
||||
@@ -20,10 +20,8 @@ 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,
|
||||
RootBlockConfigExtension,
|
||||
} from '../../index.js';
|
||||
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
|
||||
import { RootBlockConfigExtension } from '../../root-config.js';
|
||||
import {
|
||||
type AFFINE_LINKED_DOC_WIDGET,
|
||||
getMenus,
|
||||
|
||||
@@ -423,8 +423,9 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
|
||||
|
||||
override mounted() {
|
||||
const disposable = this.std.view.viewUpdated.on(payload => {
|
||||
if (payload.type !== 'block') return;
|
||||
if (
|
||||
payload.type === 'add' &&
|
||||
payload.method === 'add' &&
|
||||
matchModels(payload.view.model, [RootBlockModel])
|
||||
) {
|
||||
disposable.dispose();
|
||||
|
||||
@@ -50,7 +50,11 @@ export {
|
||||
} from './adapters/index.js';
|
||||
export type { SurfaceContext } from './surface-block.js';
|
||||
export { SurfaceBlockComponent } from './surface-block.js';
|
||||
export { SurfaceBlockModel, SurfaceBlockSchema } from './surface-model.js';
|
||||
export {
|
||||
SurfaceBlockModel,
|
||||
SurfaceBlockSchema,
|
||||
SurfaceBlockSchemaExtension,
|
||||
} from './surface-model.js';
|
||||
export type { SurfaceBlockService } from './surface-service.js';
|
||||
export {
|
||||
EdgelessSurfaceBlockSpec,
|
||||
|
||||
@@ -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 { defineBlockSchema } from '@blocksuite/store';
|
||||
import { BlockSchemaExtension, defineBlockSchema } from '@blocksuite/store';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { elementsCtorMap } from './element-model/index.js';
|
||||
@@ -36,6 +36,9 @@ export const SurfaceBlockSchema = defineBlockSchema({
|
||||
toModel: () => new SurfaceBlockModel(),
|
||||
});
|
||||
|
||||
export const SurfaceBlockSchemaExtension =
|
||||
BlockSchemaExtension(SurfaceBlockSchema);
|
||||
|
||||
export type SurfaceMiddleware = (surface: SurfaceBlockModel) => () => void;
|
||||
|
||||
export class SurfaceBlockModel extends BaseSurfaceModel {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ColorScheme, Palette } from '@blocksuite/affine-model';
|
||||
import { resolveColor } from '@blocksuite/affine-model';
|
||||
import { DefaultTheme, 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[] = [];
|
||||
accessor palettes: Palette[] = DefaultTheme.Palettes;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor pick!: (event: PickColorEvent) => void;
|
||||
|
||||
@@ -36,20 +36,22 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.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 {
|
||||
|
||||
@@ -13,8 +13,11 @@ export class ToggleButton extends WithDisposable(ShadowlessElement) {
|
||||
.toggle-icon {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
margin-top: 0.45em;
|
||||
justify-content: start;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: calc((1em - 16px) / 2 + 5px);
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
border-radius: 4px;
|
||||
@@ -22,6 +25,7 @@ export class ToggleButton extends WithDisposable(ShadowlessElement) {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.toggle-icon:hover {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ import type {
|
||||
GfxElementGeometry,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { GfxCompatible } from '@blocksuite/block-std/gfx';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
import type { EmbedCardStyle } from '../../utils/index.js';
|
||||
import { AttachmentBlockTransformer } from './attachment-transformer.js';
|
||||
@@ -86,6 +90,10 @@ export const AttachmentBlockSchema = defineBlockSchema({
|
||||
toModel: () => new AttachmentBlockModel(),
|
||||
});
|
||||
|
||||
export const AttachmentBlockSchemaExtension = BlockSchemaExtension(
|
||||
AttachmentBlockSchema
|
||||
);
|
||||
|
||||
export class AttachmentBlockModel
|
||||
extends GfxCompatible<AttachmentBlockProps>(BlockModel)
|
||||
implements GfxElementGeometry {}
|
||||
|
||||
@@ -3,7 +3,11 @@ import type {
|
||||
GfxElementGeometry,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { GfxCompatible } from '@blocksuite/block-std/gfx';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
import type { EmbedCardStyle, LinkPreviewData } from '../../utils/index.js';
|
||||
|
||||
@@ -54,6 +58,9 @@ export const BookmarkBlockSchema = defineBlockSchema({
|
||||
toModel: () => new BookmarkBlockModel(),
|
||||
});
|
||||
|
||||
export const BookmarkBlockSchemaExtension =
|
||||
BlockSchemaExtension(BookmarkBlockSchema);
|
||||
|
||||
export class BookmarkBlockModel
|
||||
extends GfxCompatible<BookmarkBlockProps>(BlockModel)
|
||||
implements GfxElementGeometry {}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
type Text,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
interface CodeBlockProps {
|
||||
text: Text;
|
||||
@@ -30,6 +35,8 @@ export const CodeBlockSchema = defineBlockSchema({
|
||||
toModel: () => new CodeBlockModel(),
|
||||
});
|
||||
|
||||
export const CodeBlockSchemaExtension = BlockSchemaExtension(CodeBlockSchema);
|
||||
|
||||
export class CodeBlockModel extends BlockModel<CodeBlockProps> {
|
||||
override text!: Text;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Text } from '@blocksuite/store';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
import type { Column, SerializedCells, ViewBasicDataType } from './types.js';
|
||||
|
||||
@@ -28,3 +32,6 @@ export const DatabaseBlockSchema = defineBlockSchema({
|
||||
},
|
||||
toModel: () => new DatabaseBlockModel(),
|
||||
});
|
||||
|
||||
export const DatabaseBlockSchemaExtension =
|
||||
BlockSchemaExtension(DatabaseBlockSchema);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export const DividerBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:divider',
|
||||
@@ -15,3 +19,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export class DividerBlockModel extends BlockModel<Props> {}
|
||||
|
||||
export const DividerBlockSchemaExtension =
|
||||
BlockSchemaExtension(DividerBlockSchema);
|
||||
|
||||
@@ -3,7 +3,11 @@ import type {
|
||||
GfxElementGeometry,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { GfxCompatible } from '@blocksuite/block-std/gfx';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
@@ -76,6 +80,10 @@ export const EdgelessTextBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const EdgelessTextBlockSchemaExtension = BlockSchemaExtension(
|
||||
EdgelessTextBlockSchema
|
||||
);
|
||||
|
||||
export class EdgelessTextBlockModel
|
||||
extends GfxCompatible<EdgelessTextProps>(BlockModel)
|
||||
implements GfxElementGeometry {}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BlockSchemaExtension } from '@blocksuite/store';
|
||||
|
||||
import { createEmbedBlockSchema } from '../../../utils/index.js';
|
||||
import {
|
||||
type EmbedFigmaBlockProps,
|
||||
@@ -20,3 +22,7 @@ export const EmbedFigmaBlockSchema = createEmbedBlockSchema({
|
||||
toModel: () => new EmbedFigmaModel(),
|
||||
props: (): EmbedFigmaBlockProps => defaultEmbedFigmaProps,
|
||||
});
|
||||
|
||||
export const EmbedFigmaBlockSchemaExtension = BlockSchemaExtension(
|
||||
EmbedFigmaBlockSchema
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BlockSchemaExtension } from '@blocksuite/store';
|
||||
|
||||
import { createEmbedBlockSchema } from '../../../utils/index.js';
|
||||
import {
|
||||
type EmbedGithubBlockProps,
|
||||
@@ -29,3 +31,7 @@ export const EmbedGithubBlockSchema = createEmbedBlockSchema({
|
||||
toModel: () => new EmbedGithubModel(),
|
||||
props: (): EmbedGithubBlockProps => defaultEmbedGithubProps,
|
||||
});
|
||||
|
||||
export const EmbedGithubBlockSchemaExtension = BlockSchemaExtension(
|
||||
EmbedGithubBlockSchema
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BlockSchemaExtension } from '@blocksuite/store';
|
||||
|
||||
import { createEmbedBlockSchema } from '../../../utils/index.js';
|
||||
import {
|
||||
type EmbedHtmlBlockProps,
|
||||
@@ -18,3 +20,6 @@ export const EmbedHtmlBlockSchema = createEmbedBlockSchema({
|
||||
toModel: () => new EmbedHtmlModel(),
|
||||
props: (): EmbedHtmlBlockProps => defaultEmbedHtmlProps,
|
||||
});
|
||||
|
||||
export const EmbedHtmlBlockSchemaExtension =
|
||||
BlockSchemaExtension(EmbedHtmlBlockSchema);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BlockSchemaExtension } from '@blocksuite/store';
|
||||
|
||||
import { createEmbedBlockSchema } from '../../../utils/index.js';
|
||||
import {
|
||||
type EmbedLinkedDocBlockProps,
|
||||
@@ -20,3 +22,7 @@ export const EmbedLinkedDocBlockSchema = createEmbedBlockSchema({
|
||||
toModel: () => new EmbedLinkedDocModel(),
|
||||
props: (): EmbedLinkedDocBlockProps => defaultEmbedLinkedDocBlockProps,
|
||||
});
|
||||
|
||||
export const EmbedLinkedDocBlockSchemaExtension = BlockSchemaExtension(
|
||||
EmbedLinkedDocBlockSchema
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BlockSchemaExtension } from '@blocksuite/store';
|
||||
|
||||
import { createEmbedBlockSchema } from '../../../utils/index.js';
|
||||
import {
|
||||
type EmbedLoomBlockProps,
|
||||
@@ -22,3 +24,6 @@ export const EmbedLoomBlockSchema = createEmbedBlockSchema({
|
||||
toModel: () => new EmbedLoomModel(),
|
||||
props: (): EmbedLoomBlockProps => defaultEmbedLoomProps,
|
||||
});
|
||||
|
||||
export const EmbedLoomBlockSchemaExtension =
|
||||
BlockSchemaExtension(EmbedLoomBlockSchema);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BlockSchemaExtension } from '@blocksuite/store';
|
||||
|
||||
import { createEmbedBlockSchema } from '../../../utils/index.js';
|
||||
import {
|
||||
type EmbedSyncedDocBlockProps,
|
||||
@@ -21,3 +23,7 @@ export const EmbedSyncedDocBlockSchema = createEmbedBlockSchema({
|
||||
toModel: () => new EmbedSyncedDocModel(),
|
||||
props: (): EmbedSyncedDocBlockProps => defaultEmbedSyncedDocBlockProps,
|
||||
});
|
||||
|
||||
export const EmbedSyncedDocBlockSchemaExtension = BlockSchemaExtension(
|
||||
EmbedSyncedDocBlockSchema
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BlockSchemaExtension } from '@blocksuite/store';
|
||||
|
||||
import { createEmbedBlockSchema } from '../../../utils/index.js';
|
||||
import {
|
||||
type EmbedYoutubeBlockProps,
|
||||
@@ -25,3 +27,7 @@ export const EmbedYoutubeBlockSchema = createEmbedBlockSchema({
|
||||
toModel: () => new EmbedYoutubeModel(),
|
||||
props: (): EmbedYoutubeBlockProps => defaultEmbedYoutubeProps,
|
||||
});
|
||||
|
||||
export const EmbedYoutubeBlockSchemaExtension = BlockSchemaExtension(
|
||||
EmbedYoutubeBlockSchema
|
||||
);
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
hasDescendantElementImpl,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
type Text,
|
||||
} from '@blocksuite/store';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type Color, ColorSchema, DefaultTheme } from '../../themes/index.js';
|
||||
@@ -57,6 +62,8 @@ export const FrameBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const FrameBlockSchemaExtension = BlockSchemaExtension(FrameBlockSchema);
|
||||
|
||||
export class FrameBlockModel
|
||||
extends GfxCompatible<FrameBlockProps>(BlockModel)
|
||||
implements GfxElementGeometry, GfxGroupCompatibleInterface
|
||||
|
||||
@@ -3,7 +3,11 @@ import type {
|
||||
GfxElementGeometry,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { GfxCompatible } from '@blocksuite/block-std/gfx';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
import { ImageBlockTransformer } from './image-transformer.js';
|
||||
|
||||
@@ -40,6 +44,8 @@ export const ImageBlockSchema = defineBlockSchema({
|
||||
toModel: () => new ImageBlockModel(),
|
||||
});
|
||||
|
||||
export const ImageBlockSchemaExtension = BlockSchemaExtension(ImageBlockSchema);
|
||||
|
||||
export class ImageBlockModel
|
||||
extends GfxCompatible<ImageBlockProps>(BlockModel)
|
||||
implements GfxElementGeometry {}
|
||||
|
||||
@@ -3,7 +3,11 @@ import {
|
||||
GfxCompatible,
|
||||
type GfxElementGeometry,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export type LatexProps = {
|
||||
latex: string;
|
||||
@@ -34,6 +38,8 @@ export const LatexBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const LatexBlockSchemaExtension = BlockSchemaExtension(LatexBlockSchema);
|
||||
|
||||
export class LatexBlockModel
|
||||
extends GfxCompatible<LatexProps>(BlockModel)
|
||||
implements GfxElementGeometry {}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Text } from '@blocksuite/store';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
// `toggle` type has been deprecated, do not use it
|
||||
export type ListType = 'bulleted' | 'numbered' | 'todo' | 'toggle';
|
||||
@@ -38,6 +42,8 @@ export const ListBlockSchema = defineBlockSchema({
|
||||
toModel: () => new ListBlockModel(),
|
||||
});
|
||||
|
||||
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
|
||||
|
||||
export class ListBlockModel extends BlockModel<ListProps> {
|
||||
override text!: Text;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import type {
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { GfxCompatible } from '@blocksuite/block-std/gfx';
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
@@ -21,6 +25,7 @@ import {
|
||||
StrokeStyleSchema,
|
||||
} from '../../consts/note';
|
||||
import { type Color, ColorSchema, DefaultTheme } from '../../themes';
|
||||
import { TableModelFlavour } from '../table';
|
||||
|
||||
export const NoteZodSchema = z
|
||||
.object({
|
||||
@@ -47,7 +52,6 @@ export const NoteZodSchema = z
|
||||
},
|
||||
},
|
||||
});
|
||||
import { TableModelFlavour } from '../table';
|
||||
|
||||
export const NoteBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:note',
|
||||
@@ -92,6 +96,7 @@ export const NoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
|
||||
export type NoteProps = {
|
||||
background: Color;
|
||||
displayMode: NoteDisplayMode;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { BlockModel, defineBlockSchema, type Text } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
type Text,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export type ParagraphType =
|
||||
| 'text'
|
||||
@@ -37,6 +42,9 @@ export const ParagraphBlockSchema = defineBlockSchema({
|
||||
toModel: () => new ParagraphBlockModel(),
|
||||
});
|
||||
|
||||
export const ParagraphBlockSchemaExtension =
|
||||
BlockSchemaExtension(ParagraphBlockSchema);
|
||||
|
||||
export class ParagraphBlockModel extends BlockModel<ParagraphProps> {
|
||||
override text!: Text;
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Text } from '@blocksuite/store';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export type RootBlockProps = {
|
||||
title: Text;
|
||||
@@ -51,3 +55,5 @@ export const RootBlockSchema = defineBlockSchema({
|
||||
},
|
||||
toModel: () => new RootBlockModel(),
|
||||
});
|
||||
|
||||
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export type SurfaceRefProps = {
|
||||
reference: string;
|
||||
@@ -21,4 +25,8 @@ export const SurfaceRefBlockSchema = defineBlockSchema({
|
||||
toModel: () => new SurfaceRefBlockModel(),
|
||||
});
|
||||
|
||||
export const SurfaceRefBlockSchemaExtension = BlockSchemaExtension(
|
||||
SurfaceRefBlockSchema
|
||||
);
|
||||
|
||||
export class SurfaceRefBlockModel extends BlockModel<SurfaceRefProps> {}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import type { Text } from '@blocksuite/store';
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export type TableCell = {
|
||||
text: Text;
|
||||
@@ -56,3 +60,5 @@ export const TableBlockSchema = defineBlockSchema({
|
||||
},
|
||||
toModel: () => new TableBlockModel(),
|
||||
});
|
||||
|
||||
export const TableBlockSchemaExtension = BlockSchemaExtension(TableBlockSchema);
|
||||
|
||||
@@ -23,7 +23,7 @@ export const LINE_WIDTHS = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Use `DefaultTheme.StrokeColorMap` instead.
|
||||
* Use `DefaultTheme.StrokeColorShortMap` instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
@@ -44,7 +44,7 @@ export enum LineColor {
|
||||
export const LineColorMap = createEnumMap(LineColor);
|
||||
|
||||
/**
|
||||
* Use `DefaultTheme.StrokeColorPalettes` instead.
|
||||
* Use `DefaultTheme.StrokeColorShortPalettes` instead.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@@ -77,11 +77,11 @@ export abstract class MindmapStyleGetter {
|
||||
|
||||
export class StyleOne extends MindmapStyleGetter {
|
||||
private readonly _colorOrders = [
|
||||
DefaultTheme.StrokeColorMap.Purple,
|
||||
DefaultTheme.StrokeColorMap.Magenta,
|
||||
DefaultTheme.StrokeColorMap.Orange,
|
||||
DefaultTheme.StrokeColorMap.Yellow,
|
||||
DefaultTheme.StrokeColorMap.Green,
|
||||
DefaultTheme.StrokeColorShortMap.Purple,
|
||||
DefaultTheme.StrokeColorShortMap.Magenta,
|
||||
DefaultTheme.StrokeColorShortMap.Orange,
|
||||
DefaultTheme.StrokeColorShortMap.Yellow,
|
||||
DefaultTheme.StrokeColorShortMap.Green,
|
||||
'#7ae2d5',
|
||||
];
|
||||
|
||||
@@ -188,9 +188,9 @@ export const styleOne = new StyleOne();
|
||||
|
||||
export class StyleTwo extends MindmapStyleGetter {
|
||||
private readonly _colorOrders = [
|
||||
DefaultTheme.StrokeColorMap.Blue,
|
||||
DefaultTheme.StrokeColorShortMap.Blue,
|
||||
'#7ae2d5',
|
||||
DefaultTheme.StrokeColorMap.Yellow,
|
||||
DefaultTheme.StrokeColorShortMap.Yellow,
|
||||
];
|
||||
|
||||
readonly root = {
|
||||
@@ -207,7 +207,7 @@ export class StyleTwo extends MindmapStyleGetter {
|
||||
color: DefaultTheme.pureBlack,
|
||||
|
||||
filled: true,
|
||||
fillColor: DefaultTheme.StrokeColorMap.Yellow,
|
||||
fillColor: DefaultTheme.StrokeColorShortMap.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.StrokeColorMap.Yellow,
|
||||
DefaultTheme.StrokeColorMap.Green,
|
||||
DefaultTheme.StrokeColorShortMap.Yellow,
|
||||
DefaultTheme.StrokeColorShortMap.Green,
|
||||
'#5cc7ba',
|
||||
];
|
||||
|
||||
@@ -317,7 +317,7 @@ export class StyleThree extends MindmapStyleGetter {
|
||||
color: DefaultTheme.pureBlack,
|
||||
|
||||
filled: true,
|
||||
fillColor: DefaultTheme.StrokeColorMap.Yellow,
|
||||
fillColor: DefaultTheme.StrokeColorShortMap.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.StrokeColorMap.Purple,
|
||||
DefaultTheme.StrokeColorMap.Magenta,
|
||||
DefaultTheme.StrokeColorMap.Orange,
|
||||
DefaultTheme.StrokeColorMap.Yellow,
|
||||
DefaultTheme.StrokeColorMap.Green,
|
||||
DefaultTheme.StrokeColorMap.Blue,
|
||||
DefaultTheme.StrokeColorShortMap.Purple,
|
||||
DefaultTheme.StrokeColorShortMap.Magenta,
|
||||
DefaultTheme.StrokeColorShortMap.Orange,
|
||||
DefaultTheme.StrokeColorShortMap.Yellow,
|
||||
DefaultTheme.StrokeColorShortMap.Green,
|
||||
DefaultTheme.StrokeColorShortMap.Blue,
|
||||
];
|
||||
|
||||
readonly root = {
|
||||
|
||||
@@ -72,15 +72,44 @@ const NoteBackgroundColorPalettes: Palette[] = [
|
||||
...buildPalettes(NoteBackgroundColorMap),
|
||||
] as const;
|
||||
|
||||
const StrokeColorMap = { ...Medium, Black, White } as const;
|
||||
const StrokeColorShortMap = { ...Medium, Black, White } as const;
|
||||
|
||||
const StrokeColorPalettes: Palette[] = [
|
||||
...buildPalettes(StrokeColorMap),
|
||||
const StrokeColorShortPalettes: Palette[] = [
|
||||
...buildPalettes(StrokeColorShortMap),
|
||||
] as const;
|
||||
|
||||
const FillColorMap = { ...Medium, Black, White } as const;
|
||||
const FillColorShortMap = { ...Medium, Black, White } as const;
|
||||
|
||||
const FillColorPalettes: Palette[] = [...buildPalettes(FillColorMap)] 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;
|
||||
|
||||
export const DefaultTheme: Theme = {
|
||||
pureBlack,
|
||||
@@ -89,18 +118,19 @@ export const DefaultTheme: Theme = {
|
||||
white: White,
|
||||
transparent: Transparent,
|
||||
textColor: Medium.Blue,
|
||||
// Custom button should be selected by default,
|
||||
// add transparent `ff` to distinguish `#000000`.
|
||||
shapeTextColor: '#000000ff',
|
||||
shapeTextColor: pureBlack,
|
||||
shapeStrokeColor: Medium.Yellow,
|
||||
shapeFillColor: Medium.Yellow,
|
||||
connectorColor: Medium.Grey,
|
||||
noteBackgrounColor: NoteBackgroundColorMap.White,
|
||||
Palettes,
|
||||
StrokeColorMap,
|
||||
StrokeColorPalettes,
|
||||
FillColorMap,
|
||||
FillColorPalettes,
|
||||
ShapeTextColorPalettes,
|
||||
NoteBackgroundColorMap,
|
||||
NoteBackgroundColorPalettes,
|
||||
StrokeColorShortMap,
|
||||
StrokeColorShortPalettes,
|
||||
FillColorShortMap,
|
||||
FillColorShortPalettes,
|
||||
ShapeTextColorShortMap,
|
||||
ShapeTextColorShortPalettes,
|
||||
} as const;
|
||||
|
||||
@@ -21,16 +21,20 @@ export const ThemeSchema = z.object({
|
||||
shapeFillColor: ColorSchema,
|
||||
connectorColor: ColorSchema,
|
||||
noteBackgrounColor: ColorSchema,
|
||||
// Universal color palette
|
||||
|
||||
// Universal color palettes
|
||||
Palettes: 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),
|
||||
ShapeTextColorPalettes: 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>;
|
||||
|
||||
@@ -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.getTransformer();
|
||||
const job = this.std.store.getTransformer();
|
||||
const snapshot = job.sliceToSnapshot(slice);
|
||||
if (!snapshot) {
|
||||
console.error('Failed to convert slice to snapshot');
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
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, ViewportLayout } from './types.js';
|
||||
import type {
|
||||
ParagraphLayout,
|
||||
RenderingState,
|
||||
ViewportLayout,
|
||||
} from './types.js';
|
||||
import type { ViewportTurboRendererExtension } from './viewport-renderer.js';
|
||||
|
||||
export function syncCanvasSize(canvas: HTMLCanvasElement, host: HTMLElement) {
|
||||
@@ -19,7 +24,7 @@ export function syncCanvasSize(canvas: HTMLCanvasElement, host: HTMLElement) {
|
||||
}
|
||||
|
||||
export function getViewportLayout(
|
||||
host: HTMLElement,
|
||||
host: EditorHost,
|
||||
viewport: Viewport
|
||||
): ViewportLayout {
|
||||
const paragraphBlocks = host.querySelectorAll(
|
||||
@@ -98,15 +103,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;'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ type WorkerMessagePaint = {
|
||||
height: number;
|
||||
dpr: number;
|
||||
zoom: number;
|
||||
version: number;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -63,7 +64,7 @@ class LayoutPainter {
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
paint(layout: ViewportLayout) {
|
||||
paint(layout: ViewportLayout, version: number) {
|
||||
const { canvas, ctx } = this;
|
||||
if (!canvas || !ctx) return;
|
||||
if (layout.rect.w === 0 || layout.rect.h === 0) {
|
||||
@@ -103,7 +104,10 @@ class LayoutPainter {
|
||||
});
|
||||
|
||||
const bitmap = canvas.transferToImageBitmap();
|
||||
self.postMessage({ type: 'bitmapPainted', bitmap }, { transfer: [bitmap] });
|
||||
self.postMessage(
|
||||
{ type: 'bitmapPainted', bitmap, version },
|
||||
{ transfer: [bitmap] }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +131,9 @@ self.onmessage = async (e: MessageEvent<WorkerMessage>) => {
|
||||
|
||||
switch (type) {
|
||||
case 'paintLayout': {
|
||||
const { layout, width, height, dpr, zoom } = data;
|
||||
const { layout, width, height, dpr, zoom, version } = data;
|
||||
painter.setSize(width, height, dpr, zoom);
|
||||
painter.paint(layout);
|
||||
painter.paint(layout, version);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,3 +32,12 @@ 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';
|
||||
|
||||
@@ -4,17 +4,20 @@ import {
|
||||
LifeCycleWatcherIdentifier,
|
||||
StdIdentifier,
|
||||
} from '@blocksuite/block-std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import { type Container, type ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import {
|
||||
GfxControllerIdentifier,
|
||||
type GfxViewportElement,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type { Container, 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 ViewportLayout } from './types.js';
|
||||
import type { RenderingState, ViewportLayout } from './types.js';
|
||||
|
||||
export const ViewportTurboRendererIdentifier = LifeCycleWatcherIdentifier(
|
||||
'ViewportTurboRenderer'
|
||||
@@ -25,12 +28,14 @@ interface Tile {
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
// With high enough zoom, fallback to DOM rendering
|
||||
const zoomThreshold = 1;
|
||||
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
|
||||
|
||||
export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
state: 'monitoring' | 'paused' = 'paused';
|
||||
state: RenderingState = 'inactive';
|
||||
disposables = new DisposableGroup();
|
||||
private layoutVersion = 0;
|
||||
|
||||
static override setup(di: Container) {
|
||||
di.addImpl(ViewportTurboRendererIdentifier, this, [StdIdentifier]);
|
||||
@@ -38,15 +43,16 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
|
||||
public readonly canvas: HTMLCanvasElement = document.createElement('canvas');
|
||||
private readonly worker: Worker;
|
||||
private layoutCache: ViewportLayout | null = null;
|
||||
private layoutCacheData: ViewportLayout | null = null;
|
||||
private tile: Tile | null = null;
|
||||
private debugPane: Pane | null = null;
|
||||
private viewportElement: GfxViewportElement | 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() {
|
||||
@@ -56,9 +62,14 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
initTweakpane(this, mountPoint as HTMLElement);
|
||||
}
|
||||
|
||||
this.viewport.elementReady.once(() => {
|
||||
this.viewport.elementReady.once(element => {
|
||||
this.viewportElement = element;
|
||||
syncCanvasSize(this.canvas, this.std.host);
|
||||
this.state = 'monitoring';
|
||||
this.setState('pending');
|
||||
|
||||
this.disposables.add(
|
||||
this.viewport.sizeUpdated.on(() => this.handleResize())
|
||||
);
|
||||
this.disposables.add(
|
||||
this.viewport.viewportUpdated.on(() => {
|
||||
this.refresh().catch(console.error);
|
||||
@@ -66,70 +77,91 @@ 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();
|
||||
debouncedRefresh();
|
||||
this.debouncedRefresh();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
this.debugLog('Unmounting renderer');
|
||||
this.clearTile();
|
||||
if (this.debugPane) {
|
||||
this.debugPane.dispose();
|
||||
this.debugPane = null;
|
||||
}
|
||||
this.clearOptimizedBlocks();
|
||||
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 === 'paused') return;
|
||||
if (this.state === 'inactive') return;
|
||||
|
||||
this.clearCanvas();
|
||||
// -> pending
|
||||
if (this.viewport.zoom > zoomThreshold) {
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
debouncedRefresh = debounce(
|
||||
() => {
|
||||
this.refresh().catch(console.error);
|
||||
},
|
||||
debounceTime,
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
|
||||
invalidate() {
|
||||
this.layoutCache = null;
|
||||
this.layoutVersion++;
|
||||
this.layoutCacheData = null;
|
||||
this.clearTile();
|
||||
this.clearCanvas(); // Should clear immediately after content updates
|
||||
this.clearCanvas();
|
||||
this.clearOptimizedBlocks();
|
||||
this.setState('pending');
|
||||
this.debugLog(`Invalidated renderer (layoutVersion=${this.layoutVersion})`);
|
||||
}
|
||||
|
||||
private updateLayoutCache() {
|
||||
const layout = getViewportLayout(this.std.host, this.viewport);
|
||||
this.layoutCache = layout;
|
||||
private debugLog(message: string) {
|
||||
if (!debug) return;
|
||||
debugLog(message, this.state);
|
||||
}
|
||||
|
||||
private clearTile() {
|
||||
if (this.tile) {
|
||||
this.tile.bitmap.close();
|
||||
this.tile = null;
|
||||
}
|
||||
if (!this.tile) return;
|
||||
this.tile.bitmap.close();
|
||||
this.tile = null;
|
||||
this.debugLog('Tile cleared');
|
||||
}
|
||||
|
||||
private async paintLayout(layout: ViewportLayout): Promise<void> {
|
||||
@@ -137,6 +169,9 @@ 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: {
|
||||
@@ -145,25 +180,37 @@ 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') {
|
||||
this.handlePaintedBitmap(e.data.bitmap, resolve);
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private handlePaintedBitmap(bitmap: ImageBitmap, resolve: () => void) {
|
||||
if (this.tile) {
|
||||
this.tile.bitmap.close();
|
||||
}
|
||||
this.clearTile();
|
||||
this.tile = {
|
||||
bitmap,
|
||||
zoom: this.viewport.zoom,
|
||||
};
|
||||
this.setState('ready');
|
||||
resolve();
|
||||
}
|
||||
|
||||
@@ -177,10 +224,17 @@ 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) {
|
||||
const bitmap = this.tile!.bitmap;
|
||||
if (!this.tile) {
|
||||
this.debugLog('No cached bitmap available, requesting refresh');
|
||||
this.debouncedRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const bitmap = this.tile.bitmap;
|
||||
const ctx = this.canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
@@ -197,5 +251,61 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +145,8 @@ 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);
|
||||
|
||||
|
||||
@@ -1354,7 +1354,7 @@ export class DragEventWatcher {
|
||||
middlewares.push(gfxBlocksFilter(selectedIds, std));
|
||||
}
|
||||
|
||||
return std.getTransformer(middlewares);
|
||||
return std.store.getTransformer(middlewares);
|
||||
}
|
||||
|
||||
private _isDropOnCurrentEditor(std?: BlockStdScope) {
|
||||
@@ -1625,10 +1625,13 @@ export class DragEventWatcher {
|
||||
|
||||
disposables.add(
|
||||
std.view.viewUpdated.on(payload => {
|
||||
if (payload.type === 'add') {
|
||||
if (payload.type !== 'block') {
|
||||
return;
|
||||
}
|
||||
if (payload.method === 'add') {
|
||||
this._makeDropTarget(payload.view);
|
||||
} else if (
|
||||
payload.type === 'delete' &&
|
||||
payload.method === 'delete' &&
|
||||
this.dropTargetCleanUps.has(payload.id)
|
||||
) {
|
||||
this.dropTargetCleanUps.get(payload.id)!.forEach(clean => clean());
|
||||
|
||||
@@ -250,6 +250,7 @@ export class EdgelessRemoteSelectionWidget extends WidgetComponent<RootBlockMode
|
||||
${MultiCursorDuotoneIcon({
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
style: `fill: ${_remoteColorManager.get(id)}; stroke: ${_remoteColorManager.get(id)};`,
|
||||
})}
|
||||
<div
|
||||
class="remote-username"
|
||||
|
||||
@@ -4,8 +4,11 @@ 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 =
|
||||
|
||||
@@ -11,39 +11,37 @@ import {
|
||||
type Cell,
|
||||
type Column,
|
||||
type DatabaseBlockModel,
|
||||
DatabaseBlockSchema,
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
DatabaseBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { propertyModelPresets } from '@blocksuite/data-view/property-pure-presets';
|
||||
import type { BlockModel, Store } from '@blocksuite/store';
|
||||
import { Schema, Text } from '@blocksuite/store';
|
||||
import { Text } from '@blocksuite/store';
|
||||
import {
|
||||
createAutoIncrementIdGenerator,
|
||||
TestWorkspace,
|
||||
} from '@blocksuite/store/test';
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
const AffineSchemas = [
|
||||
RootBlockSchema,
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
DatabaseBlockSchema,
|
||||
const extensions = [
|
||||
RootBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
DatabaseBlockSchemaExtension,
|
||||
];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register(AffineSchemas);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
function createTestDoc(docId = 'doc0') {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: docId });
|
||||
const doc = collection.createDoc({ id: docId, extensions });
|
||||
doc.load();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defaultImageProxyMiddleware } from '@blocksuite/affine-block-image';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import { SpecProvider } from '@blocksuite/affine-shared/utils';
|
||||
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({ schema });
|
||||
docCollection.storeExtensions = [FeatureFlagService];
|
||||
const docCollection = new TestWorkspace();
|
||||
docCollection.storeExtensions = SpecProvider._.getSpec('store').value;
|
||||
docCollection.meta.initialize();
|
||||
return new Transformer({
|
||||
schema,
|
||||
|
||||
@@ -2,15 +2,12 @@ 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,
|
||||
DatabaseSelectionExtension,
|
||||
} from '@blocksuite/affine-block-database';
|
||||
import { DatabaseBlockSpec } 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, ImageStoreSpec } from '@blocksuite/affine-block-image';
|
||||
import { ImageBlockSpec } from '@blocksuite/affine-block-image';
|
||||
import { LatexBlockSpec } from '@blocksuite/affine-block-latex';
|
||||
import { ListBlockSpec } from '@blocksuite/affine-block-list';
|
||||
import {
|
||||
@@ -26,43 +23,19 @@ import {
|
||||
EdgelessSurfaceRefBlockSpec,
|
||||
PageSurfaceRefBlockSpec,
|
||||
} from '@blocksuite/affine-block-surface-ref';
|
||||
import {
|
||||
TableBlockSpec,
|
||||
TableSelectionExtension,
|
||||
} from '@blocksuite/affine-block-table';
|
||||
import { TableBlockSpec } 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,
|
||||
@@ -82,7 +55,6 @@ export const CommonBlockSpecs: ExtensionType[] = [
|
||||
ParagraphBlockSpec,
|
||||
DefaultOpenDocExtension,
|
||||
FontLoaderService,
|
||||
AdapterFactoryExtensions,
|
||||
].flat();
|
||||
|
||||
export const PageFirstPartyBlockSpecs: ExtensionType[] = [
|
||||
@@ -101,24 +73,3 @@ 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();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './common.js';
|
||||
export * from './editor-specs.js';
|
||||
export * from './preview-specs.js';
|
||||
export * from './store.js';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { SpecProvider } from '@blocksuite/affine-shared/utils';
|
||||
|
||||
import { StoreExtensions } from './common.js';
|
||||
import {
|
||||
EdgelessEditorBlockSpecs,
|
||||
PageEditorBlockSpecs,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
PreviewEdgelessEditorBlockSpecs,
|
||||
PreviewPageEditorBlockSpecs,
|
||||
} from './preview-specs.js';
|
||||
import { StoreExtensions } from './store.js';
|
||||
|
||||
export function registerSpecs() {
|
||||
SpecProvider._.addSpec('store', StoreExtensions);
|
||||
|
||||
101
blocksuite/blocks/src/extensions/store.ts
Normal file
101
blocksuite/blocks/src/extensions/store.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
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();
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Schema } from '@blocksuite/store';
|
||||
import {
|
||||
createAutoIncrementIdGenerator,
|
||||
TestWorkspace,
|
||||
@@ -9,19 +8,23 @@ import { effects } from '../effects.js';
|
||||
import { TestEditorContainer } from './test-editor.js';
|
||||
import {
|
||||
type HeadingBlockModel,
|
||||
HeadingBlockSchema,
|
||||
NoteBlockSchema,
|
||||
RootBlockSchema,
|
||||
HeadingBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
import { testSpecs } from './test-spec.js';
|
||||
|
||||
effects();
|
||||
|
||||
const extensions = [
|
||||
RootBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
HeadingBlockSchemaExtension,
|
||||
];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register([RootBlockSchema, NoteBlockSchema, HeadingBlockSchema]);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
function wait(time: number) {
|
||||
@@ -33,7 +36,7 @@ describe('editor host', () => {
|
||||
const collection = new TestWorkspace(createTestOptions());
|
||||
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
const rootId = doc.addBlock('test:page');
|
||||
const noteId = doc.addBlock('test:note', {}, rootId);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
|
||||
import {
|
||||
BlockModel,
|
||||
BlockSchemaExtension,
|
||||
defineBlockSchema,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export const RootBlockSchema = defineBlockSchema({
|
||||
flavour: 'test:page',
|
||||
@@ -15,6 +19,8 @@ export const RootBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
|
||||
|
||||
export class RootBlockModel extends BlockModel<
|
||||
ReturnType<(typeof RootBlockSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -30,6 +36,8 @@ export const NoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
|
||||
|
||||
export class NoteBlockModel extends BlockModel<
|
||||
ReturnType<(typeof NoteBlockSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -47,6 +55,9 @@ export const HeadingBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const HeadingBlockSchemaExtension =
|
||||
BlockSchemaExtension(HeadingBlockSchema);
|
||||
|
||||
export class HeadingBlockModel extends BlockModel<
|
||||
ReturnType<(typeof HeadingBlockSchema)['model']['props']>
|
||||
> {}
|
||||
|
||||
@@ -285,7 +285,7 @@ export class Clipboard extends LifeCycleWatcher {
|
||||
}
|
||||
|
||||
private _getJob() {
|
||||
return this.std.getTransformer(this._jobMiddlewares);
|
||||
return this.std.store.getTransformer(this._jobMiddlewares);
|
||||
}
|
||||
|
||||
readFromClipboard(clipboardData: DataTransfer) {
|
||||
|
||||
@@ -5,5 +5,4 @@ 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';
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
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)]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import { assertType, type Constructor, Slot } from '@blocksuite/global/utils';
|
||||
import {
|
||||
assertType,
|
||||
type Constructor,
|
||||
DisposableGroup,
|
||||
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 {
|
||||
@@ -98,6 +104,8 @@ 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));
|
||||
@@ -113,7 +121,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
|
||||
}
|
||||
|
||||
override isEmpty(): boolean {
|
||||
return this._elementModels.size === 0 && this.children.length === 0;
|
||||
return this._isEmpty$.value;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@@ -370,6 +378,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
|
||||
if (isGfxGroupCompatibleModel(payload.model)) {
|
||||
this._groupLikeModels.set(payload.id, payload.model);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'delete':
|
||||
if (isGfxGroupCompatibleModel(payload.model)) {
|
||||
@@ -382,6 +391,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
|
||||
group.removeChild(payload.model as GfxModel);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -445,6 +455,25 @@ 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,
|
||||
@@ -460,6 +489,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
|
||||
protected _init() {
|
||||
this._initElementModels();
|
||||
this._watchGroupRelationChange();
|
||||
this._watchChildrenChange();
|
||||
}
|
||||
|
||||
getConstructor(type: string) {
|
||||
|
||||
@@ -57,27 +57,30 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
`;
|
||||
|
||||
optimizedBlocks = new Set<string>();
|
||||
|
||||
private readonly _hideOutsideBlock = () => {
|
||||
if (this.getModelsInViewport && this.host) {
|
||||
const host = this.host;
|
||||
const modelsInViewport = this.getModelsInViewport();
|
||||
if (!this.host) return;
|
||||
|
||||
modelsInViewport.forEach(model => {
|
||||
const view = host.std.view.getBlock(model.id);
|
||||
setDisplay(view, 'block');
|
||||
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);
|
||||
|
||||
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>;
|
||||
@@ -154,7 +157,8 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) {
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor getModelsInViewport: undefined | (() => Set<GfxBlockElementModel>);
|
||||
accessor getModelsInViewport: () => Set<GfxBlockElementModel> = () =>
|
||||
new Set();
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor host: undefined | EditorHost;
|
||||
@@ -167,4 +171,29 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
type ExtensionType,
|
||||
type Store,
|
||||
StoreSelectionExtension,
|
||||
Transformer,
|
||||
type TransformerMiddleware,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
import { Clipboard } from '../clipboard/index.js';
|
||||
@@ -140,19 +138,6 @@ 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();
|
||||
|
||||
@@ -3,16 +3,20 @@ import { Slot } from '@blocksuite/global/utils';
|
||||
import { LifeCycleWatcher } from '../extension/index.js';
|
||||
import type { BlockComponent, WidgetComponent } from './element/index.js';
|
||||
|
||||
type ViewUpdatePayload =
|
||||
type ViewUpdateMethod = 'delete' | 'add';
|
||||
|
||||
export type ViewUpdatePayload =
|
||||
| {
|
||||
id: string;
|
||||
type: 'delete';
|
||||
method: ViewUpdateMethod;
|
||||
type: 'block';
|
||||
view: BlockComponent;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
type: 'add';
|
||||
view: BlockComponent;
|
||||
method: ViewUpdateMethod;
|
||||
type: 'widget';
|
||||
view: WidgetComponent;
|
||||
};
|
||||
|
||||
export class ViewStore extends LifeCycleWatcher {
|
||||
@@ -42,7 +46,8 @@ export class ViewStore extends LifeCycleWatcher {
|
||||
this._blockMap.delete(node.model.id);
|
||||
this.viewUpdated.emit({
|
||||
id: node.model.id,
|
||||
type: 'delete',
|
||||
method: 'delete',
|
||||
type: 'block',
|
||||
view: node,
|
||||
});
|
||||
};
|
||||
@@ -51,6 +56,12 @@ 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 => {
|
||||
@@ -72,7 +83,8 @@ export class ViewStore extends LifeCycleWatcher {
|
||||
this._blockMap.set(node.model.id, node);
|
||||
this.viewUpdated.emit({
|
||||
id: node.model.id,
|
||||
type: 'add',
|
||||
method: 'add',
|
||||
type: 'block',
|
||||
view: node,
|
||||
});
|
||||
};
|
||||
@@ -81,6 +93,12 @@ 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 = (
|
||||
|
||||
@@ -2,6 +2,7 @@ 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,
|
||||
@@ -9,7 +10,6 @@ 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,6 +27,7 @@ const pageSchema = defineBlockSchema({
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
const pageSchemaExtension = BlockSchemaExtension(pageSchema);
|
||||
|
||||
const tableSchema = defineBlockSchema({
|
||||
flavour: 'table',
|
||||
@@ -39,6 +40,7 @@ const tableSchema = defineBlockSchema({
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
const tableSchemaExtension = BlockSchemaExtension(tableSchema);
|
||||
|
||||
const flatTableSchema = defineBlockSchema({
|
||||
flavour: 'flat-table',
|
||||
@@ -54,6 +56,8 @@ const flatTableSchema = defineBlockSchema({
|
||||
isFlatData: true,
|
||||
},
|
||||
});
|
||||
const flatTableSchemaExtension = BlockSchemaExtension(flatTableSchema);
|
||||
|
||||
class RootModel extends BlockModel<
|
||||
ReturnType<(typeof pageSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -66,9 +70,7 @@ class FlatTableModel extends BlockModel<
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register([pageSchema, tableSchema, flatTableSchema]);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const defaultDocId = 'doc:home';
|
||||
@@ -76,7 +78,14 @@ function createTestDoc(docId = defaultDocId) {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: docId });
|
||||
const doc = collection.createDoc({
|
||||
id: docId,
|
||||
extensions: [
|
||||
pageSchemaExtension,
|
||||
tableSchemaExtension,
|
||||
flatTableSchemaExtension,
|
||||
],
|
||||
});
|
||||
doc.load();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -4,29 +4,20 @@ 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, BlockSchemaType, DocMeta, Store } from '../index.js';
|
||||
import { Schema } from '../index.js';
|
||||
import type { BlockModel, DocMeta, Store } from '../index.js';
|
||||
import { Text } from '../reactive/text.js';
|
||||
import { createAutoIncrementIdGenerator } from '../test/index.js';
|
||||
import { TestWorkspace } from '../test/test-workspace.js';
|
||||
import {
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
import { assertExists } from './test-utils-dom.js';
|
||||
|
||||
export const BlockSchemas = [
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
NoteBlockSchema,
|
||||
] as BlockSchemaType[];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register(BlockSchemas);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const defaultDocId = 'doc:home';
|
||||
@@ -58,11 +49,20 @@ 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 });
|
||||
const doc = collection.createDoc({
|
||||
id: docId,
|
||||
extensions,
|
||||
});
|
||||
doc.load();
|
||||
return doc;
|
||||
}
|
||||
@@ -113,13 +113,6 @@ describe('basic', () => {
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
workspaceVersion: 2,
|
||||
pageVersion: 2,
|
||||
blockVersions: {
|
||||
'affine:note': 1,
|
||||
'affine:page': 2,
|
||||
'affine:paragraph': 1,
|
||||
},
|
||||
},
|
||||
spaces: {
|
||||
[spaceId]: {
|
||||
@@ -155,6 +148,7 @@ describe('basic', () => {
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({
|
||||
id: 'space:0',
|
||||
extensions,
|
||||
});
|
||||
|
||||
const readyCallback = vi.fn();
|
||||
@@ -181,6 +175,7 @@ describe('basic', () => {
|
||||
const collection2 = new TestWorkspace(options);
|
||||
const doc = collection.createDoc({
|
||||
id: 'space:0',
|
||||
extensions,
|
||||
});
|
||||
doc.load(() => {
|
||||
doc.addBlock('affine:page', {
|
||||
@@ -209,7 +204,9 @@ describe('basic', () => {
|
||||
// apply doc update
|
||||
const update = encodeStateAsUpdate(doc.spaceDoc);
|
||||
expect(collection2.docs.size).toBe(1);
|
||||
const doc2 = collection2.getDoc('space:0');
|
||||
const doc2 = collection2.getDoc('space:0', {
|
||||
extensions,
|
||||
});
|
||||
assertExists(doc2);
|
||||
applyUpdate(doc2.spaceDoc, update);
|
||||
expect(serializCollection(collection2.doc)['spaces']).toEqual({
|
||||
|
||||
@@ -2,31 +2,28 @@ 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 {
|
||||
DividerBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
DividerBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
type RootBlockModel,
|
||||
RootBlockSchema,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
|
||||
const BlockSchemas = [
|
||||
RootBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
DividerBlockSchema,
|
||||
const extensions = [
|
||||
RootBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
DividerBlockSchemaExtension,
|
||||
];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register(BlockSchemas);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
test('trigger props updated', () => {
|
||||
@@ -34,7 +31,7 @@ test('trigger props updated', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
|
||||
doc.addBlock('affine:page');
|
||||
@@ -94,7 +91,7 @@ test('stash and pop', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
|
||||
doc.addBlock('affine:page');
|
||||
@@ -164,7 +161,7 @@ test('always get latest value in onChange', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
|
||||
doc.addBlock('affine:page');
|
||||
@@ -210,11 +207,12 @@ test('query', () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc1 = collection.createDoc({ id: 'home' });
|
||||
const doc1 = collection.createDoc({ id: 'home', extensions });
|
||||
doc1.load();
|
||||
const doc2 = collection.getDoc('home');
|
||||
const doc2 = collection.getDoc('home', { extensions });
|
||||
|
||||
const doc3 = collection.getDoc('home', {
|
||||
extensions,
|
||||
query: {
|
||||
mode: 'loose',
|
||||
match: [
|
||||
@@ -247,10 +245,10 @@ test('local readonly', () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc1 = collection.createDoc({ id: 'home' });
|
||||
const doc1 = collection.createDoc({ id: 'home', extensions });
|
||||
doc1.load();
|
||||
const doc2 = collection.getDoc('home', { readonly: true });
|
||||
const doc3 = collection.getDoc('home', { readonly: false });
|
||||
const doc2 = collection.getDoc('home', { readonly: true, extensions });
|
||||
const doc3 = collection.getDoc('home', { readonly: false, extensions });
|
||||
|
||||
expect(doc1.readonly).toBeFalsy();
|
||||
expect(doc2?.readonly).toBeTruthy();
|
||||
@@ -276,7 +274,7 @@ describe('move blocks', () => {
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
const pageId = doc.addBlock('affine:page');
|
||||
const page = doc.getBlock(pageId)!.model;
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
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 {
|
||||
DividerBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
RootBlockSchema,
|
||||
DividerBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
RootBlockSchemaExtension,
|
||||
} from './test-schema.js';
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register(BlockSchemas);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const TestCustomNoteBlockSchema = defineBlockSchema({
|
||||
@@ -35,6 +33,10 @@ const TestCustomNoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
const TestCustomNoteBlockSchemaExtension = BlockSchemaExtension(
|
||||
TestCustomNoteBlockSchema
|
||||
);
|
||||
|
||||
const TestInvalidNoteBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:note-invalid-block-video',
|
||||
props: internal => ({
|
||||
@@ -48,14 +50,18 @@ const TestInvalidNoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
const BlockSchemas = [
|
||||
RootBlockSchema,
|
||||
ParagraphBlockSchema,
|
||||
ListBlockSchema,
|
||||
NoteBlockSchema,
|
||||
DividerBlockSchema,
|
||||
TestCustomNoteBlockSchema,
|
||||
TestInvalidNoteBlockSchema,
|
||||
const TestInvalidNoteBlockSchemaExtension = BlockSchemaExtension(
|
||||
TestInvalidNoteBlockSchema
|
||||
);
|
||||
|
||||
const extensions = [
|
||||
RootBlockSchemaExtension,
|
||||
ParagraphBlockSchemaExtension,
|
||||
ListBlockSchemaExtension,
|
||||
NoteBlockSchemaExtension,
|
||||
DividerBlockSchemaExtension,
|
||||
TestCustomNoteBlockSchemaExtension,
|
||||
TestInvalidNoteBlockSchemaExtension,
|
||||
];
|
||||
|
||||
const defaultDocId = 'doc0';
|
||||
@@ -63,7 +69,7 @@ function createTestDoc(docId = defaultDocId) {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: docId });
|
||||
const doc = collection.createDoc({ id: docId, extensions });
|
||||
doc.load();
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BlockSchemaExtension } from '../extension/schema.js';
|
||||
import { BlockModel, defineBlockSchema } from '../model/index.js';
|
||||
|
||||
export const RootBlockSchema = defineBlockSchema({
|
||||
@@ -14,6 +15,8 @@ export const RootBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const RootBlockSchemaExtension = BlockSchemaExtension(RootBlockSchema);
|
||||
|
||||
export class RootBlockModel extends BlockModel<
|
||||
ReturnType<(typeof RootBlockSchema)['model']['props']>
|
||||
> {}
|
||||
@@ -42,6 +45,8 @@ export const NoteBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const NoteBlockSchemaExtension = BlockSchemaExtension(NoteBlockSchema);
|
||||
|
||||
export const ParagraphBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:paragraph',
|
||||
props: internal => ({
|
||||
@@ -60,6 +65,9 @@ export const ParagraphBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const ParagraphBlockSchemaExtension =
|
||||
BlockSchemaExtension(ParagraphBlockSchema);
|
||||
|
||||
export const ListBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:list',
|
||||
props: internal => ({
|
||||
@@ -80,6 +88,8 @@ export const ListBlockSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
export const ListBlockSchemaExtension = BlockSchemaExtension(ListBlockSchema);
|
||||
|
||||
export const DividerBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:divider',
|
||||
metadata: {
|
||||
@@ -88,3 +98,6 @@ export const DividerBlockSchema = defineBlockSchema({
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
|
||||
export const DividerBlockSchemaExtension =
|
||||
BlockSchemaExtension(DividerBlockSchema);
|
||||
|
||||
@@ -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,15 +39,16 @@ const docSchema = defineBlockSchema({
|
||||
},
|
||||
});
|
||||
|
||||
const docSchemaExtension = BlockSchemaExtension(docSchema);
|
||||
class RootBlockModel extends BlockModel<
|
||||
ReturnType<(typeof docSchema)['model']['props']>
|
||||
> {}
|
||||
|
||||
const extensions = [docSchemaExtension];
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register([docSchema]);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
return { id: 'test-collection', idGenerator };
|
||||
}
|
||||
|
||||
const transformer = new BaseBlockTransformer(new Map());
|
||||
@@ -58,7 +59,7 @@ test('model to snapshot', () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
doc.addBlock('page');
|
||||
const rootModel = doc.root as RootBlockModel;
|
||||
@@ -75,7 +76,7 @@ test('snapshot to model', async () => {
|
||||
const options = createTestOptions();
|
||||
const collection = new TestWorkspace(options);
|
||||
collection.meta.initialize();
|
||||
const doc = collection.createDoc({ id: 'home' });
|
||||
const doc = collection.createDoc({ id: 'home', extensions });
|
||||
doc.load();
|
||||
doc.addBlock('page');
|
||||
const rootModel = doc.root as RootBlockModel;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './extension';
|
||||
export * from './schema';
|
||||
export * from './selection';
|
||||
export * from './store-extension';
|
||||
|
||||
20
blocksuite/framework/store/src/extension/schema.ts
Normal file
20
blocksuite/framework/store/src/extension/schema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -15,10 +15,14 @@ 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]: origin[key as keyof Model],
|
||||
[key]: value,
|
||||
};
|
||||
}, {} as ModelProps<Model>);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
@@ -18,7 +17,6 @@ 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;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Container, type ServiceProvider } from '@blocksuite/global/di';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { type Disposable, Slot } from '@blocksuite/global/utils';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
|
||||
import type { ExtensionType } from '../../extension/extension.js';
|
||||
import { StoreSelectionExtension } from '../../extension/index.js';
|
||||
import type { Schema } from '../../schema/index.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 {
|
||||
Block,
|
||||
type BlockModel,
|
||||
@@ -20,7 +25,6 @@ import { type Query, runQuery } from './query.js';
|
||||
import { syncBlockProps } from './utils.js';
|
||||
|
||||
export type StoreOptions = {
|
||||
schema: Schema;
|
||||
doc: Doc;
|
||||
id?: string;
|
||||
readonly?: boolean;
|
||||
@@ -55,6 +59,10 @@ 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'] & {
|
||||
@@ -215,7 +223,11 @@ export class Store {
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return this.root?.isEmpty() ?? true;
|
||||
return this._isEmpty.peek();
|
||||
}
|
||||
|
||||
get isEmpty$() {
|
||||
return this._isEmpty;
|
||||
}
|
||||
|
||||
get loaded() {
|
||||
@@ -290,14 +302,7 @@ export class Store {
|
||||
return this._doc.withoutTransact.bind(this._doc);
|
||||
}
|
||||
|
||||
constructor({
|
||||
schema,
|
||||
doc,
|
||||
readonly,
|
||||
query,
|
||||
provider,
|
||||
extensions,
|
||||
}: StoreOptions) {
|
||||
constructor({ doc, readonly, query, provider, extensions }: StoreOptions) {
|
||||
const container = new Container();
|
||||
container.addImpl(StoreIdentifier, () => this);
|
||||
|
||||
@@ -323,8 +328,11 @@ export class Store {
|
||||
yBlockUpdated: this._doc.slots.yBlockUpdated,
|
||||
};
|
||||
|
||||
this._crud = new DocCRUD(this._yBlocks, doc.schema);
|
||||
this._schema = schema;
|
||||
this._schema = new Schema();
|
||||
this._provider.getAll(BlockSchemaIdentifier).forEach(schema => {
|
||||
this._schema.register([schema]);
|
||||
});
|
||||
this._crud = new DocCRUD(this._yBlocks, this._schema);
|
||||
if (readonly !== undefined) {
|
||||
this._readonly.value = readonly;
|
||||
}
|
||||
@@ -709,4 +717,17 @@ 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { Slot } from '@blocksuite/global/utils';
|
||||
|
||||
import type { Workspace } from './workspace.js';
|
||||
|
||||
export type Tag = {
|
||||
id: string;
|
||||
value: string;
|
||||
@@ -38,8 +36,6 @@ 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
Reference in New Issue
Block a user