mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-13 04:42:56 +08:00
chore(editor): reorg packages (#10702)
This commit is contained in:
@@ -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) {}
|
||||
}
|
||||
Reference in New Issue
Block a user