@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.
- package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +6 -7
- package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/components/embed-synced-doc-card.js +5 -5
- package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -1
- package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-root/index.js +1 -0
- package/dist/_pkgs/affine-block-root/keyboard/keyboard-manager.js +3 -21
- package/dist/_pkgs/affine-block-root/keyboard/shortcuts.d.ts +8 -0
- package/dist/_pkgs/affine-block-root/keyboard/shortcuts.js +48 -0
- package/dist/_pkgs/affine-block-root/view.js +6 -1
- package/dist/_pkgs/affine-gfx-link/effects.js +2 -2
- package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.d.ts +3 -0
- package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.js +12 -0
- package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.d.ts +18 -0
- package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.js +41 -0
- package/dist/_pkgs/affine-gfx-link/undo-tool.d.ts +6 -0
- package/dist/_pkgs/affine-gfx-link/undo-tool.js +17 -0
- package/dist/_pkgs/affine-gfx-link/view.js +4 -2
- package/dist/_pkgs/affine-gfx-mindmap/toolbar/mindmap-tool-button.js +13 -2
- package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.d.ts +12 -2
- package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.js +62 -11
- package/dist/_pkgs/affine-gfx-mindmap/view.d.ts +13 -2
- package/dist/_pkgs/affine-gfx-mindmap/view.js +24 -5
- package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
- package/dist/_pkgs/affine-shared/services/index.js +2 -0
- package/dist/_pkgs/affine-shared/services/keymap-override-service.d.ts +15 -0
- package/dist/_pkgs/affine-shared/services/keymap-override-service.js +26 -0
- package/dist/_pkgs/affine-shared/services/linked-doc-content-service.d.ts +42 -0
- package/dist/_pkgs/affine-shared/services/linked-doc-content-service.js +47 -0
- package/dist/_pkgs/std/extension/index.d.ts +1 -0
- package/dist/_pkgs/std/extension/index.js +1 -0
- package/dist/_pkgs/std/extension/shortcut.d.ts +69 -0
- package/dist/_pkgs/std/extension/shortcut.js +106 -0
- package/dist/_pkgs/std/identifier.d.ts +7 -0
- package/dist/_pkgs/std/identifier.js +6 -0
- package/dist/extensions/view.js +5 -2
- package/dist/flags.d.ts +1 -1
- package/dist/flags.js +4 -1
- package/dist/shortcuts.d.ts +24 -0
- package/dist/shortcuts.js +39 -0
- package/package.json +5 -1
- package/dist/_pkgs/affine-gfx-link/link-tool.d.ts +0 -1
- package/dist/_pkgs/affine-gfx-link/link-tool.js +0 -11
- package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.d.ts +0 -2
- package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.js +0 -26
- package/dist/_pkgs/affine-gfx-link/toolbar/link-tool-button.d.ts +0 -10
- 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
|
+
}
|
|
@@ -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');
|
package/dist/extensions/view.js
CHANGED
|
@@ -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,
|
|
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", "
|
|
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
|
-
|
|
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.
|
|
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,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
|
-
}
|