refactor(core): improve scroll anchoring logic (#8269)
Upstreams: https://github.com/toeverything/blocksuite/pull/8378
This commit is contained in:
@@ -180,12 +180,12 @@ export class Editor extends Entity {
|
|||||||
(a, b) => a?.id === b?.id && a?.refreshKey === b?.refreshKey
|
(a, b) => a?.id === b?.id && a?.refreshKey === b?.refreshKey
|
||||||
)
|
)
|
||||||
.subscribe(params => {
|
.subscribe(params => {
|
||||||
if (params?.id) {
|
if (!params?.id) return;
|
||||||
const std = editorContainer.host?.std;
|
|
||||||
if (std) {
|
const std = editorContainer.host?.std;
|
||||||
scrollAnchoring(std, this.mode$.value, params.id);
|
if (!std) return;
|
||||||
}
|
|
||||||
}
|
scrollAnchoring(std, this.mode$.value, params.id);
|
||||||
});
|
});
|
||||||
unsubs.push(subscription.unsubscribe.bind(subscription));
|
unsubs.push(subscription.unsubscribe.bind(subscription));
|
||||||
|
|
||||||
|
|||||||
@@ -1,115 +1,43 @@
|
|||||||
import { notify } from '@affine/component';
|
import { notify } from '@affine/component';
|
||||||
import { I18n } from '@affine/i18n';
|
import { I18n } from '@affine/i18n';
|
||||||
import type { BlockStdScope, SelectionManager } from '@blocksuite/block-std';
|
import type { BlockStdScope } from '@blocksuite/block-std';
|
||||||
import type {
|
import {
|
||||||
DocMode,
|
GfxControllerIdentifier,
|
||||||
EdgelessRootService,
|
type GfxModel,
|
||||||
PageRootService,
|
} from '@blocksuite/block-std/gfx';
|
||||||
} from '@blocksuite/blocks';
|
import type { DocMode } from '@blocksuite/blocks';
|
||||||
import { Bound, deserializeXYWH } from '@blocksuite/global/utils';
|
|
||||||
|
|
||||||
function scrollAnchoringInEdgelessMode(
|
|
||||||
service: EdgelessRootService,
|
|
||||||
id: string
|
|
||||||
) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
let bounds: Bound | null = null;
|
|
||||||
let pageSelection: SelectionManager | null = null;
|
|
||||||
|
|
||||||
const blockComponent = service.std.view.getBlock(id);
|
|
||||||
const parentComponent = blockComponent?.parentComponent;
|
|
||||||
if (parentComponent && parentComponent.flavour === 'affine:note') {
|
|
||||||
pageSelection = parentComponent.std.selection;
|
|
||||||
if (!pageSelection) return;
|
|
||||||
|
|
||||||
const { left: x, width: w } = parentComponent.getBoundingClientRect();
|
|
||||||
const { top: y, height: h } = blockComponent.getBoundingClientRect();
|
|
||||||
const coord = service.viewport.toModelCoordFromClientCoord([x, y]);
|
|
||||||
bounds = new Bound(
|
|
||||||
coord[0],
|
|
||||||
coord[1],
|
|
||||||
w / service.viewport.zoom,
|
|
||||||
h / service.viewport.zoom
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!service.getElementById) return;
|
|
||||||
const model = service.getElementById(id);
|
|
||||||
if (!model) return;
|
|
||||||
|
|
||||||
bounds = Bound.fromXYWH(deserializeXYWH(model.xywh));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bounds) {
|
|
||||||
notify.error({ title: I18n['Block not found']() });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { zoom, centerX, centerY } = service.getFitToScreenData(
|
|
||||||
[20, 20, 100, 20],
|
|
||||||
[bounds]
|
|
||||||
);
|
|
||||||
|
|
||||||
service.viewport.setCenter(centerX, centerY);
|
|
||||||
service.viewport.setZoom(zoom);
|
|
||||||
|
|
||||||
if (pageSelection) {
|
|
||||||
pageSelection.setGroup('note', [
|
|
||||||
pageSelection.create('block', {
|
|
||||||
blockId: id,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
service.selection.set({
|
|
||||||
elements: [id],
|
|
||||||
editing: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// const surfaceComponent = service.std.view.getBlock(service.surface.id);
|
|
||||||
// if (!surfaceComponent) return;
|
|
||||||
// (surfaceComponent as SurfaceBlockComponent).refresh();
|
|
||||||
|
|
||||||
// TODO(@fundon): toolbar should be hidden
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollAnchoringInPageMode(service: PageRootService, id: string) {
|
|
||||||
const blockComponent = service.std.view.getBlock(id);
|
|
||||||
if (!blockComponent) {
|
|
||||||
notify.error({ title: I18n['Block not found']() });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockComponent.scrollIntoView({
|
|
||||||
behavior: 'instant',
|
|
||||||
block: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
const selection = service.std.selection;
|
|
||||||
if (!selection) return;
|
|
||||||
|
|
||||||
selection.setGroup('note', [
|
|
||||||
selection.create('block', {
|
|
||||||
blockId: id,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// TODO(@fundon): toolbar should be hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(@fundon): it should be a command
|
// TODO(@fundon): it should be a command
|
||||||
export function scrollAnchoring(std: BlockStdScope, mode: DocMode, id: string) {
|
export function scrollAnchoring(std: BlockStdScope, mode: DocMode, id: string) {
|
||||||
if (mode === 'edgeless') {
|
let key = 'blockIds';
|
||||||
const service = std.getService<EdgelessRootService>('affine:page');
|
let exists = false;
|
||||||
if (!service) return;
|
|
||||||
|
|
||||||
scrollAnchoringInEdgelessMode(service, id);
|
if (mode === 'page') {
|
||||||
|
exists = std.doc.hasBlock(id);
|
||||||
|
} else {
|
||||||
|
const controller = std.getOptional(GfxControllerIdentifier);
|
||||||
|
if (!controller) return;
|
||||||
|
|
||||||
|
exists = !!controller.getElementById<GfxModel>(id)?.xywh;
|
||||||
|
if (controller.surface?.hasElementById(id)) {
|
||||||
|
key = 'elementIds';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
notify.error({
|
||||||
|
title: I18n['Block not found'](),
|
||||||
|
message: I18n['Block not found description'](),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = std.getService<PageRootService>('affine:page');
|
const selection = std.selection;
|
||||||
if (!service) return;
|
|
||||||
|
|
||||||
scrollAnchoringInPageMode(service, id);
|
selection.setGroup('scene', [
|
||||||
|
selection.create('highlight', {
|
||||||
|
mode,
|
||||||
|
[key]: [id],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"Back to Quick Search": "Back to quick search",
|
"Back to Quick Search": "Back to quick search",
|
||||||
"Back to all": "Back to all",
|
"Back to all": "Back to all",
|
||||||
"Block not found": "Block not found",
|
"Block not found": "Block not found",
|
||||||
|
"Block not found description": "The block you are trying to access does not exist or has been removed.",
|
||||||
"Body text": "Body text",
|
"Body text": "Body text",
|
||||||
"Bold": "Bold",
|
"Bold": "Bold",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ test('can use cmdk to search page content and scroll to it, then the block will
|
|||||||
);
|
);
|
||||||
expect(isVisitable).toBe(true);
|
expect(isVisitable).toBe(true);
|
||||||
const selectionElement = page.locator(
|
const selectionElement = page.locator(
|
||||||
'affine-block-selection[style*="display: block;"]'
|
'affine-scroll-anchoring-widget div.highlight'
|
||||||
);
|
);
|
||||||
await expect(selectionElement).toBeVisible();
|
await expect(selectionElement).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user