@formicoidea/labre-core 0.26.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/_pkgs/affine-block-database/data-source.d.ts +28 -40
  2. package/dist/_pkgs/affine-block-database/data-source.js +116 -178
  3. package/dist/_pkgs/affine-block-database/database-block.d.ts +10 -3
  4. package/dist/_pkgs/affine-block-database/database-block.js +110 -70
  5. package/dist/_pkgs/affine-block-database/external-data-source.d.ts +69 -0
  6. package/dist/_pkgs/affine-block-database/external-data-source.js +144 -0
  7. package/dist/_pkgs/affine-block-database/index.d.ts +1 -0
  8. package/dist/_pkgs/affine-block-database/index.js +1 -0
  9. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +6 -7
  10. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/components/embed-synced-doc-card.js +5 -5
  11. package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -1
  12. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/misc.d.ts +18 -0
  13. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.d.ts +18 -0
  14. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.js +104 -2
  15. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  16. package/dist/_pkgs/affine-block-root/index.js +1 -0
  17. package/dist/_pkgs/affine-block-root/keyboard/keyboard-manager.js +3 -21
  18. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.d.ts +8 -0
  19. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.js +48 -0
  20. package/dist/_pkgs/affine-block-root/view.js +6 -1
  21. package/dist/_pkgs/affine-gfx-link/effects.js +2 -2
  22. package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.d.ts +3 -0
  23. package/dist/_pkgs/affine-gfx-link/toolbar/undo-dense-menu.js +12 -0
  24. package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.d.ts +18 -0
  25. package/dist/_pkgs/affine-gfx-link/toolbar/undo-tool-button.js +41 -0
  26. package/dist/_pkgs/affine-gfx-link/undo-tool.d.ts +6 -0
  27. package/dist/_pkgs/affine-gfx-link/undo-tool.js +17 -0
  28. package/dist/_pkgs/affine-gfx-link/view.js +4 -2
  29. package/dist/_pkgs/affine-gfx-mindmap/toolbar/mindmap-tool-button.js +13 -2
  30. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.d.ts +12 -2
  31. package/dist/_pkgs/affine-gfx-mindmap/toolbar/senior-tool.js +62 -11
  32. package/dist/_pkgs/affine-gfx-mindmap/view.d.ts +13 -2
  33. package/dist/_pkgs/affine-gfx-mindmap/view.js +24 -5
  34. package/dist/_pkgs/affine-shared/services/index.d.ts +2 -0
  35. package/dist/_pkgs/affine-shared/services/index.js +2 -0
  36. package/dist/_pkgs/affine-shared/services/keymap-override-service.d.ts +15 -0
  37. package/dist/_pkgs/affine-shared/services/keymap-override-service.js +26 -0
  38. package/dist/_pkgs/affine-shared/services/linked-doc-content-service.d.ts +42 -0
  39. package/dist/_pkgs/affine-shared/services/linked-doc-content-service.js +47 -0
  40. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-element-link.d.ts +29 -0
  41. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-element-link.js +233 -0
  42. package/dist/_pkgs/affine-widget-edgeless-selected-rect/effects.js +2 -0
  43. package/dist/_pkgs/affine-widget-edgeless-selected-rect/spec.d.ts +1 -0
  44. package/dist/_pkgs/affine-widget-edgeless-selected-rect/spec.js +2 -0
  45. package/dist/_pkgs/affine-widget-edgeless-selected-rect/view.js +2 -1
  46. package/dist/_pkgs/data-view/core/data-source/base.d.ts +6 -0
  47. package/dist/_pkgs/data-view/core/data-source/base.js +6 -0
  48. package/dist/_pkgs/std/extension/index.d.ts +1 -0
  49. package/dist/_pkgs/std/extension/index.js +1 -0
  50. package/dist/_pkgs/std/extension/shortcut.d.ts +69 -0
  51. package/dist/_pkgs/std/extension/shortcut.js +106 -0
  52. package/dist/_pkgs/std/gfx/model/surface/element-model.d.ts +7 -0
  53. package/dist/_pkgs/std/gfx/model/surface/element-model.js +24 -1
  54. package/dist/_pkgs/std/identifier.d.ts +7 -0
  55. package/dist/_pkgs/std/identifier.js +6 -0
  56. package/dist/_pkgs/std/inline/components/v-element.js +5 -2
  57. package/dist/extensions/view.js +5 -2
  58. package/dist/flags.d.ts +1 -1
  59. package/dist/flags.js +4 -1
  60. package/dist/shortcuts.d.ts +24 -0
  61. package/dist/shortcuts.js +39 -0
  62. package/package.json +5 -1
  63. package/dist/_pkgs/affine-gfx-link/link-tool.d.ts +0 -1
  64. package/dist/_pkgs/affine-gfx-link/link-tool.js +0 -11
  65. package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.d.ts +0 -2
  66. package/dist/_pkgs/affine-gfx-link/toolbar/link-dense-menu.js +0 -26
  67. package/dist/_pkgs/affine-gfx-link/toolbar/link-tool-button.d.ts +0 -10
  68. package/dist/_pkgs/affine-gfx-link/toolbar/link-tool-button.js +0 -84
@@ -6,10 +6,11 @@ import { ImageBlockComponent } from '../../../../affine-block-image/index.js';
6
6
  import { EdgelessCRUDIdentifier, getSurfaceComponent, } from '../../../../affine-block-surface/index.js';
7
7
  import { createGroupFromSelectedCommand } from '../../../../affine-gfx-group/index.js';
8
8
  import { AttachmentBlockModel, BookmarkBlockModel, EmbedLinkedDocBlockSchema, EmbedLinkedDocModel, EmbedSyncedDocBlockSchema, EmbedSyncedDocModel, FrameBlockModel, ImageBlockModel, isExternalEmbedModel, NoteBlockModel, ParagraphBlockModel, } from '../../../../affine-model/index.js';
9
+ import { QuickSearchProvider } from '../../../../affine-shared/services/index.js';
9
10
  import { matchModels, } from '../../../../affine-shared/utils/index.js';
10
11
  import { Bound, getCommonBoundWithRotation } from '../../../../global/gfx/index.js';
11
- import { ArrowDownBigBottomIcon, ArrowDownBigIcon, ArrowUpBigIcon, ArrowUpBigTopIcon, CopyIcon, DeleteIcon, DuplicateIcon, FrameIcon, GroupIcon, LinkedPageIcon, ResetIcon, } from '@blocksuite/icons/lit';
12
- import { GfxBlockElementModel } from '../../../../std/gfx/index.js';
12
+ import { ArrowDownBigBottomIcon, ArrowDownBigIcon, ArrowUpBigIcon, ArrowUpBigTopIcon, CopyIcon, DeleteIcon, DuplicateIcon, FrameIcon, GroupIcon, LinkIcon, LinkedPageIcon, ResetIcon, UnlinkIcon, } from '@blocksuite/icons/lit';
13
+ import { GfxBlockElementModel, GfxPrimitiveElementModel, } from '../../../../std/gfx/index.js';
13
14
  import { EdgelessClipboardController } from '../../clipboard/clipboard';
14
15
  import { duplicate } from '../../utils/clipboard-utils';
15
16
  import { getSortedCloneElements } from '../../utils/clone-utils';
@@ -298,6 +299,54 @@ export const moreActions = [
298
299
  create().catch(console.error);
299
300
  },
300
301
  },
302
+ {
303
+ id: 'c.link-element',
304
+ // Attach a link (existing doc or external URL) to a single drawing
305
+ // element. A hover arrow (edgeless-element-link widget) opens it.
306
+ label: 'Link',
307
+ icon: LinkIcon(),
308
+ when(ctx) {
309
+ const el = getLinkableElement(ctx);
310
+ return el !== null && !hasElementLink(el) && canPickLink(ctx);
311
+ },
312
+ run(ctx) {
313
+ const el = getLinkableElement(ctx);
314
+ if (el)
315
+ linkElementViaQuickSearch(ctx, el);
316
+ },
317
+ },
318
+ {
319
+ id: 'c.edit-element-link',
320
+ label: 'Edit link',
321
+ icon: LinkIcon(),
322
+ when(ctx) {
323
+ const el = getLinkableElement(ctx);
324
+ return el !== null && hasElementLink(el) && canPickLink(ctx);
325
+ },
326
+ run(ctx) {
327
+ const el = getLinkableElement(ctx);
328
+ if (el)
329
+ linkElementViaQuickSearch(ctx, el);
330
+ },
331
+ },
332
+ {
333
+ id: 'c.remove-element-link',
334
+ label: 'Remove link',
335
+ icon: UnlinkIcon(),
336
+ when(ctx) {
337
+ const el = getLinkableElement(ctx);
338
+ return el !== null && hasElementLink(el);
339
+ },
340
+ run(ctx) {
341
+ const el = getLinkableElement(ctx);
342
+ if (!el)
343
+ return;
344
+ ctx.std.get(EdgelessCRUDIdentifier).updateElement(el.id, {
345
+ linkedDocId: undefined,
346
+ externalLink: undefined,
347
+ });
348
+ },
349
+ },
301
350
  ],
302
351
  },
303
352
  // Deleting Group
@@ -321,6 +370,59 @@ export const moreActions = [
321
370
  },
322
371
  },
323
372
  ];
373
+ /** The single selected drawing element a link can be attached to, or null. */
374
+ function getLinkableElement(ctx) {
375
+ const models = ctx.getSurfaceModels();
376
+ return models.length === 1 && models[0] instanceof GfxPrimitiveElementModel
377
+ ? models[0]
378
+ : null;
379
+ }
380
+ function hasElementLink(el) {
381
+ return Boolean(el.linkedDocId || el.externalLink);
382
+ }
383
+ /**
384
+ * Picking a doc/URL needs the host-provided quick-search modal. Hide the
385
+ * Link / Edit link items where it is absent (they would no-op); Remove link
386
+ * stays available since clearing needs no picker.
387
+ */
388
+ function canPickLink(ctx) {
389
+ return Boolean(ctx.std.getOptional(QuickSearchProvider));
390
+ }
391
+ /** Pick a doc or URL via the quick-search modal and store it on the element. */
392
+ function linkElementViaQuickSearch(ctx, el) {
393
+ const quickSearch = ctx.std.getOptional(QuickSearchProvider);
394
+ if (!quickSearch)
395
+ return;
396
+ quickSearch
397
+ .openQuickSearch()
398
+ .then(result => {
399
+ if (!result)
400
+ return;
401
+ const crud = ctx.std.get(EdgelessCRUDIdentifier);
402
+ if ('docId' in result) {
403
+ crud.updateElement(el.id, {
404
+ linkedDocId: result.docId,
405
+ externalLink: undefined,
406
+ });
407
+ ctx.track('LinkedDocCreated', {
408
+ control: 'link element',
409
+ type: 'element-ref',
410
+ other: 'existing doc',
411
+ });
412
+ }
413
+ else if ('externalUrl' in result) {
414
+ crud.updateElement(el.id, {
415
+ externalLink: result.externalUrl,
416
+ linkedDocId: undefined,
417
+ });
418
+ ctx.track('Link', {
419
+ control: 'link element',
420
+ type: 'external url',
421
+ });
422
+ }
423
+ })
424
+ .catch(console.error);
425
+ }
324
426
  function reorderElements(ctx, models, type) {
325
427
  if (!models.length)
326
428
  return;
@@ -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';