chore(editor): reorg packages (#10702)

This commit is contained in:
Saul-Mirone
2025-03-08 03:57:04 +00:00
parent 334912e85b
commit 8aedef0a36
961 changed files with 837 additions and 927 deletions
@@ -0,0 +1,227 @@
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { RootBlockModel } from '@blocksuite/affine-model';
import {
DocModeExtension,
DocModeProvider,
EditorSettingExtension,
EditorSettingProvider,
} from '@blocksuite/affine-shared/services';
import { matchModels, SpecProvider } from '@blocksuite/affine-shared/utils';
import {
type BlockComponent,
BlockStdScope,
BlockViewIdentifier,
LifeCycleWatcher,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import type {
BlockModel,
BlockViewType,
ExtensionType,
Query,
SliceSnapshot,
} from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import { literal } from 'lit/static-html.js';
import type { AffineDragHandleWidget } from '../drag-handle.js';
import { getSnapshotRect } from '../utils.js';
export class PreviewHelper {
private readonly _calculateQuery = (
selectedIds: string[],
mode: 'block' | 'gfx'
): Query => {
const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map(
id => ({
id,
viewType: 'display',
})
);
// The ancestors of the selected blocks should be rendered as Bypass
selectedIds.forEach(block => {
let parent: string | null = block;
do {
if (!selectedIds.includes(parent)) {
ids.push({ viewType: 'bypass', id: parent });
}
parent = this.widget.doc.getParent(parent)?.id ?? null;
} while (parent && !ids.map(({ id }) => id).includes(parent));
});
// The children of the selected blocks should be rendered as Display
const addChildren = (id: string) => {
const model = this.widget.doc.getBlock(id)?.model;
if (!model) {
return;
}
const children = model.children ?? [];
if (
mode === 'gfx' &&
matchModels(model, [RootBlockModel, SurfaceBlockModel])
) {
children.forEach(child => {
if (selectedIds.includes(child.id)) {
ids.push({ viewType: 'display', id: child.id });
addChildren(child.id);
}
});
} else {
children.forEach(child => {
ids.push({ viewType: 'display', id: child.id });
addChildren(child.id);
});
}
};
selectedIds.forEach(addChildren);
return {
match: ids,
mode: 'strict',
};
};
getPreviewStd = (
blockIds: string[],
snapshot: SliceSnapshot,
mode: 'block' | 'gfx'
) => {
const widget = this.widget;
const std = widget.std;
const sourceGfx = std.get(GfxControllerIdentifier);
const isEdgeless = mode === 'gfx';
blockIds = blockIds.slice();
if (isEdgeless) {
blockIds.push(sourceGfx.surface!.id, std.store.root!.id);
}
const docModeService = std.get(DocModeProvider);
const editorSetting = std.get(EditorSettingProvider).peek();
const query = this._calculateQuery(blockIds as string[], mode);
const store = widget.doc.doc.getStore({ query });
const previewSpec = SpecProvider._.getSpec(
isEdgeless ? 'preview:edgeless' : 'preview:page'
);
const settingSignal = signal({ ...editorSetting });
const extensions = [
DocModeExtension(docModeService),
EditorSettingExtension(settingSignal),
{
setup(di) {
di.override(
BlockViewIdentifier('affine:database'),
() => literal`affine-dnd-preview-database`
);
},
} as ExtensionType,
{
setup(di) {
di.override(BlockViewIdentifier('affine:image'), () => {
return (model: BlockModel) => {
const parent = model.doc.getParent(model.id);
if (parent?.flavour === 'affine:surface') {
return literal`affine-edgeless-placeholder-preview-image`;
}
return literal`affine-placeholder-preview-image`;
};
});
},
} as ExtensionType,
];
if (isEdgeless) {
class PreviewViewportInitializer extends LifeCycleWatcher {
static override key = 'preview-viewport-initializer';
override mounted(): void {
const rect = getSnapshotRect(snapshot);
if (!rect) {
return;
}
this.std.view.viewUpdated.on(payload => {
if (payload.type !== 'block') return;
if (payload.view.model.flavour === 'affine:page') {
const gfx = this.std.get(GfxControllerIdentifier);
(
payload.view as BlockComponent & { overrideBackground: string }
).overrideBackground = 'transparent';
gfx.viewport.setViewportByBound(rect);
}
});
}
}
extensions.push(PreviewViewportInitializer);
}
previewSpec.extend(extensions);
settingSignal.value = {
...settingSignal.value,
edgelessDisableScheduleUpdate: true,
};
const previewStd = new BlockStdScope({
store,
extensions: previewSpec.value,
});
let width: number = 500;
let height;
let scale = 1;
if (isEdgeless) {
const rect = getSnapshotRect(snapshot);
if (rect) {
width = rect.w;
height = rect.h;
} else {
height = 500;
}
scale = sourceGfx.viewport.zoom;
} else {
const noteBlock = this.widget.host.querySelector('affine-note');
width = noteBlock?.offsetWidth ?? noteBlock?.clientWidth ?? 500;
}
return {
scale,
previewStd,
width,
height,
};
};
renderDragPreview = (options: {
blockIds: string[];
snapshot: SliceSnapshot;
container: HTMLElement;
mode: 'block' | 'gfx';
}): void => {
const { blockIds, snapshot, container, mode } = options;
const { previewStd, width, height, scale } = this.getPreviewStd(
blockIds,
snapshot,
mode
);
const previewTemplate = previewStd.render();
container.style.transform = `scale(${scale})`;
container.style.width = `${width}px`;
if (height) {
container.style.height = `${height}px`;
}
container.append(previewTemplate);
};
constructor(readonly widget: AffineDragHandleWidget) {}
}
@@ -0,0 +1,95 @@
import { getCurrentNativeRange } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/block-std';
import { Rect } from '@blocksuite/global/gfx';
import {
DRAG_HANDLE_CONTAINER_WIDTH,
DRAG_HOVER_RECT_PADDING,
} from '../config.js';
import type { AffineDragHandleWidget } from '../drag-handle.js';
import {
containBlock,
getDragHandleLeftPadding,
includeTextSelection,
} from '../utils.js';
export class RectHelper {
private readonly _getHoveredBlocks = (): BlockComponent[] => {
if (!this.widget.isBlockDragHandleVisible || !this.widget.anchorBlockId)
return [];
const hoverBlock = this.widget.anchorBlockComponent.peek();
if (!hoverBlock) return [];
const selections = this.widget.selectionHelper.selectedBlocks;
let blocks: BlockComponent[] = [];
// When current selection is TextSelection, should cover all the blocks in native range
if (selections.length > 0 && includeTextSelection(selections)) {
const range = getCurrentNativeRange();
if (!range) return [];
const rangeManager = this.widget.std.range;
if (!rangeManager) return [];
blocks = rangeManager.getSelectedBlockComponentsByRange(range, {
match: el => el.model.role === 'content',
mode: 'highest',
});
} else {
blocks = this.widget.selectionHelper.selectedBlockComponents;
}
if (
containBlock(
blocks.map(block => block.blockId),
this.widget.anchorBlockId.peek()!
)
) {
return blocks;
}
return [hoverBlock];
};
getDraggingAreaRect = (): Rect | null => {
const block = this.widget.anchorBlockComponent.value;
if (!block) return null;
// When hover block is in selected blocks, should show hover rect on the selected blocks
// Top: the top of the first selected block
// Left: the left of the first selected block
// Right: the largest right of the selected blocks
// Bottom: the bottom of the last selected block
let { left, top, right, bottom } = block.getBoundingClientRect();
const blocks = this._getHoveredBlocks();
blocks.forEach(block => {
left = Math.min(left, block.getBoundingClientRect().left);
top = Math.min(top, block.getBoundingClientRect().top);
right = Math.max(right, block.getBoundingClientRect().right);
bottom = Math.max(bottom, block.getBoundingClientRect().bottom);
});
const offsetLeft = getDragHandleLeftPadding(blocks);
const offsetParentRect =
this.widget.dragHandleContainerOffsetParent.getBoundingClientRect();
if (!offsetParentRect) return null;
left -= offsetParentRect.left;
right -= offsetParentRect.left;
top -= offsetParentRect.top;
bottom -= offsetParentRect.top;
const scaleInNote = this.widget.scaleInNote.value;
// Add padding to hover rect
left -= (DRAG_HANDLE_CONTAINER_WIDTH + offsetLeft) * scaleInNote;
top -= DRAG_HOVER_RECT_PADDING * scaleInNote;
right += DRAG_HOVER_RECT_PADDING * scaleInNote;
bottom += DRAG_HOVER_RECT_PADDING * scaleInNote;
return new Rect(left, top, right, bottom);
};
constructor(readonly widget: AffineDragHandleWidget) {}
}
@@ -0,0 +1,67 @@
import { findNoteBlockModel } from '@blocksuite/affine-shared/utils';
import {
type BlockComponent,
BlockSelection,
SurfaceSelection,
TextSelection,
} from '@blocksuite/block-std';
import type { AffineDragHandleWidget } from '../drag-handle.js';
export class SelectionHelper {
/** Check if given block component is selected */
isBlockSelected = (block?: BlockComponent) => {
if (!block) return false;
return this.selectedBlocks.some(
selection => selection.blockId === block.model.id
);
};
setSelectedBlocks = (blocks: BlockComponent[], noteId?: string) => {
const { selection } = this;
const selections = blocks.map(block =>
selection.create(BlockSelection, {
blockId: block.blockId,
})
);
// When current page is edgeless page
// We need to remain surface selection and set editing as true
if (this.widget.mode === 'edgeless') {
const surfaceElementId = noteId
? noteId
: findNoteBlockModel(blocks[0].model)?.id;
if (!surfaceElementId) return;
const surfaceSelection = selection.create(
SurfaceSelection,
blocks[0]!.blockId,
[surfaceElementId],
true
);
selections.push(surfaceSelection);
}
selection.set(selections);
};
get selectedBlockComponents() {
return this.selectedBlocks
.map(block => this.widget.std.view.getBlock(block.blockId))
.filter((block): block is BlockComponent => !!block);
}
get selectedBlocks() {
const selection = this.selection;
return selection.find(TextSelection)
? selection.filter(TextSelection)
: selection.filter(BlockSelection);
}
get selection() {
return this.widget.std.selection;
}
constructor(readonly widget: AffineDragHandleWidget) {}
}