@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
@@ -38,7 +38,7 @@ import { LoadingIcon } from '../../affine-components/icons/index.js';
38
38
  import { isPeekable, Peekable } from '../../affine-components/peek/index.js';
39
39
  import { RefNodeSlotsProvider } from '../../affine-inline-reference/index.js';
40
40
  import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, REFERENCE_NODE, } from '../../affine-shared/consts/index.js';
41
- import { CitationProvider, DocDisplayMetaProvider, DocModeProvider, OpenDocExtensionIdentifier, ThemeProvider, } from '../../affine-shared/services/index.js';
41
+ import { CitationProvider, DocDisplayMetaProvider, DocModeProvider, OpenDocExtensionIdentifier, ThemeProvider, whenLinkedDocContentReady, } from '../../affine-shared/services/index.js';
42
42
  import { cloneReferenceInfo, cloneReferenceInfoWithoutAliases, isNewTabTrigger, isNewViewTrigger, matchModels, referenceToNode, } from '../../affine-shared/utils/index.js';
43
43
  import { Bound } from '../../global/gfx/index.js';
44
44
  import { ResetIcon } from '@blocksuite/icons/lit';
@@ -286,12 +286,11 @@ let EmbedLinkedDocBlockComponent = (() => {
286
286
  }
287
287
  }
288
288
  if (!this.isError && !linkedDoc.root) {
289
- await new Promise(resolve => {
290
- const subscription = linkedDoc.slots.rootAdded.subscribe(() => {
291
- subscription.unsubscribe();
292
- resolve();
293
- });
294
- });
289
+ // Bounded wait: if the host hasn't preloaded the referenced doc, ask the
290
+ // optional content resolver to hydrate it and wait at most `timeoutMs`.
291
+ // On timeout we stop loading and fall through to a degraded card (title
292
+ // only) instead of spinning forever.
293
+ await whenLinkedDocContentReady(this.std, linkedDoc, this.model.props.pageId);
295
294
  }
296
295
  this._loading = false;
297
296
  // If it is a link to a block or element, the content will not be rendered.
@@ -34,7 +34,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
34
34
  };
35
35
  import { RENDER_CARD_THROTTLE_MS } from '../../../affine-block-embed/index.js';
36
36
  import { LoadingIcon } from '../../../affine-components/icons/index.js';
37
- import { ThemeProvider } from '../../../affine-shared/services/index.js';
37
+ import { ThemeProvider, whenLinkedDocContentReady, } from '../../../affine-shared/services/index.js';
38
38
  import { WithDisposable } from '../../../global/lit/index.js';
39
39
  import { ResetIcon } from '@blocksuite/icons/lit';
40
40
  import { BlockSelection, isGfxBlockComponent, ShadowlessElement, } from '../../../std/index.js';
@@ -139,10 +139,10 @@ let EmbedSyncedDocCard = (() => {
139
139
  renderLinkedDocInCard(this);
140
140
  }
141
141
  else {
142
- const subscription = syncedDoc.slots.rootAdded.subscribe(() => {
143
- subscription.unsubscribe();
144
- renderLinkedDocInCard(this);
145
- });
142
+ // Bounded: ask the host to hydrate the doc, wait at most timeoutMs, then
143
+ // render regardless (degraded if still empty) rather than waiting for a
144
+ // `rootAdded` that may never fire when the doc isn't preloaded.
145
+ void whenLinkedDocContentReady(this.std, syncedDoc, this.model.props.pageId).then(() => renderLinkedDocInCard(this));
146
146
  }
147
147
  this.disposables.add(syncedDoc.workspace.slots.docListUpdated.subscribe(() => {
148
148
  renderLinkedDocInCard(this);
@@ -39,12 +39,12 @@ export declare const builtinToolbarConfig: {
39
39
  readonly label?: string;
40
40
  readonly icon?: import("lit-html").TemplateResult;
41
41
  readonly active?: ((cx: import("../../affine-shared/services/index.js").ToolbarContext) => boolean) | boolean;
42
+ readonly disabled?: ((cx: import("../../affine-shared/services/index.js").ToolbarContext) => boolean) | boolean;
42
43
  readonly placement?: ActionPlacement;
43
44
  readonly tooltip?: string | import("lit-html").TemplateResult;
44
45
  readonly score?: number;
45
46
  readonly when?: ((cx: import("../../affine-shared/services/index.js").ToolbarContext) => boolean) | boolean;
46
47
  readonly showLabel?: boolean;
47
- readonly disabled?: ((cx: import("../../affine-shared/services/index.js").ToolbarContext) => boolean) | boolean;
48
48
  readonly run?: (cx: import("../../affine-shared/services/index.js").ToolbarContext) => void;
49
49
  readonly id: "g.comment";
50
50
  }, {
@@ -2,6 +2,7 @@ export * from './adapters';
2
2
  export * from './clipboard/index.js';
3
3
  export * from './edgeless/edgeless-root-spec.js';
4
4
  export * from './edgeless/index.js';
5
+ export * from './keyboard/shortcuts.js';
5
6
  export * from './page/page-root-block.js';
6
7
  export * from './preview/preview-root-block.js';
7
8
  export * from './types.js';
@@ -2,6 +2,7 @@ export * from './adapters';
2
2
  export * from './clipboard/index.js';
3
3
  export * from './edgeless/edgeless-root-spec.js';
4
4
  export * from './edgeless/index.js';
5
+ export * from './keyboard/shortcuts.js';
5
6
  export * from './page/page-root-block.js';
6
7
  export * from './preview/preview-root-block.js';
7
8
  export * from './types.js';
@@ -3,7 +3,7 @@ import { ParagraphBlockComponent } from '../../affine-block-paragraph/index.js';
3
3
  import { NoteBlockModel, ParagraphBlockModel } from '../../affine-model/index.js';
4
4
  import { draftSelectedModelsCommand, getSelectedModelsCommand, } from '../../affine-shared/commands/index.js';
5
5
  import { matchModels } from '../../affine-shared/utils/index.js';
6
- import { IS_MAC, IS_WINDOWS } from '../../global/env/index.js';
6
+ import { IS_MAC } from '../../global/env/index.js';
7
7
  import { BlockSelection, } from '../../std/index.js';
8
8
  import { toDraftModel } from '../../store/index.js';
9
9
  export class PageKeyboardManager {
@@ -55,27 +55,9 @@ export class PageKeyboardManager {
55
55
  this._selection.clear(['block', 'text']);
56
56
  });
57
57
  };
58
+ // undo / redo are now rebindable shortcut descriptors (see
59
+ // `keyboard/shortcuts.ts`), installed via `ShortcutKeymapExtension`.
58
60
  this.rootComponent.bindHotKey({
59
- 'Mod-z': ctx => {
60
- ctx.get('defaultState').event.preventDefault();
61
- if (this._doc.canUndo) {
62
- this._doc.undo();
63
- }
64
- },
65
- 'Shift-Mod-z': ctx => {
66
- ctx.get('defaultState').event.preventDefault();
67
- if (this._doc.canRedo) {
68
- this._doc.redo();
69
- }
70
- },
71
- 'Control-y': ctx => {
72
- if (!IS_WINDOWS)
73
- return;
74
- ctx.get('defaultState').event.preventDefault();
75
- if (this._doc.canRedo) {
76
- this._doc.redo();
77
- }
78
- },
79
61
  'Mod-Backspace': () => true,
80
62
  Backspace: this._handleDelete,
81
63
  Delete: this._handleDelete,
@@ -0,0 +1,8 @@
1
+ import type { ShortcutDescriptor } from '../../std/index.js';
2
+ /**
3
+ * Core (always-on) keyboard shortcuts, expressed as rebindable descriptors.
4
+ * Migrated out of the imperative `PageKeyboardManager` bindings so the host can
5
+ * enumerate and override them. Installed by `ShortcutKeymapExtension` (see
6
+ * root `view.ts`).
7
+ */
8
+ export declare const coreShortcuts: ShortcutDescriptor[];
@@ -0,0 +1,48 @@
1
+ import { IS_WINDOWS } from '../../global/env/index.js';
2
+ /**
3
+ * Core (always-on) keyboard shortcuts, expressed as rebindable descriptors.
4
+ * Migrated out of the imperative `PageKeyboardManager` bindings so the host can
5
+ * enumerate and override them. Installed by `ShortcutKeymapExtension` (see
6
+ * root `view.ts`).
7
+ */
8
+ export const coreShortcuts = [
9
+ {
10
+ id: 'undo',
11
+ labelKey: 'com.affine.keyboardShortcuts.undo',
12
+ defaultKeys: { mac: ['Mod', 'z'], other: ['Mod', 'z'] },
13
+ scope: 'global',
14
+ owner: 'core',
15
+ handler: std => ctx => {
16
+ ctx.get('defaultState').event.preventDefault();
17
+ if (std.store.canUndo)
18
+ std.store.undo();
19
+ },
20
+ },
21
+ {
22
+ id: 'redo',
23
+ labelKey: 'com.affine.keyboardShortcuts.redo',
24
+ defaultKeys: { mac: ['Shift', 'Mod', 'z'], other: ['Shift', 'Mod', 'z'] },
25
+ scope: 'global',
26
+ owner: 'core',
27
+ handler: std => ctx => {
28
+ ctx.get('defaultState').event.preventDefault();
29
+ if (std.store.canRedo)
30
+ std.store.redo();
31
+ },
32
+ },
33
+ {
34
+ // Windows-only redo alias (Ctrl+Y); not bound on mac (empty combo).
35
+ id: 'redo-windows',
36
+ labelKey: 'com.affine.keyboardShortcuts.redo',
37
+ defaultKeys: { mac: [], other: ['Control', 'y'] },
38
+ scope: 'global',
39
+ owner: 'core',
40
+ handler: std => ctx => {
41
+ if (!IS_WINDOWS)
42
+ return;
43
+ ctx.get('defaultState').event.preventDefault();
44
+ if (std.store.canRedo)
45
+ std.store.redo();
46
+ },
47
+ },
48
+ ];
@@ -1,7 +1,7 @@
1
1
  import { ViewExtensionProvider, } from '../affine-ext-loader/index.js';
2
2
  import { NoteBlockSchema } from '../affine-model/index.js';
3
3
  import { ToolbarModuleExtension, ViewportElementExtension, } from '../affine-shared/services/index.js';
4
- import { BlockFlavourIdentifier, BlockViewExtension, FlavourExtension, } from '../std/index.js';
4
+ import { BlockFlavourIdentifier, BlockViewExtension, FlavourExtension, ShortcutExtension, ShortcutKeymapExtension, } from '../std/index.js';
5
5
  import { literal } from 'lit/static-html.js';
6
6
  import { PageClipboard, ReadOnlyClipboard } from './clipboard';
7
7
  import { builtinToolbarConfig } from './configs/toolbar';
@@ -11,6 +11,7 @@ import { EdgelessLocker } from './edgeless/edgeless-root-spec';
11
11
  import { AltCloneExtension } from './edgeless/interact-extensions/clone-ext';
12
12
  import { effects } from './effects';
13
13
  import { fallbackKeymap } from './keyboard/keymap';
14
+ import { coreShortcuts } from './keyboard/shortcuts';
14
15
  export class RootViewExtension extends ViewExtensionProvider {
15
16
  constructor() {
16
17
  super(...arguments);
@@ -53,6 +54,10 @@ export class RootViewExtension extends ViewExtensionProvider {
53
54
  context.register([
54
55
  FlavourExtension('affine:page'),
55
56
  fallbackKeymap,
57
+ // Enumerable, host-overridable shortcuts. Core undo/redo live here; the
58
+ // installer binds the effective keymap (see `@labre/std` shortcut.ts).
59
+ ShortcutExtension(coreShortcuts),
60
+ ShortcutKeymapExtension('global'),
56
61
  ToolbarModuleExtension({
57
62
  id: BlockFlavourIdentifier(NoteBlockSchema.model.flavour),
58
63
  config: builtinToolbarConfig,
@@ -1,4 +1,4 @@
1
- import { EdgelessLinkToolButton } from './toolbar/link-tool-button';
1
+ import { EdgelessUndoToolButton } from './toolbar/undo-tool-button';
2
2
  export function effects() {
3
- customElements.define('edgeless-link-tool-button', EdgelessLinkToolButton);
3
+ customElements.define('edgeless-undo-tool-button', EdgelessUndoToolButton);
4
4
  }
@@ -0,0 +1,3 @@
1
+ import type { DenseMenuBuilder } from '../../affine-widget-edgeless-toolbar/index.js';
2
+ /** Dense-mode (narrow / mobile toolbar) entry for the undo action. */
3
+ export declare const buildUndoDenseMenu: DenseMenuBuilder;
@@ -0,0 +1,12 @@
1
+ import { menu } from '../../affine-components/context-menu/index.js';
2
+ import { UndoIcon } from '@blocksuite/icons/lit';
3
+ /** Dense-mode (narrow / mobile toolbar) entry for the undo action. */
4
+ export const buildUndoDenseMenu = edgeless => menu.action({
5
+ name: 'Undo',
6
+ prefix: UndoIcon(),
7
+ select: () => {
8
+ const { store } = edgeless;
9
+ if (store.canUndo)
10
+ store.undo();
11
+ },
12
+ });
@@ -0,0 +1,18 @@
1
+ import { LitElement } from 'lit';
2
+ declare const EdgelessUndoToolButton_base: typeof LitElement & import("../../global/utils/types.js").Constructor<import("../../affine-widget-edgeless-toolbar/index.js").QuickToolMixinClass>;
3
+ /**
4
+ * Edgeless quick-tool "undo" button. It took over the slot of the former link
5
+ * quick tool (see `undo-tool.ts`). It is an action, not a tool mode, so `type`
6
+ * is empty (the button never renders as "active").
7
+ *
8
+ * ponytail: always clickable; the click is a no-op when there is nothing to
9
+ * undo. A reactive disabled state would need the history `canUndo$` signal,
10
+ * which `Store` does not expose today — not worth wiring for this button.
11
+ */
12
+ export declare class EdgelessUndoToolButton extends EdgelessUndoToolButton_base {
13
+ static styles: import("lit").CSSResult;
14
+ type: never[];
15
+ private _onClick;
16
+ render(): import("lit-html").TemplateResult<1>;
17
+ }
18
+ export {};
@@ -0,0 +1,41 @@
1
+ import { QuickToolMixin } from '../../affine-widget-edgeless-toolbar/index.js';
2
+ import { UndoIcon } from '@blocksuite/icons/lit';
3
+ import { css, html, LitElement } from 'lit';
4
+ /**
5
+ * Edgeless quick-tool "undo" button. It took over the slot of the former link
6
+ * quick tool (see `undo-tool.ts`). It is an action, not a tool mode, so `type`
7
+ * is empty (the button never renders as "active").
8
+ *
9
+ * ponytail: always clickable; the click is a no-op when there is nothing to
10
+ * undo. A reactive disabled state would need the history `canUndo$` signal,
11
+ * which `Store` does not expose today — not worth wiring for this button.
12
+ */
13
+ export class EdgelessUndoToolButton extends QuickToolMixin(LitElement) {
14
+ constructor() {
15
+ super(...arguments);
16
+ this.type = [];
17
+ }
18
+ static { this.styles = css `
19
+ .undo-icon,
20
+ .undo-icon > svg {
21
+ width: 24px;
22
+ height: 24px;
23
+ }
24
+ `; }
25
+ _onClick() {
26
+ const { store } = this.edgeless;
27
+ if (store.canUndo)
28
+ store.undo();
29
+ }
30
+ render() {
31
+ return html `<edgeless-tool-icon-button
32
+ .iconContainerPadding="${6}"
33
+ .tooltip="${'Undo'}"
34
+ .tooltipOffset=${17}
35
+ class="edgeless-undo-tool-button"
36
+ @click=${this._onClick}
37
+ >
38
+ <span class="undo-icon">${UndoIcon()}</span>
39
+ </edgeless-tool-icon-button>`;
40
+ }
41
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Undo quick tool. It replaces the former link quick tool in the edgeless
3
+ * toolbar (same slot, next to `frame`). Wired to the store undo command, the
4
+ * same one the Ctrl/Cmd-Z shortcut uses.
5
+ */
6
+ export declare const undoQuickTool: import("../store/index.js").ExtensionType;
@@ -0,0 +1,17 @@
1
+ import { QuickToolExtension } from '../affine-widget-edgeless-toolbar/index.js';
2
+ import { html } from 'lit';
3
+ import { buildUndoDenseMenu } from './toolbar/undo-dense-menu';
4
+ /**
5
+ * Undo quick tool. It replaces the former link quick tool in the edgeless
6
+ * toolbar (same slot, next to `frame`). Wired to the store undo command, the
7
+ * same one the Ctrl/Cmd-Z shortcut uses.
8
+ */
9
+ export const undoQuickTool = QuickToolExtension('undo', ({ block, gfx }) => {
10
+ return {
11
+ content: html `<edgeless-undo-tool-button
12
+ .edgeless=${block}
13
+ ></edgeless-undo-tool-button>`,
14
+ menu: buildUndoDenseMenu(block, gfx),
15
+ enable: !block.store.readonly,
16
+ };
17
+ });
@@ -1,6 +1,8 @@
1
1
  import { ViewExtensionProvider, } from '../affine-ext-loader/index.js';
2
2
  import { effects } from './effects';
3
- import { linkQuickTool } from './link-tool';
3
+ import { undoQuickTool } from './undo-tool';
4
+ // This gfx package historically shipped the edgeless "link" quick tool; it now
5
+ // ships the "undo" quick tool, which took over that toolbar slot (see #27).
4
6
  export class LinkViewExtension extends ViewExtensionProvider {
5
7
  constructor() {
6
8
  super(...arguments);
@@ -13,7 +15,7 @@ export class LinkViewExtension extends ViewExtensionProvider {
13
15
  setup(context) {
14
16
  super.setup(context);
15
17
  if (this.isEdgeless(context.scope)) {
16
- context.register(linkQuickTool);
18
+ context.register(undoQuickTool);
17
19
  }
18
20
  }
19
21
  }
@@ -77,6 +77,9 @@ let EdgelessMindmapToolButton = (() => {
77
77
  display: flex;
78
78
  justify-content: center;
79
79
  align-items: center;
80
+ /* Treat the whole button as a tap target (no double-tap zoom / tap delay
81
+ * on touch browsers like Firefox mobile). */
82
+ touch-action: manipulation;
80
83
  }
81
84
  .partial-clip {
82
85
  flex-shrink: 0;
@@ -104,8 +107,10 @@ let EdgelessMindmapToolButton = (() => {
104
107
  }
105
108
  .basket-tool-item svg {
106
109
  display: block;
107
- max-width: 46px;
108
- max-height: 46px;
110
+ /* Render near the icons' natural size (58x44 / 64x48) so the mindmap
111
+ * button reads at the same size as its neighbours (text / add-file). */
112
+ max-width: 64px;
113
+ max-height: 50px;
109
114
  width: auto;
110
115
  height: auto;
111
116
  }
@@ -202,6 +207,11 @@ let EdgelessMindmapToolButton = (() => {
202
207
  scopeElement: this.toolbarContainer,
203
208
  standardWidth: 100,
204
209
  clickToDrag: false,
210
+ // On touch, the drag controller calls preventDefault on touchstart, so the
211
+ // native click that opens the menu never fires (button is "dead" on
212
+ // mobile, or only its non-icon edge responds). A tap without drag fires
213
+ // onElementClick instead — wire it to the same toggle as the button click.
214
+ onElementClick: () => this._toggleMenu(),
205
215
  onOverlayCreated: (overlay, { data }) => {
206
216
  const tool = this.draggableTools.find(t => t.name === data.name);
207
217
  if (!tool)
@@ -347,6 +357,7 @@ let EdgelessMindmapToolButton = (() => {
347
357
  preview: tool.icon,
348
358
  standardWidth: tool.standardWidth,
349
359
  })}
360
+ @click=${(e) => e.stopPropagation()}
350
361
  class="basket-tool-item current ${tool.name}"
351
362
  >
352
363
  ${tool.icon}
@@ -1,3 +1,13 @@
1
1
  export declare const mindMapSeniorTool: import("../../store/index.js").ExtensionType;
2
- /** The "Others" button: free-text + add-file (mindmap moved to its own tool). */
3
- export declare const otherSeniorTool: import("../../store/index.js").ExtensionType;
2
+ /**
3
+ * Standalone "Text" tool. Promoted out of the former "Others" submenu: a single
4
+ * tap inserts an editable text element at the viewport center. Reuses the
5
+ * submenu's `textRender` action.
6
+ */
7
+ export declare const textSeniorTool: import("../../store/index.js").ExtensionType;
8
+ /**
9
+ * Standalone "Add file" tool. Promoted out of the former "Others" submenu: a
10
+ * single tap opens the file picker and inserts the image/attachment at the
11
+ * viewport center. Reuses the submenu's `mediaRender` action.
12
+ */
13
+ export declare const mediaSeniorTool: import("../../store/index.js").ExtensionType;
@@ -1,5 +1,8 @@
1
1
  import { SeniorToolExtension } from '../../affine-widget-edgeless-toolbar/index.js';
2
+ import { Bound } from '../../global/gfx/index.js';
2
3
  import { html } from 'lit';
4
+ import { mediaRender, textRender } from './basket-elements';
5
+ import { mindmapMenuMediaIcon, textIcon } from './icons';
3
6
  export const mindMapSeniorTool = SeniorToolExtension('mindMap', ({ block, toolbarContainer }) => {
4
7
  return {
5
8
  name: 'Mind Map',
@@ -10,14 +13,62 @@ export const mindMapSeniorTool = SeniorToolExtension('mindMap', ({ block, toolba
10
13
  ></edgeless-mindmap-tool-button>`,
11
14
  };
12
15
  });
13
- /** The "Others" button: free-text + add-file (mindmap moved to its own tool). */
14
- export const otherSeniorTool = SeniorToolExtension('other', ({ block, toolbarContainer }) => {
15
- return {
16
- name: 'Others',
17
- content: html `<edgeless-mindmap-tool-button
18
- .edgeless=${block}
19
- .toolbarContainer=${toolbarContainer}
20
- .variant=${'other'}
21
- ></edgeless-mindmap-tool-button>`,
22
- };
23
- });
16
+ // Reuse the (nicely drawn) icons from the former "Others" submenu, with a
17
+ // playful resting tilt that pops upright + scales on hover — echoing the old
18
+ // basket hover animation.
19
+ const slot = (icon, tooltip, onClick) => html `<style>
20
+ .promoted-tool {
21
+ width: 100%;
22
+ height: 100%;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ }
27
+ .promoted-tool .promoted-tool-icon {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ /* Scale the drawn icons up so they read at the same size as the
32
+ * neighbouring pen/shape icons (their natural SVG size is smaller). */
33
+ transform: rotate(-4deg) scale(1.4);
34
+ transition: transform 0.24s cubic-bezier(0.34, 1.56, 0.64, 1);
35
+ }
36
+ .promoted-tool:hover .promoted-tool-icon {
37
+ transform: rotate(0deg) scale(1.65);
38
+ }
39
+ .promoted-tool .promoted-tool-icon svg {
40
+ width: auto;
41
+ height: auto;
42
+ max-width: 52px;
43
+ max-height: 44px;
44
+ }
45
+ </style>
46
+ <div class="promoted-tool">
47
+ <edgeless-tool-icon-button .tooltip=${tooltip} @click=${onClick}>
48
+ <span class="promoted-tool-icon">${icon}</span>
49
+ </edgeless-tool-icon-button>
50
+ </div>`;
51
+ /**
52
+ * Standalone "Text" tool. Promoted out of the former "Others" submenu: a single
53
+ * tap inserts an editable text element at the viewport center. Reuses the
54
+ * submenu's `textRender` action.
55
+ */
56
+ export const textSeniorTool = SeniorToolExtension('edgeless-text', ({ block, gfx }) => ({
57
+ name: 'Text',
58
+ content: slot(textIcon, 'Text', () => {
59
+ const { centerX, centerY } = gfx.viewport;
60
+ void textRender(new Bound(centerX - 50, centerY - 16, 100, 32), block);
61
+ }),
62
+ }));
63
+ /**
64
+ * Standalone "Add file" tool. Promoted out of the former "Others" submenu: a
65
+ * single tap opens the file picker and inserts the image/attachment at the
66
+ * viewport center. Reuses the submenu's `mediaRender` action.
67
+ */
68
+ export const mediaSeniorTool = SeniorToolExtension('edgeless-media', ({ block, gfx }) => ({
69
+ name: 'Add file',
70
+ content: slot(mindmapMenuMediaIcon, 'Add file', () => {
71
+ const { centerX, centerY } = gfx.viewport;
72
+ void mediaRender(new Bound(centerX - 50, centerY - 50, 100, 100), block);
73
+ }),
74
+ }));
@@ -15,8 +15,19 @@ export declare class MindmapToolViewExtension extends ViewExtensionProvider {
15
15
  name: string;
16
16
  setup(context: ViewExtensionContext): void;
17
17
  }
18
- /** The "Others" senior button (free-text + add-file) — flag-gated (`other`). */
19
- export declare class OtherToolViewExtension extends ViewExtensionProvider {
18
+ /**
19
+ * Standalone "Text" senior button (formerly inside the "Others" submenu) —
20
+ * flag-gated (`edgeless-text`).
21
+ */
22
+ export declare class TextToolViewExtension extends ViewExtensionProvider {
23
+ name: string;
24
+ setup(context: ViewExtensionContext): void;
25
+ }
26
+ /**
27
+ * Standalone "Add file" senior button (formerly inside the "Others" submenu) —
28
+ * flag-gated (`edgeless-media`).
29
+ */
30
+ export declare class MediaToolViewExtension extends ViewExtensionProvider {
20
31
  name: string;
21
32
  setup(context: ViewExtensionContext): void;
22
33
  }
@@ -5,7 +5,7 @@ import { MindmapElementRendererExtension } from './element-renderer';
5
5
  import { MindMapIndicatorOverlay } from './indicator-overlay';
6
6
  import { MindMapDragExtension } from './interactivity';
7
7
  import { mindmapToolbarExtension, shapeMindmapToolbarExtension, } from './toolbar/config';
8
- import { mindMapSeniorTool, otherSeniorTool } from './toolbar/senior-tool';
8
+ import { mediaSeniorTool, mindMapSeniorTool, textSeniorTool, } from './toolbar/senior-tool';
9
9
  import { mindmapTemplateCategory } from './templates';
10
10
  import { MindMapInteraction, MindMapView } from './view/view';
11
11
  /**
@@ -48,16 +48,35 @@ export class MindmapToolViewExtension extends ViewExtensionProvider {
48
48
  }
49
49
  }
50
50
  }
51
- /** The "Others" senior button (free-text + add-file) — flag-gated (`other`). */
52
- export class OtherToolViewExtension extends ViewExtensionProvider {
51
+ /**
52
+ * Standalone "Text" senior button (formerly inside the "Others" submenu) —
53
+ * flag-gated (`edgeless-text`).
54
+ */
55
+ export class TextToolViewExtension extends ViewExtensionProvider {
56
+ constructor() {
57
+ super(...arguments);
58
+ this.name = 'affine-edgeless-text-tool-gfx';
59
+ }
60
+ setup(context) {
61
+ super.setup(context);
62
+ if (this.isEdgeless(context.scope)) {
63
+ context.register(textSeniorTool);
64
+ }
65
+ }
66
+ }
67
+ /**
68
+ * Standalone "Add file" senior button (formerly inside the "Others" submenu) —
69
+ * flag-gated (`edgeless-media`).
70
+ */
71
+ export class MediaToolViewExtension extends ViewExtensionProvider {
53
72
  constructor() {
54
73
  super(...arguments);
55
- this.name = 'affine-other-gfx';
74
+ this.name = 'affine-edgeless-media-tool-gfx';
56
75
  }
57
76
  setup(context) {
58
77
  super.setup(context);
59
78
  if (this.isEdgeless(context.scope)) {
60
- context.register(otherSeniorTool);
79
+ context.register(mediaSeniorTool);
61
80
  }
62
81
  }
63
82
  }
@@ -13,7 +13,9 @@ export * from './feature-flag-service';
13
13
  export * from './file-size-limit-service';
14
14
  export * from './font-loader';
15
15
  export * from './generate-url-service';
16
+ export * from './keymap-override-service';
16
17
  export * from './link-preview-service';
18
+ export * from './linked-doc-content-service';
17
19
  export * from './linked-doc-creation-service';
18
20
  export * from './native-clipboard-service';
19
21
  export * from './notification-service';
@@ -13,7 +13,9 @@ export * from './feature-flag-service';
13
13
  export * from './file-size-limit-service';
14
14
  export * from './font-loader';
15
15
  export * from './generate-url-service';
16
+ export * from './keymap-override-service';
16
17
  export * from './link-preview-service';
18
+ export * from './linked-doc-content-service';
17
19
  export * from './linked-doc-creation-service';
18
20
  export * from './native-clipboard-service';
19
21
  export * from './notification-service';
@@ -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
+ }