@formicoidea/labre-core 0.26.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/_pkgs/affine-block-database/data-source.d.ts +28 -40
  2. package/dist/_pkgs/affine-block-database/data-source.js +116 -178
  3. package/dist/_pkgs/affine-block-database/database-block.d.ts +10 -3
  4. package/dist/_pkgs/affine-block-database/database-block.js +110 -70
  5. package/dist/_pkgs/affine-block-database/external-data-source.d.ts +69 -0
  6. package/dist/_pkgs/affine-block-database/external-data-source.js +144 -0
  7. package/dist/_pkgs/affine-block-database/index.d.ts +1 -0
  8. package/dist/_pkgs/affine-block-database/index.js +1 -0
  9. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +6 -7
  10. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/components/embed-synced-doc-card.js +5 -5
  11. package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -1
  12. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/misc.d.ts +18 -0
  13. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.d.ts +18 -0
  14. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.js +104 -2
  15. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  16. package/dist/_pkgs/affine-block-root/index.js +1 -0
  17. package/dist/_pkgs/affine-block-root/keyboard/keyboard-manager.js +3 -21
  18. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.d.ts +8 -0
  19. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.js +48 -0
  20. package/dist/_pkgs/affine-block-root/view.js +6 -1
  21. package/dist/_pkgs/affine-gfx-link/effects.js +2 -2
  22. package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.d.ts +3 -0
  23. package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.js +12 -0
  24. package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.d.ts +18 -0
  25. package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.js +41 -0
  26. package/dist/_pkgs/affine-gfx-link/undo-tool.d.ts +6 -0
  27. package/dist/_pkgs/affine-gfx-link/undo-tool.js +17 -0
  28. package/dist/_pkgs/affine-gfx-link/view.js +4 -2
  29. package/dist/_pkgs/affine-gfx-mindmap/toolbar/mindmap-tool-button.js +13 -2
  30. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.d.ts +12 -2
  31. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.js +62 -11
  32. package/dist/_pkgs/affine-gfx-mindmap/view.d.ts +13 -2
  33. package/dist/_pkgs/affine-gfx-mindmap/view.js +24 -5
  34. package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
  35. package/dist/_pkgs/affine-shared/services/index.js +2 -0
  36. package/dist/_pkgs/affine-shared/services/keymap-override-service.d.ts +15 -0
  37. package/dist/_pkgs/affine-shared/services/keymap-override-service.js +26 -0
  38. package/dist/_pkgs/affine-shared/services/linked-doc-content-service.d.ts +42 -0
  39. package/dist/_pkgs/affine-shared/services/linked-doc-content-service.js +47 -0
  40. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-element-link.d.ts +29 -0
  41. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-element-link.js +233 -0
  42. package/dist/_pkgs/affine-widget-edgeless-selected-rect/effects.js +2 -0
  43. package/dist/_pkgs/affine-widget-edgeless-selected-rect/spec.d.ts +1 -0
  44. package/dist/_pkgs/affine-widget-edgeless-selected-rect/spec.js +2 -0
  45. package/dist/_pkgs/affine-widget-edgeless-selected-rect/view.js +2 -1
  46. package/dist/_pkgs/data-view/core/data-source/base.d.ts +6 -0
  47. package/dist/_pkgs/data-view/core/data-source/base.js +6 -0
  48. package/dist/_pkgs/std/extension/index.d.ts +1 -0
  49. package/dist/_pkgs/std/extension/index.js +1 -0
  50. package/dist/_pkgs/std/extension/shortcut.d.ts +69 -0
  51. package/dist/_pkgs/std/extension/shortcut.js +106 -0
  52. package/dist/_pkgs/std/gfx/model/surface/element-model.d.ts +7 -0
  53. package/dist/_pkgs/std/gfx/model/surface/element-model.js +24 -1
  54. package/dist/_pkgs/std/identifier.d.ts +7 -0
  55. package/dist/_pkgs/std/identifier.js +6 -0
  56. package/dist/_pkgs/std/inline/components/v-element.js +5 -2
  57. package/dist/extensions/view.js +5 -2
  58. package/dist/flags.d.ts +1 -1
  59. package/dist/flags.js +4 -1
  60. package/dist/shortcuts.d.ts +24 -0
  61. package/dist/shortcuts.js +39 -0
  62. package/package.json +5 -1
  63. package/dist/_pkgs/affine-gfx-link/link-tool.d.ts +0 -1
  64. package/dist/_pkgs/affine-gfx-link/link-tool.js +0 -11
  65. package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.d.ts +0 -2
  66. package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.js +0 -26
  67. package/dist/_pkgs/affine-gfx-link/toolbar/link-tool-button.d.ts +0 -10
  68. package/dist/_pkgs/affine-gfx-link/toolbar/link-tool-button.js +0 -84
@@ -0,0 +1,15 @@
1
+ import { type ShortcutConflictReporter, type ShortcutOverrides } from '../../std/index.js';
2
+ import type { ExtensionType } from '../../store/index.js';
3
+ /**
4
+ * Host-injected shortcut rebinding table. Each entry maps a shortcut id to a
5
+ * combo (array of keys, e.g. `['Ctrl', 'Shift', 'Z']`) or `'disabled'`. The
6
+ * effective keymap is `override ?? default`; `'disabled'` removes the binding.
7
+ *
8
+ * Mirrors `TelemetryExtension` / `EditorSettingExtension`.
9
+ */
10
+ export declare function KeymapOverrideExtension(overrides: ShortcutOverrides): ExtensionType;
11
+ /**
12
+ * Host-injected sink for combo conflicts detected within a scope. Without it,
13
+ * conflicts are logged to the console.
14
+ */
15
+ export declare function ShortcutConflictReporterExtension(reporter: ShortcutConflictReporter): ExtensionType;
@@ -0,0 +1,26 @@
1
+ import { ShortcutConflictReporterIdentifier, ShortcutOverrideIdentifier, } from '../../std/index.js';
2
+ /**
3
+ * Host-injected shortcut rebinding table. Each entry maps a shortcut id to a
4
+ * combo (array of keys, e.g. `['Ctrl', 'Shift', 'Z']`) or `'disabled'`. The
5
+ * effective keymap is `override ?? default`; `'disabled'` removes the binding.
6
+ *
7
+ * Mirrors `TelemetryExtension` / `EditorSettingExtension`.
8
+ */
9
+ export function KeymapOverrideExtension(overrides) {
10
+ return {
11
+ setup: di => {
12
+ di.override(ShortcutOverrideIdentifier, () => overrides);
13
+ },
14
+ };
15
+ }
16
+ /**
17
+ * Host-injected sink for combo conflicts detected within a scope. Without it,
18
+ * conflicts are logged to the console.
19
+ */
20
+ export function ShortcutConflictReporterExtension(reporter) {
21
+ return {
22
+ setup: di => {
23
+ di.override(ShortcutConflictReporterIdentifier, () => reporter);
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,42 @@
1
+ import type { BlockStdScope } from '../../std/index.js';
2
+ import type { ExtensionType } from '../../store/index.js';
3
+ /**
4
+ * Host seam for hydrating a linked doc's content on demand. An app that does not
5
+ * preload its whole corpus into the workspace (so a referenced doc has no
6
+ * content in memory) implements this to fetch/load that doc when its
7
+ * preview/embed needs it.
8
+ */
9
+ export interface LinkedDocContentResolver {
10
+ /**
11
+ * Load the referenced doc's content into the workspace. May resolve before
12
+ * the content actually arrives — the preview also watches for the doc's root
13
+ * and is bounded by {@link LinkedDocContentResolver.timeoutMs}.
14
+ */
15
+ resolve: (docId: string) => void | Promise<void>;
16
+ /** Max time the preview waits for content before degrading. Default 8000ms. */
17
+ timeoutMs?: number;
18
+ }
19
+ export declare const LinkedDocContentResolverIdentifier: import("../../global/di/types.js").ServiceIdentifier<LinkedDocContentResolver> & (<U extends LinkedDocContentResolver = LinkedDocContentResolver>(variant: import("../../global/di/types.js").ServiceVariant) => import("../../global/di/types.js").ServiceIdentifier<U>);
20
+ /** Inject a host {@link LinkedDocContentResolver}. */
21
+ export declare function LinkedDocContentResolverExtension(resolver: LinkedDocContentResolver): ExtensionType;
22
+ export declare const DEFAULT_LINKED_DOC_CONTENT_TIMEOUT_MS = 8000;
23
+ /** Minimal shape of a loadable doc store the preview waits on. */
24
+ interface AwaitableDoc {
25
+ root: object | null;
26
+ slots: {
27
+ rootAdded: {
28
+ subscribe: (cb: () => void) => {
29
+ unsubscribe: () => void;
30
+ };
31
+ };
32
+ };
33
+ }
34
+ /**
35
+ * Resolve a linked doc's content for preview, **bounded in time**. If a host
36
+ * {@link LinkedDocContentResolver} is registered it is asked to hydrate the doc
37
+ * first. Returns `true` once the doc has a root (content available), or `false`
38
+ * if it timed out — in which case the caller should render a degraded preview
39
+ * (title only) instead of spinning forever.
40
+ */
41
+ export declare function whenLinkedDocContentReady(std: BlockStdScope, doc: AwaitableDoc, docId: string): Promise<boolean>;
42
+ export {};
@@ -0,0 +1,47 @@
1
+ import { createIdentifier } from '../../global/di/index.js';
2
+ export const LinkedDocContentResolverIdentifier = createIdentifier('AffineLinkedDocContentResolver');
3
+ /** Inject a host {@link LinkedDocContentResolver}. */
4
+ export function LinkedDocContentResolverExtension(resolver) {
5
+ return {
6
+ setup: di => {
7
+ di.override(LinkedDocContentResolverIdentifier, () => resolver);
8
+ },
9
+ };
10
+ }
11
+ export const DEFAULT_LINKED_DOC_CONTENT_TIMEOUT_MS = 8000;
12
+ /**
13
+ * Resolve a linked doc's content for preview, **bounded in time**. If a host
14
+ * {@link LinkedDocContentResolver} is registered it is asked to hydrate the doc
15
+ * first. Returns `true` once the doc has a root (content available), or `false`
16
+ * if it timed out — in which case the caller should render a degraded preview
17
+ * (title only) instead of spinning forever.
18
+ */
19
+ export async function whenLinkedDocContentReady(std, doc, docId) {
20
+ if (doc.root)
21
+ return true;
22
+ const resolver = std.getOptional(LinkedDocContentResolverIdentifier);
23
+ if (resolver) {
24
+ try {
25
+ await resolver.resolve(docId);
26
+ }
27
+ catch (e) {
28
+ console.error(e);
29
+ }
30
+ if (doc.root)
31
+ return true;
32
+ }
33
+ const timeoutMs = resolver?.timeoutMs ?? DEFAULT_LINKED_DOC_CONTENT_TIMEOUT_MS;
34
+ return new Promise(resolve => {
35
+ let settled = false;
36
+ const finish = (ready) => {
37
+ if (settled)
38
+ return;
39
+ settled = true;
40
+ subscription.unsubscribe();
41
+ clearTimeout(timer);
42
+ resolve(ready);
43
+ };
44
+ const subscription = doc.slots.rootAdded.subscribe(() => finish(true));
45
+ const timer = setTimeout(() => finish(false), timeoutMs);
46
+ });
47
+ }
@@ -0,0 +1,29 @@
1
+ import type { RootBlockModel } from '../affine-model/index.js';
2
+ import { WidgetComponent } from '../std/index.js';
3
+ import { nothing } from 'lit';
4
+ export declare const EDGELESS_ELEMENT_LINK_WIDGET = "edgeless-element-link";
5
+ /**
6
+ * Shows a clickable arrow when hovering a drawing element that carries a link
7
+ * ({@link GfxPrimitiveElementModel.linkedDocId} or `.externalLink`). Clicking it
8
+ * opens the linked doc (host side-view via `docLinkClicked`) or the URL.
9
+ *
10
+ * ponytail: hover is resolved via `getElementByPoint`, which returns the topmost
11
+ * leaf — a link set on a group only shows when a child is hovered. Good enough
12
+ * for v1 (shapes/text/connectors are hit directly).
13
+ */
14
+ export declare class EdgelessElementLinkWidget extends WidgetComponent<RootBlockModel> {
15
+ static styles: import("lit").CSSResult;
16
+ private _hideTimer;
17
+ private accessor _target;
18
+ private accessor _rect;
19
+ get gfx(): import("../std/gfx/controller.js").GfxController;
20
+ private readonly _updateRect;
21
+ private _hasLink;
22
+ private _setTarget;
23
+ /** The hovered element if it carries a link, else its nearest linked group. */
24
+ private _resolveLinked;
25
+ private readonly _open;
26
+ private readonly _onKeydown;
27
+ firstUpdated(): void;
28
+ render(): import("lit-html").TemplateResult<1> | typeof nothing;
29
+ }
@@ -0,0 +1,233 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { RefNodeSlotsProvider } from '../affine-inline-reference/index.js';
36
+ import { requestThrottledConnectedFrame } from '../affine-shared/utils/index.js';
37
+ import { WidgetComponent } from '../std/index.js';
38
+ import { GfxControllerIdentifier, GfxPrimitiveElementModel, } from '../std/gfx/index.js';
39
+ import { OpenInNewIcon, RightSidebarIcon } from '@blocksuite/icons/lit';
40
+ import { css, html, nothing } from 'lit';
41
+ import { state } from 'lit/decorators.js';
42
+ import { styleMap } from 'lit/directives/style-map.js';
43
+ export const EDGELESS_ELEMENT_LINK_WIDGET = 'edgeless-element-link';
44
+ /**
45
+ * Shows a clickable arrow when hovering a drawing element that carries a link
46
+ * ({@link GfxPrimitiveElementModel.linkedDocId} or `.externalLink`). Clicking it
47
+ * opens the linked doc (host side-view via `docLinkClicked`) or the URL.
48
+ *
49
+ * ponytail: hover is resolved via `getElementByPoint`, which returns the topmost
50
+ * leaf — a link set on a group only shows when a child is hovered. Good enough
51
+ * for v1 (shapes/text/connectors are hit directly).
52
+ */
53
+ let EdgelessElementLinkWidget = (() => {
54
+ let _classSuper = WidgetComponent;
55
+ let __target_decorators;
56
+ let __target_initializers = [];
57
+ let __target_extraInitializers = [];
58
+ let __rect_decorators;
59
+ let __rect_initializers = [];
60
+ let __rect_extraInitializers = [];
61
+ return class EdgelessElementLinkWidget extends _classSuper {
62
+ constructor() {
63
+ super(...arguments);
64
+ this._hideTimer = null;
65
+ this.#_target_accessor_storage = __runInitializers(this, __target_initializers, null);
66
+ this.#_rect_accessor_storage = (__runInitializers(this, __target_extraInitializers), __runInitializers(this, __rect_initializers, null));
67
+ this._updateRect = (__runInitializers(this, __rect_extraInitializers), requestThrottledConnectedFrame(() => {
68
+ const el = this._target;
69
+ if (!el) {
70
+ this._rect = null;
71
+ return;
72
+ }
73
+ const bound = el.elementBound;
74
+ const { viewport } = this.gfx;
75
+ const [left, top] = viewport.toViewCoord(bound.x, bound.y);
76
+ this._rect = {
77
+ left,
78
+ top,
79
+ width: bound.w * viewport.zoom,
80
+ height: bound.h * viewport.zoom,
81
+ };
82
+ }, this));
83
+ this._open = (e) => {
84
+ e.stopPropagation();
85
+ const el = this._target;
86
+ if (!el)
87
+ return;
88
+ if (el.linkedDocId) {
89
+ this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.next({
90
+ pageId: el.linkedDocId,
91
+ host: this.std.host,
92
+ });
93
+ }
94
+ else if (el.externalLink) {
95
+ window.open(el.externalLink, '_blank', 'noopener,noreferrer');
96
+ }
97
+ };
98
+ this._onKeydown = (e) => {
99
+ if (e.key === 'Enter' || e.key === ' ') {
100
+ e.preventDefault();
101
+ this._open(e);
102
+ }
103
+ };
104
+ }
105
+ static {
106
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
107
+ __target_decorators = [state()];
108
+ __rect_decorators = [state()];
109
+ __esDecorate(this, null, __target_decorators, { kind: "accessor", name: "_target", static: false, private: false, access: { has: obj => "_target" in obj, get: obj => obj._target, set: (obj, value) => { obj._target = value; } }, metadata: _metadata }, __target_initializers, __target_extraInitializers);
110
+ __esDecorate(this, null, __rect_decorators, { kind: "accessor", name: "_rect", static: false, private: false, access: { has: obj => "_rect" in obj, get: obj => obj._rect, set: (obj, value) => { obj._rect = value; } }, metadata: _metadata }, __rect_initializers, __rect_extraInitializers);
111
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
112
+ }
113
+ static { this.styles = css `
114
+ :host {
115
+ position: absolute;
116
+ top: 0;
117
+ left: 0;
118
+ z-index: 1;
119
+ pointer-events: none;
120
+ }
121
+
122
+ .open-link-button {
123
+ position: absolute;
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ width: 24px;
128
+ height: 24px;
129
+ border-radius: 4px;
130
+ background: var(--affine-background-overlay-panel-color, #fff);
131
+ box-shadow: var(--affine-shadow-1);
132
+ color: var(--affine-icon-color);
133
+ cursor: pointer;
134
+ pointer-events: auto;
135
+ transform: translate(-50%, -50%);
136
+ }
137
+
138
+ .open-link-button:hover {
139
+ background: var(--affine-hover-color);
140
+ }
141
+
142
+ .open-link-button svg {
143
+ width: 16px;
144
+ height: 16px;
145
+ }
146
+ `; }
147
+ #_target_accessor_storage;
148
+ get _target() { return this.#_target_accessor_storage; }
149
+ set _target(value) { this.#_target_accessor_storage = value; }
150
+ #_rect_accessor_storage;
151
+ get _rect() { return this.#_rect_accessor_storage; }
152
+ set _rect(value) { this.#_rect_accessor_storage = value; }
153
+ get gfx() {
154
+ return this.std.get(GfxControllerIdentifier);
155
+ }
156
+ _hasLink(el) {
157
+ return (el instanceof GfxPrimitiveElementModel &&
158
+ Boolean(el.linkedDocId || el.externalLink));
159
+ }
160
+ _setTarget(el) {
161
+ if (el) {
162
+ if (this._hideTimer) {
163
+ clearTimeout(this._hideTimer);
164
+ this._hideTimer = null;
165
+ }
166
+ this._target = el;
167
+ this._updateRect();
168
+ return;
169
+ }
170
+ // Delay hiding so the pointer can travel from the element to the button.
171
+ if (this._target && !this._hideTimer) {
172
+ this._hideTimer = setTimeout(() => {
173
+ this._hideTimer = null;
174
+ this._target = null;
175
+ this._rect = null;
176
+ }, 200);
177
+ }
178
+ }
179
+ /** The hovered element if it carries a link, else its nearest linked group. */
180
+ _resolveLinked(hit) {
181
+ if (this._hasLink(hit))
182
+ return hit;
183
+ const groups = hit instanceof GfxPrimitiveElementModel ? hit.groups : [];
184
+ for (const group of groups) {
185
+ if (this._hasLink(group))
186
+ return group;
187
+ }
188
+ return null;
189
+ }
190
+ firstUpdated() {
191
+ const { _disposables, gfx } = this;
192
+ _disposables.add(this.std.host.event.add('pointerMove', ctx => {
193
+ const evt = ctx.get('pointerState');
194
+ const [x, y] = gfx.viewport.toModelCoord(evt.x, evt.y);
195
+ this._setTarget(this._resolveLinked(gfx.getElementByPoint(x, y)));
196
+ }));
197
+ _disposables.add(gfx.viewport.viewportUpdated.subscribe(() => {
198
+ if (this._target)
199
+ this._updateRect();
200
+ }));
201
+ _disposables.add(() => {
202
+ if (this._hideTimer)
203
+ clearTimeout(this._hideTimer);
204
+ });
205
+ }
206
+ render() {
207
+ const rect = this._rect;
208
+ const target = this._target;
209
+ if (!target || !rect)
210
+ return nothing;
211
+ const isDoc = Boolean(target.linkedDocId);
212
+ const label = isDoc ? 'Open linked doc' : 'Open link';
213
+ return html `<div
214
+ class="open-link-button"
215
+ role="button"
216
+ tabindex="0"
217
+ title=${label}
218
+ aria-label=${label}
219
+ style=${styleMap({
220
+ left: `${rect.left + rect.width}px`,
221
+ top: `${rect.top}px`,
222
+ })}
223
+ @click=${this._open}
224
+ @keydown=${this._onKeydown}
225
+ @pointerenter=${() => this._setTarget(target)}
226
+ @pointerleave=${() => this._setTarget(null)}
227
+ >
228
+ ${isDoc ? RightSidebarIcon() : OpenInNewIcon()}
229
+ </div>`;
230
+ }
231
+ };
232
+ })();
233
+ export { EdgelessElementLinkWidget };
@@ -1,8 +1,10 @@
1
1
  import { EdgelessAutoCompletePanel } from './auto-complete-panel';
2
2
  import { EdgelessAutoComplete } from './edgeless-auto-complete';
3
+ import { EDGELESS_ELEMENT_LINK_WIDGET, EdgelessElementLinkWidget, } from './edgeless-element-link';
3
4
  import { EDGELESS_SELECTED_RECT_WIDGET, EdgelessSelectedRectWidget, } from './edgeless-selected-rect';
4
5
  export function effects() {
5
6
  customElements.define('edgeless-auto-complete-panel', EdgelessAutoCompletePanel);
6
7
  customElements.define('edgeless-auto-complete', EdgelessAutoComplete);
7
8
  customElements.define(EDGELESS_SELECTED_RECT_WIDGET, EdgelessSelectedRectWidget);
9
+ customElements.define(EDGELESS_ELEMENT_LINK_WIDGET, EdgelessElementLinkWidget);
8
10
  }
@@ -1 +1,2 @@
1
1
  export declare const edgelessSelectedRectWidget: import("../store/index.js").ExtensionType;
2
+ export declare const edgelessElementLinkWidget: import("../store/index.js").ExtensionType;
@@ -1,4 +1,6 @@
1
1
  import { WidgetViewExtension } from '../std/index.js';
2
2
  import { literal, unsafeStatic } from 'lit/static-html.js';
3
+ import { EDGELESS_ELEMENT_LINK_WIDGET } from './edgeless-element-link';
3
4
  import { EDGELESS_SELECTED_RECT_WIDGET } from './edgeless-selected-rect';
4
5
  export const edgelessSelectedRectWidget = WidgetViewExtension('affine:page', EDGELESS_SELECTED_RECT_WIDGET, literal `${unsafeStatic(EDGELESS_SELECTED_RECT_WIDGET)}`);
6
+ export const edgelessElementLinkWidget = WidgetViewExtension('affine:page', EDGELESS_ELEMENT_LINK_WIDGET, literal `${unsafeStatic(EDGELESS_ELEMENT_LINK_WIDGET)}`);
@@ -1,6 +1,6 @@
1
1
  import { ViewExtensionProvider, } from '../affine-ext-loader/index.js';
2
2
  import { effects } from './effects';
3
- import { edgelessSelectedRectWidget } from './spec';
3
+ import { edgelessElementLinkWidget, edgelessSelectedRectWidget } from './spec';
4
4
  export class EdgelessSelectedRectViewExtension extends ViewExtensionProvider {
5
5
  constructor() {
6
6
  super(...arguments);
@@ -14,6 +14,7 @@ export class EdgelessSelectedRectViewExtension extends ViewExtensionProvider {
14
14
  super.setup(context);
15
15
  if (this.isEdgeless(context.scope)) {
16
16
  context.register(edgelessSelectedRectWidget);
17
+ context.register(edgelessElementLinkWidget);
17
18
  }
18
19
  }
19
20
  }
@@ -129,4 +129,10 @@ export declare abstract class DataSourceBase implements DataSource {
129
129
  index: number;
130
130
  } | undefined;
131
131
  isFixedProperty(propertyId: string): boolean;
132
+ /**
133
+ * Tear down anything the source subscribed to (e.g. external observers).
134
+ * No-op by default — the inline blob source holds no subscriptions. The
135
+ * owning block calls this when it rebuilds the source or disconnects.
136
+ */
137
+ dispose(): void;
132
138
  }
@@ -80,4 +80,10 @@ export class DataSourceBase {
80
80
  }
81
81
  return false;
82
82
  }
83
+ /**
84
+ * Tear down anything the source subscribed to (e.g. external observers).
85
+ * No-op by default — the inline blob source holds no subscriptions. The
86
+ * owning block calls this when it rebuilds the source or disconnects.
87
+ */
88
+ dispose() { }
83
89
  }
@@ -6,5 +6,6 @@ export * from './flavour.js';
6
6
  export * from './keymap.js';
7
7
  export * from './lifecycle-watcher.js';
8
8
  export * from './service.js';
9
+ export * from './shortcut.js';
9
10
  export * from './service-manager.js';
10
11
  export * from './widget-view-map.js';
@@ -6,5 +6,6 @@ export * from './flavour.js';
6
6
  export * from './keymap.js';
7
7
  export * from './lifecycle-watcher.js';
8
8
  export * from './service.js';
9
+ export * from './shortcut.js';
9
10
  export * from './service-manager.js';
10
11
  export * from './widget-view-map.js';
@@ -0,0 +1,69 @@
1
+ import type { ExtensionType } from '../../store/index.js';
2
+ import type { UIEventHandler } from '../event/index.js';
3
+ import type { BlockStdScope } from '../scope/index.js';
4
+ export type ShortcutScope = 'global' | 'page' | 'edgeless';
5
+ /**
6
+ * An enumerable, rebindable keyboard shortcut. A descriptor is both a manifest
7
+ * entry (for a host "Shortcuts" settings panel) and the source of the actual
8
+ * binding. The `handler` is a factory receiving the std scope (mirroring
9
+ * {@link KeymapExtension}); the manifest the host consumes only needs the
10
+ * metadata fields, not the handler.
11
+ */
12
+ export interface ShortcutDescriptor {
13
+ /** Stable id, e.g. `'undo'`, `'wardley.addNode'`. */
14
+ id: string;
15
+ /** i18n key; the label is resolved by the host. */
16
+ labelKey: string;
17
+ /** Default key combos per platform, each as an array, e.g. `['Mod', 'z']`. */
18
+ defaultKeys: {
19
+ mac: string[];
20
+ other: string[];
21
+ };
22
+ scope: ShortcutScope;
23
+ /** `'core'` or the owning optional-block flag, for per-framework filtering. */
24
+ owner: string;
25
+ /** Optional activation context (host-interpreted). */
26
+ when?: string;
27
+ handler: (std: BlockStdScope) => UIEventHandler;
28
+ }
29
+ /** Host-provided rebinding table: combo array per id, or `'disabled'`. */
30
+ export type ShortcutOverrides = Record<string, string[] | 'disabled'>;
31
+ export interface ShortcutConflict {
32
+ /** Canonical combo that more than one shortcut resolved to. */
33
+ combo: string;
34
+ scope: ShortcutScope;
35
+ /** Ids competing for the combo (first is the one actually bound). */
36
+ ids: string[];
37
+ }
38
+ export type ShortcutConflictReporter = (conflicts: ShortcutConflict[]) => void;
39
+ /**
40
+ * Register shortcut descriptors. Each becomes a manifest entry and, once the
41
+ * installer ({@link ShortcutKeymapExtension}) runs, an effective binding.
42
+ */
43
+ export declare function ShortcutExtension(descriptors: ShortcutDescriptor[]): ExtensionType;
44
+ /**
45
+ * Canonicalize a combo (array of keys) so equivalent spellings compare equal:
46
+ * modifiers are lowercased/sorted and `Mod` resolves to the platform modifier;
47
+ * the final key is lowercased (Shift is a separate modifier).
48
+ */
49
+ export declare function canonicalCombo(keys: string[]): string;
50
+ /**
51
+ * Build the effective keymap for one scope from the registered descriptors and
52
+ * the override table. `'disabled'` ids are dropped; combo conflicts within the
53
+ * scope are reported (and only the first descriptor for a combo is bound — the
54
+ * duplicates are never bound silently).
55
+ */
56
+ export declare function resolveKeymap(descriptors: ShortcutDescriptor[], overrides: ShortcutOverrides, scope: ShortcutScope, std: BlockStdScope): {
57
+ keymap: Record<string, UIEventHandler>;
58
+ conflicts: ShortcutConflict[];
59
+ };
60
+ /**
61
+ * Install the effective keymap for a scope. Reads every registered
62
+ * {@link ShortcutDescriptor} plus the host override table, resolves conflicts,
63
+ * and binds via the normal dispatcher keymap mechanism. Re-runs whenever the
64
+ * specs are rebuilt (e.g. a framework is toggled on/off).
65
+ *
66
+ * Only `'global'` scope is wired today; `'page'`/`'edgeless'` scoping is a
67
+ * follow-up.
68
+ */
69
+ export declare function ShortcutKeymapExtension(scope?: ShortcutScope): ExtensionType;
@@ -0,0 +1,106 @@
1
+ import { IS_MAC } from '../../global/env/index.js';
2
+ import { ShortcutConflictReporterIdentifier, ShortcutIdentifier, ShortcutOverrideIdentifier, } from '../identifier.js';
3
+ import { KeymapExtension } from './keymap.js';
4
+ let _shortcutId = 1;
5
+ /**
6
+ * Register shortcut descriptors. Each becomes a manifest entry and, once the
7
+ * installer ({@link ShortcutKeymapExtension}) runs, an effective binding.
8
+ */
9
+ export function ShortcutExtension(descriptors) {
10
+ return {
11
+ setup: di => {
12
+ descriptors.forEach(descriptor => {
13
+ di.addImpl(ShortcutIdentifier(`Shortcut-${_shortcutId++}`), descriptor);
14
+ });
15
+ },
16
+ };
17
+ }
18
+ const platformKeys = (d) => IS_MAC ? d.defaultKeys.mac : d.defaultKeys.other;
19
+ /**
20
+ * Canonicalize a combo (array of keys) so equivalent spellings compare equal:
21
+ * modifiers are lowercased/sorted and `Mod` resolves to the platform modifier;
22
+ * the final key is lowercased (Shift is a separate modifier).
23
+ */
24
+ export function canonicalCombo(keys) {
25
+ const key = (keys.at(-1) ?? '').toLowerCase();
26
+ const mods = keys
27
+ .slice(0, -1)
28
+ .map(m => {
29
+ const l = m.toLowerCase();
30
+ if (l === 'mod')
31
+ return IS_MAC ? 'meta' : 'ctrl';
32
+ if (l === 'cmd' || l === 'm' || l === 'meta')
33
+ return 'meta';
34
+ if (l === 'control' || l === 'c' || l === 'ctrl')
35
+ return 'ctrl';
36
+ if (l === 'a' || l === 'alt')
37
+ return 'alt';
38
+ if (l === 's' || l === 'shift')
39
+ return 'shift';
40
+ return l;
41
+ })
42
+ .sort();
43
+ return [...mods, key].join('-');
44
+ }
45
+ /**
46
+ * Build the effective keymap for one scope from the registered descriptors and
47
+ * the override table. `'disabled'` ids are dropped; combo conflicts within the
48
+ * scope are reported (and only the first descriptor for a combo is bound — the
49
+ * duplicates are never bound silently).
50
+ */
51
+ export function resolveKeymap(descriptors, overrides, scope, std) {
52
+ const keymap = {};
53
+ const conflicts = [];
54
+ const boundBy = new Map(); // canonical combo -> bound id
55
+ for (const d of descriptors) {
56
+ if (d.scope !== scope)
57
+ continue;
58
+ const override = overrides[d.id];
59
+ if (override === 'disabled')
60
+ continue;
61
+ const keys = override ?? platformKeys(d);
62
+ if (!keys.length)
63
+ continue;
64
+ const canonical = canonicalCombo(keys);
65
+ const existing = boundBy.get(canonical);
66
+ if (existing) {
67
+ const conflict = conflicts.find(c => c.combo === canonical);
68
+ if (conflict) {
69
+ if (!conflict.ids.includes(d.id))
70
+ conflict.ids.push(d.id);
71
+ }
72
+ else {
73
+ conflicts.push({ combo: canonical, scope, ids: [existing, d.id] });
74
+ }
75
+ continue; // do not silently bind a second action to the same combo
76
+ }
77
+ boundBy.set(canonical, d.id);
78
+ keymap[keys.join('-')] = d.handler(std);
79
+ }
80
+ return { keymap, conflicts };
81
+ }
82
+ /**
83
+ * Install the effective keymap for a scope. Reads every registered
84
+ * {@link ShortcutDescriptor} plus the host override table, resolves conflicts,
85
+ * and binds via the normal dispatcher keymap mechanism. Re-runs whenever the
86
+ * specs are rebuilt (e.g. a framework is toggled on/off).
87
+ *
88
+ * Only `'global'` scope is wired today; `'page'`/`'edgeless'` scoping is a
89
+ * follow-up.
90
+ */
91
+ export function ShortcutKeymapExtension(scope = 'global') {
92
+ // No flavour/blockId option → a global keymap (like `fallbackKeymap`).
93
+ return KeymapExtension(std => {
94
+ const descriptors = [...std.provider.getAll(ShortcutIdentifier).values()];
95
+ const overrides = std.getOptional(ShortcutOverrideIdentifier) ?? {};
96
+ const { keymap, conflicts } = resolveKeymap(descriptors, overrides, scope, std);
97
+ if (conflicts.length) {
98
+ const report = std.getOptional(ShortcutConflictReporterIdentifier);
99
+ if (report)
100
+ report(conflicts);
101
+ else
102
+ console.warn('[shortcut] combo conflicts (duplicates not bound):', conflicts);
103
+ }
104
+ return keymap;
105
+ });
106
+ }