@formicoidea/labre-core 0.28.0 → 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 (46) hide show
  1. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +6 -7
  2. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/components/embed-synced-doc-card.js +5 -5
  3. package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -1
  4. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  5. package/dist/_pkgs/affine-block-root/index.js +1 -0
  6. package/dist/_pkgs/affine-block-root/keyboard/keyboard-manager.js +3 -21
  7. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.d.ts +8 -0
  8. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.js +48 -0
  9. package/dist/_pkgs/affine-block-root/view.js +6 -1
  10. package/dist/_pkgs/affine-gfx-link/effects.js +2 -2
  11. package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.d.ts +3 -0
  12. package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.js +12 -0
  13. package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.d.ts +18 -0
  14. package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.js +41 -0
  15. package/dist/_pkgs/affine-gfx-link/undo-tool.d.ts +6 -0
  16. package/dist/_pkgs/affine-gfx-link/undo-tool.js +17 -0
  17. package/dist/_pkgs/affine-gfx-link/view.js +4 -2
  18. package/dist/_pkgs/affine-gfx-mindmap/toolbar/mindmap-tool-button.js +13 -2
  19. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.d.ts +12 -2
  20. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.js +62 -11
  21. package/dist/_pkgs/affine-gfx-mindmap/view.d.ts +13 -2
  22. package/dist/_pkgs/affine-gfx-mindmap/view.js +24 -5
  23. package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
  24. package/dist/_pkgs/affine-shared/services/index.js +2 -0
  25. package/dist/_pkgs/affine-shared/services/keymap-override-service.d.ts +15 -0
  26. package/dist/_pkgs/affine-shared/services/keymap-override-service.js +26 -0
  27. package/dist/_pkgs/affine-shared/services/linked-doc-content-service.d.ts +42 -0
  28. package/dist/_pkgs/affine-shared/services/linked-doc-content-service.js +47 -0
  29. package/dist/_pkgs/std/extension/index.d.ts +1 -0
  30. package/dist/_pkgs/std/extension/index.js +1 -0
  31. package/dist/_pkgs/std/extension/shortcut.d.ts +69 -0
  32. package/dist/_pkgs/std/extension/shortcut.js +106 -0
  33. package/dist/_pkgs/std/identifier.d.ts +7 -0
  34. package/dist/_pkgs/std/identifier.js +6 -0
  35. package/dist/extensions/view.js +5 -2
  36. package/dist/flags.d.ts +1 -1
  37. package/dist/flags.js +4 -1
  38. package/dist/shortcuts.d.ts +24 -0
  39. package/dist/shortcuts.js +39 -0
  40. package/package.json +5 -1
  41. package/dist/_pkgs/affine-gfx-link/link-tool.d.ts +0 -1
  42. package/dist/_pkgs/affine-gfx-link/link-tool.js +0 -11
  43. package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.d.ts +0 -2
  44. package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.js +0 -26
  45. package/dist/_pkgs/affine-gfx-link/toolbar/link-tool-button.d.ts +0 -10
  46. package/dist/_pkgs/affine-gfx-link/toolbar/link-tool-button.js +0 -84
@@ -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
+ }
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Command } from './command/index.js';
2
2
  import type { EventOptions, UIEventHandler } from './event/index.js';
3
3
  import type { BlockService, LifeCycleWatcher } from './extension/index.js';
4
+ import type { ShortcutConflictReporter, ShortcutDescriptor, ShortcutOverrides } from './extension/shortcut.js';
4
5
  import type { BlockStdScope } from './scope/index.js';
5
6
  import type { BlockViewType } from './spec/type.js';
6
7
  export declare const BlockServiceIdentifier: import("../global/di/types.js").ServiceIdentifier<BlockService> & (<U extends BlockService = BlockService>(variant: import("../global/di/types.js").ServiceVariant) => import("../global/di/types.js").ServiceIdentifier<U>);
@@ -27,3 +28,9 @@ export declare const KeymapIdentifier: import("../global/di/types.js").ServiceId
27
28
  getter: (std: BlockStdScope) => Record<string, UIEventHandler>;
28
29
  options?: EventOptions;
29
30
  }>(variant: import("../global/di/types.js").ServiceVariant) => import("../global/di/types.js").ServiceIdentifier<U>);
31
+ /** Multi-instance: one registered {@link ShortcutDescriptor} per impl. */
32
+ export declare const ShortcutIdentifier: import("../global/di/types.js").ServiceIdentifier<ShortcutDescriptor> & (<U extends ShortcutDescriptor = ShortcutDescriptor>(variant: import("../global/di/types.js").ServiceVariant) => import("../global/di/types.js").ServiceIdentifier<U>);
33
+ /** Host-injected rebinding table (see `KeymapOverrideExtension`). */
34
+ export declare const ShortcutOverrideIdentifier: import("../global/di/types.js").ServiceIdentifier<ShortcutOverrides> & (<U extends ShortcutOverrides = ShortcutOverrides>(variant: import("../global/di/types.js").ServiceVariant) => import("../global/di/types.js").ServiceIdentifier<U>);
35
+ /** Host-injected sink for reported combo conflicts. */
36
+ export declare const ShortcutConflictReporterIdentifier: import("../global/di/types.js").ServiceIdentifier<ShortcutConflictReporter> & (<U extends ShortcutConflictReporter = ShortcutConflictReporter>(variant: import("../global/di/types.js").ServiceVariant) => import("../global/di/types.js").ServiceIdentifier<U>);
@@ -8,3 +8,9 @@ export const WidgetViewIdentifier = createIdentifier('WidgetView');
8
8
  export const LifeCycleWatcherIdentifier = createIdentifier('LifeCycleWatcher');
9
9
  export const StdIdentifier = createIdentifier('Std');
10
10
  export const KeymapIdentifier = createIdentifier('Keymap');
11
+ /** Multi-instance: one registered {@link ShortcutDescriptor} per impl. */
12
+ export const ShortcutIdentifier = createIdentifier('Shortcut');
13
+ /** Host-injected rebinding table (see `KeymapOverrideExtension`). */
14
+ export const ShortcutOverrideIdentifier = createIdentifier('ShortcutOverride');
15
+ /** Host-injected sink for reported combo conflicts. */
16
+ export const ShortcutConflictReporterIdentifier = createIdentifier('ShortcutConflictReporter');
@@ -27,7 +27,7 @@ import { BrushViewExtension } from '../_pkgs/affine-gfx-brush/view.js';
27
27
  import { ConnectorViewExtension } from '../_pkgs/affine-gfx-connector/view.js';
28
28
  import { GroupViewExtension } from '../_pkgs/affine-gfx-group/view.js';
29
29
  import { LinkViewExtension as GfxLinkViewExtension } from '../_pkgs/affine-gfx-link/view.js';
30
- import { MindmapRenderViewExtension, MindmapToolViewExtension, OtherToolViewExtension, } from '../_pkgs/affine-gfx-mindmap/view.js';
30
+ import { MediaToolViewExtension, MindmapRenderViewExtension, MindmapToolViewExtension, TextToolViewExtension, } from '../_pkgs/affine-gfx-mindmap/view.js';
31
31
  import { NoteViewExtension as GfxNoteViewExtension } from '../_pkgs/affine-gfx-note/view.js';
32
32
  import { PointerViewExtension } from '../_pkgs/affine-gfx-pointer/view.js';
33
33
  import { ShapeViewExtension } from '../_pkgs/affine-gfx-shape/view.js';
@@ -69,11 +69,14 @@ export function getInternalViewExtensions(flags) {
69
69
  PointerViewExtension,
70
70
  GfxNoteViewExtension,
71
71
  ...(on('brush') ? [BrushViewExtension] : []),
72
+ // Standalone text / add-file senior buttons, placed right after pen/eraser
73
+ // (registration order = senior-row order for the default order group).
74
+ ...(on('edgeless-text') ? [TextToolViewExtension] : []),
75
+ ...(on('edgeless-media') ? [MediaToolViewExtension] : []),
72
76
  ShapeViewExtension,
73
77
  // Mindmap rendering is always on; the flags gate only the senior buttons.
74
78
  MindmapRenderViewExtension,
75
79
  ...(on('mindmap') ? [MindmapToolViewExtension] : []),
76
- ...(on('other') ? [OtherToolViewExtension] : []),
77
80
  ConnectorViewExtension,
78
81
  GroupViewExtension,
79
82
  TextViewExtension,
package/dist/flags.d.ts CHANGED
@@ -23,7 +23,7 @@
23
23
  * - `latex` controls both the latex block and inline latex.
24
24
  * - `frame` also controls the frame panel fragment.
25
25
  */
26
- export declare const OPTIONAL_BLOCKS: readonly ["attachment", "bookmark", "callout", "code", "data-view", "database", "divider", "edgeless-text", "embed", "embed-doc", "frame", "image", "latex", "list", "surface-ref", "table", "brush", "mindmap", "other", "template", "link"];
26
+ export declare const OPTIONAL_BLOCKS: readonly ["attachment", "bookmark", "callout", "code", "data-view", "database", "divider", "edgeless-text", "embed", "embed-doc", "frame", "image", "latex", "list", "surface-ref", "table", "brush", "mindmap", "edgeless-text", "edgeless-media", "template", "link"];
27
27
  export type OptionalBlock = (typeof OPTIONAL_BLOCKS)[number];
28
28
  /** Map of optional block -> enabled. Missing keys default to enabled. */
29
29
  export type BlockFlags = Partial<Record<OptionalBlock, boolean>>;
package/dist/flags.js CHANGED
@@ -43,7 +43,10 @@ export const OPTIONAL_BLOCKS = [
43
43
  // gfx modules
44
44
  'brush',
45
45
  'mindmap',
46
- 'other',
46
+ // Standalone text / add-file tools, promoted out of the former "Others"
47
+ // senior submenu (each individually toggleable).
48
+ 'edgeless-text',
49
+ 'edgeless-media',
47
50
  'template',
48
51
  'link',
49
52
  ];
@@ -0,0 +1,24 @@
1
+ import type { ShortcutDescriptor } from './_pkgs/std/index.js';
2
+ import { type BlockFlags, type OptionalBlock } from './flags.js';
3
+ /**
4
+ * Manifest view of a shortcut: the metadata a host "Shortcuts" settings panel
5
+ * needs, without the runtime `handler`.
6
+ */
7
+ export type ShortcutManifestEntry = Omit<ShortcutDescriptor, 'handler'>;
8
+ interface FrameworkShortcutGroup {
9
+ owner: OptionalBlock;
10
+ shortcuts: ShortcutDescriptor[];
11
+ }
12
+ /**
13
+ * Pure aggregator (exported for testing): core shortcuts plus the shortcuts of
14
+ * every framework group whose flag is enabled.
15
+ */
16
+ export declare function buildShortcutManifest(core: ShortcutDescriptor[], groups: FrameworkShortcutGroup[], flags?: BlockFlags): ShortcutManifestEntry[];
17
+ /**
18
+ * The enumerable shortcut manifest for a given flag set: core shortcuts plus
19
+ * the shortcuts contributed by the currently-enabled frameworks. Enumerable
20
+ * without an editor instance (for a settings panel). Mirrors the flag gating of
21
+ * `getInternalViewExtensions`.
22
+ */
23
+ export declare function getShortcutManifest(flags?: BlockFlags): ShortcutManifestEntry[];
24
+ export {};
@@ -0,0 +1,39 @@
1
+ import { coreShortcuts } from './_pkgs/affine-block-root/index.js';
2
+ import { isBlockEnabled } from './flags.js';
3
+ /**
4
+ * Per-framework shortcut contributions. A framework that adds shortcuts lists
5
+ * them here (manifest) AND registers `ShortcutExtension(...)` in its view
6
+ * (runtime binding); both are gated by its flag. No framework ships shortcuts
7
+ * yet — the seam is ready and the manifest currently returns core only.
8
+ */
9
+ const FRAMEWORK_SHORTCUT_GROUPS = [];
10
+ const toEntry = (d) => ({
11
+ id: d.id,
12
+ labelKey: d.labelKey,
13
+ defaultKeys: d.defaultKeys,
14
+ scope: d.scope,
15
+ owner: d.owner,
16
+ when: d.when,
17
+ });
18
+ /**
19
+ * Pure aggregator (exported for testing): core shortcuts plus the shortcuts of
20
+ * every framework group whose flag is enabled.
21
+ */
22
+ export function buildShortcutManifest(core, groups, flags) {
23
+ const entries = core.map(toEntry);
24
+ for (const { owner, shortcuts } of groups) {
25
+ if (isBlockEnabled(flags, owner)) {
26
+ entries.push(...shortcuts.map(toEntry));
27
+ }
28
+ }
29
+ return entries;
30
+ }
31
+ /**
32
+ * The enumerable shortcut manifest for a given flag set: core shortcuts plus
33
+ * the shortcuts contributed by the currently-enabled frameworks. Enumerable
34
+ * without an editor instance (for a settings panel). Mirrors the flag gating of
35
+ * `getInternalViewExtensions`.
36
+ */
37
+ export function getShortcutManifest(flags) {
38
+ return buildShortcutManifest(coreShortcuts, FRAMEWORK_SHORTCUT_GROUPS, flags);
39
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-core",
3
3
  "description": "Labre editor — the full BlockSuite-derived editor as one package.",
4
- "version": "0.28.0",
4
+ "version": "0.29.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -810,6 +810,10 @@
810
810
  "types": "./dist/flags.d.ts",
811
811
  "import": "./dist/flags.js"
812
812
  },
813
+ "./shortcuts": {
814
+ "types": "./dist/shortcuts.d.ts",
815
+ "import": "./dist/shortcuts.js"
816
+ },
813
817
  "./model": {
814
818
  "types": "./dist/model/index.d.ts",
815
819
  "import": "./dist/model/index.js"
@@ -1 +0,0 @@
1
- export declare const linkQuickTool: import("../store/index.js").ExtensionType;
@@ -1,11 +0,0 @@
1
- import { QuickToolExtension } from '../affine-widget-edgeless-toolbar/index.js';
2
- import { html } from 'lit';
3
- import { buildLinkDenseMenu } from './toolbar/link-dense-menu';
4
- export const linkQuickTool = QuickToolExtension('link', ({ block, gfx }) => {
5
- return {
6
- content: html `<edgeless-link-tool-button
7
- .edgeless=${block}
8
- ></edgeless-link-tool-button>`,
9
- menu: buildLinkDenseMenu(block, gfx),
10
- };
11
- });
@@ -1,2 +0,0 @@
1
- import type { DenseMenuBuilder } from '../../affine-widget-edgeless-toolbar/index.js';
2
- export declare const buildLinkDenseMenu: DenseMenuBuilder;
@@ -1,26 +0,0 @@
1
- import { insertLinkByQuickSearchCommand } from '../../affine-block-bookmark/index.js';
2
- import { menu } from '../../affine-components/context-menu/index.js';
3
- import { LinkIcon } from '../../affine-components/icons/index.js';
4
- import { TelemetryProvider } from '../../affine-shared/services/index.js';
5
- export const buildLinkDenseMenu = edgeless => menu.action({
6
- name: 'Link',
7
- prefix: LinkIcon,
8
- select: () => {
9
- const [_, { insertedLinkType }] = edgeless.std.command.exec(insertLinkByQuickSearchCommand);
10
- insertedLinkType
11
- ?.then(type => {
12
- const flavour = type?.flavour;
13
- if (!flavour)
14
- return;
15
- edgeless.std
16
- .getOptional(TelemetryProvider)
17
- ?.track('CanvasElementAdded', {
18
- control: 'toolbar:general',
19
- page: 'whiteboard editor',
20
- module: 'toolbar',
21
- type: flavour.split(':')[1],
22
- });
23
- })
24
- .catch(console.error);
25
- },
26
- });
@@ -1,10 +0,0 @@
1
- import { DefaultTool } from '../../affine-block-surface/index.js';
2
- import { LitElement } from 'lit';
3
- declare const EdgelessLinkToolButton_base: typeof LitElement & import("../../global/utils/types.js").Constructor<import("../../affine-widget-edgeless-toolbar/index.js").QuickToolMixinClass>;
4
- export declare class EdgelessLinkToolButton extends EdgelessLinkToolButton_base {
5
- static styles: import("lit").CSSResult;
6
- type: typeof DefaultTool;
7
- private _onClick;
8
- render(): import("lit-html").TemplateResult<1>;
9
- }
10
- export {};
@@ -1,84 +0,0 @@
1
- import { insertLinkByQuickSearchCommand } from '../../affine-block-bookmark/index.js';
2
- import { insertEmbedCard } from '../../affine-block-embed/index.js';
3
- import { DefaultTool } from '../../affine-block-surface/index.js';
4
- import { toggleEmbedCardCreateModal } from '../../affine-components/embed-card-modal/index.js';
5
- import { LinkIcon } from '../../affine-components/icons/index.js';
6
- import { TelemetryProvider } from '../../affine-shared/services/index.js';
7
- import { QuickToolMixin } from '../../affine-widget-edgeless-toolbar/index.js';
8
- import { GfxControllerIdentifier } from '../../std/gfx/index.js';
9
- import { css, html, LitElement } from 'lit';
10
- export class EdgelessLinkToolButton extends QuickToolMixin(LitElement) {
11
- constructor() {
12
- super(...arguments);
13
- this.type = DefaultTool;
14
- }
15
- static { this.styles = css `
16
- .link-icon,
17
- .link-icon > svg {
18
- width: 24px;
19
- height: 24px;
20
- }
21
- `; }
22
- _onClick() {
23
- const [success, { insertedLinkType }] = this.edgeless.std.command.exec(insertLinkByQuickSearchCommand);
24
- if (!success) {
25
- // fallback to create a bookmark block with input modal
26
- toggleEmbedCardCreateModal(this.edgeless.host, 'Links', 'The added link will be displayed as a card view.', {
27
- mode: 'edgeless',
28
- onSave: url => {
29
- insertEmbedCard(this.edgeless.std, {
30
- flavour: 'affine:bookmark',
31
- targetStyle: 'vertical',
32
- props: { url },
33
- });
34
- },
35
- }, ({ mode }) => {
36
- if (mode === 'edgeless') {
37
- const gfx = this.edgeless.std.get(GfxControllerIdentifier);
38
- gfx.tool.setTool(DefaultTool);
39
- }
40
- }).catch(console.error);
41
- return;
42
- }
43
- insertedLinkType
44
- ?.then(type => {
45
- const flavour = type?.flavour;
46
- if (!flavour)
47
- return;
48
- this.edgeless.std
49
- .getOptional(TelemetryProvider)
50
- ?.track('CanvasElementAdded', {
51
- control: 'toolbar:general',
52
- page: 'whiteboard editor',
53
- module: 'toolbar',
54
- segment: 'toolbar',
55
- type: flavour.split(':')[1],
56
- });
57
- this.edgeless.std
58
- .getOptional(TelemetryProvider)
59
- ?.track('LinkedDocCreated', {
60
- control: 'links',
61
- page: 'whiteboard editor',
62
- module: 'edgeless toolbar',
63
- segment: 'whiteboard',
64
- type: flavour.split(':')[1],
65
- other: 'existing doc',
66
- });
67
- })
68
- .catch(console.error);
69
- }
70
- render() {
71
- return html `<edgeless-tool-icon-button
72
- .iconContainerPadding="${6}"
73
- .tooltip="${html `<affine-tooltip-content-with-shortcut
74
- data-tip="${'Link'}"
75
- data-shortcut="${'@'}"
76
- ></affine-tooltip-content-with-shortcut>`}"
77
- .tooltipOffset=${17}
78
- class="edgeless-link-tool-button"
79
- @click=${this._onClick}
80
- >
81
- <span class="link-icon">${LinkIcon}</span>
82
- </edgeless-tool-icon-button>`;
83
- }
84
- }