@wonderwhy-er/desktop-commander 0.2.39 → 0.2.40

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 (137) hide show
  1. package/dist/server.js +1 -1
  2. package/dist/ui/file-preview/preview-runtime.js +204 -153
  3. package/dist/ui/file-preview/src/markdown/controller.d.ts +7 -1
  4. package/dist/ui/file-preview/src/markdown/controller.js +135 -16
  5. package/dist/ui/file-preview/src/markdown/editor.d.ts +97 -1
  6. package/dist/ui/file-preview/src/markdown/editor.js +814 -26
  7. package/dist/ui/file-preview/src/model.d.ts +2 -1
  8. package/dist/utils/capture.js +1 -1
  9. package/dist/utils/toolHistory.d.ts +13 -0
  10. package/dist/utils/toolHistory.js +65 -0
  11. package/dist/version.d.ts +1 -1
  12. package/dist/version.js +1 -1
  13. package/package.json +7 -1
  14. package/dist/ui/config-editor/app.js +0 -840
  15. package/dist/ui/config-editor/array-modal.d.ts +0 -19
  16. package/dist/ui/config-editor/array-modal.js +0 -185
  17. package/dist/ui/config-editor/main.d.ts +0 -1
  18. package/dist/ui/config-editor/main.js +0 -2
  19. package/dist/ui/config-editor/src/App.d.ts +0 -43
  20. package/dist/ui/config-editor/src/components/layout.d.ts +0 -4
  21. package/dist/ui/config-editor/src/components/layout.js +0 -83
  22. package/dist/ui/config-editor/src/components/toolbar.d.ts +0 -1
  23. package/dist/ui/config-editor/src/components/toolbar.js +0 -21
  24. package/dist/ui/config-editor/src/config-values.d.ts +0 -6
  25. package/dist/ui/config-editor/src/config-values.js +0 -61
  26. package/dist/ui/config-editor/src/contracts.d.ts +0 -14
  27. package/dist/ui/config-editor/src/contracts.js +0 -3
  28. package/dist/ui/config-editor/src/directory-browser.d.ts +0 -6
  29. package/dist/ui/config-editor/src/directory-browser.js +0 -71
  30. package/dist/ui/config-editor/src/layout.d.ts +0 -5
  31. package/dist/ui/config-editor/src/layout.js +0 -90
  32. package/dist/ui/config-editor/src/parsing.d.ts +0 -5
  33. package/dist/ui/config-editor/src/parsing.js +0 -50
  34. package/dist/ui/config-editor/src/toolbar.d.ts +0 -1
  35. package/dist/ui/config-editor/src/toolbar.js +0 -18
  36. package/dist/ui/config-editor/src/types.d.ts +0 -17
  37. package/dist/ui/config-editor/src/types.js +0 -3
  38. package/dist/ui/config-editor/src/utils/config-values.d.ts +0 -9
  39. package/dist/ui/config-editor/src/utils/config-values.js +0 -61
  40. package/dist/ui/config-editor/src/utils/directory-browser.d.ts +0 -31
  41. package/dist/ui/config-editor/src/utils/directory-browser.js +0 -201
  42. package/dist/ui/config-editor/src/utils/parsing.d.ts +0 -8
  43. package/dist/ui/config-editor/src/utils/parsing.js +0 -50
  44. package/dist/ui/file-preview/app.d.ts +0 -8
  45. package/dist/ui/file-preview/app.js +0 -2020
  46. package/dist/ui/file-preview/components/code-viewer.d.ts +0 -6
  47. package/dist/ui/file-preview/components/code-viewer.js +0 -73
  48. package/dist/ui/file-preview/components/highlighting.d.ts +0 -2
  49. package/dist/ui/file-preview/components/highlighting.js +0 -54
  50. package/dist/ui/file-preview/components/html-renderer.d.ts +0 -5
  51. package/dist/ui/file-preview/components/html-renderer.js +0 -47
  52. package/dist/ui/file-preview/components/markdown-renderer.d.ts +0 -1
  53. package/dist/ui/file-preview/components/markdown-renderer.js +0 -67
  54. package/dist/ui/file-preview/components/toolbar.d.ts +0 -6
  55. package/dist/ui/file-preview/components/toolbar.js +0 -75
  56. package/dist/ui/file-preview/image-preview.d.ts +0 -3
  57. package/dist/ui/file-preview/image-preview.js +0 -21
  58. package/dist/ui/file-preview/main.d.ts +0 -1
  59. package/dist/ui/file-preview/main.js +0 -5
  60. package/dist/ui/file-preview/markdown/editor.d.ts +0 -36
  61. package/dist/ui/file-preview/markdown/editor.js +0 -643
  62. package/dist/ui/file-preview/markdown/linking.d.ts +0 -9
  63. package/dist/ui/file-preview/markdown/linking.js +0 -210
  64. package/dist/ui/file-preview/markdown/outline.d.ts +0 -7
  65. package/dist/ui/file-preview/markdown/outline.js +0 -40
  66. package/dist/ui/file-preview/markdown/preview.d.ts +0 -8
  67. package/dist/ui/file-preview/markdown/preview.js +0 -33
  68. package/dist/ui/file-preview/markdown/slugify.d.ts +0 -3
  69. package/dist/ui/file-preview/markdown/slugify.js +0 -31
  70. package/dist/ui/file-preview/markdown/toc.d.ts +0 -11
  71. package/dist/ui/file-preview/markdown/toc.js +0 -75
  72. package/dist/ui/file-preview/markdown/utils.d.ts +0 -1
  73. package/dist/ui/file-preview/markdown/utils.js +0 -15
  74. package/dist/ui/file-preview/markdown/workspace-controller.d.ts +0 -25
  75. package/dist/ui/file-preview/markdown/workspace-controller.js +0 -40
  76. package/dist/ui/file-preview/src/components/CodeViewer.d.ts +0 -6
  77. package/dist/ui/file-preview/src/components/CodeViewer.js +0 -60
  78. package/dist/ui/file-preview/src/components/HtmlRenderer.d.ts +0 -8
  79. package/dist/ui/file-preview/src/components/HtmlRenderer.js +0 -45
  80. package/dist/ui/file-preview/src/components/MarkdownRenderer.d.ts +0 -1
  81. package/dist/ui/file-preview/src/components/MarkdownRenderer.js +0 -15
  82. package/dist/ui/file-preview/src/components/Toolbar.d.ts +0 -6
  83. package/dist/ui/file-preview/src/components/Toolbar.js +0 -75
  84. package/dist/ui/file-preview/src/components/editor-toolbar.d.ts +0 -15
  85. package/dist/ui/file-preview/src/components/editor-toolbar.js +0 -384
  86. package/dist/ui/file-preview/src/components/markdown-editor.d.ts +0 -29
  87. package/dist/ui/file-preview/src/components/markdown-editor.js +0 -535
  88. package/dist/ui/file-preview/src/markdown/block-merge.d.ts +0 -25
  89. package/dist/ui/file-preview/src/markdown/block-merge.js +0 -86
  90. package/dist/ui/file-preview/src/markdown/link-modal.d.ts +0 -13
  91. package/dist/ui/file-preview/src/markdown/link-modal.js +0 -213
  92. package/dist/ui/file-preview/src/markdown/raw-editor.d.ts +0 -8
  93. package/dist/ui/file-preview/src/markdown/raw-editor.js +0 -61
  94. package/dist/ui/file-preview/src/markdown/selection-toolbar.d.ts +0 -14
  95. package/dist/ui/file-preview/src/markdown/selection-toolbar.js +0 -128
  96. package/dist/ui/file-preview/src/markdown/toc.d.ts +0 -11
  97. package/dist/ui/file-preview/src/markdown/toc.js +0 -75
  98. package/dist/ui/file-preview/src/markdown-workspace/editor.d.ts +0 -36
  99. package/dist/ui/file-preview/src/markdown-workspace/editor.js +0 -643
  100. package/dist/ui/file-preview/src/markdown-workspace/linking.d.ts +0 -9
  101. package/dist/ui/file-preview/src/markdown-workspace/linking.js +0 -210
  102. package/dist/ui/file-preview/src/markdown-workspace/outline.d.ts +0 -7
  103. package/dist/ui/file-preview/src/markdown-workspace/outline.js +0 -40
  104. package/dist/ui/file-preview/src/markdown-workspace/preview.d.ts +0 -8
  105. package/dist/ui/file-preview/src/markdown-workspace/preview.js +0 -33
  106. package/dist/ui/file-preview/src/markdown-workspace/slugify.d.ts +0 -3
  107. package/dist/ui/file-preview/src/markdown-workspace/slugify.js +0 -31
  108. package/dist/ui/file-preview/src/markdown-workspace/toc.d.ts +0 -11
  109. package/dist/ui/file-preview/src/markdown-workspace/toc.js +0 -75
  110. package/dist/ui/file-preview/src/markdown-workspace/utils.d.ts +0 -1
  111. package/dist/ui/file-preview/src/markdown-workspace/utils.js +0 -15
  112. package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.d.ts +0 -25
  113. package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.js +0 -40
  114. package/dist/ui/file-preview/types.d.ts +0 -1
  115. package/dist/ui/file-preview/types.js +0 -1
  116. package/dist/ui/server-integration.d.ts +0 -13
  117. package/dist/ui/server-integration.js +0 -31
  118. package/dist/ui/shared/ToolHeader.d.ts +0 -9
  119. package/dist/ui/shared/ToolHeader.js +0 -29
  120. package/dist/ui/shared/app-bootstrap.d.ts +0 -9
  121. package/dist/ui/shared/app-bootstrap.js +0 -15
  122. package/dist/ui/shared/guards.d.ts +0 -1
  123. package/dist/ui/shared/guards.js +0 -3
  124. package/dist/ui/shared/host-lifecycle.d.ts +0 -17
  125. package/dist/ui/shared/host-lifecycle.js +0 -41
  126. package/dist/ui/shared/rpc-client.d.ts +0 -14
  127. package/dist/ui/shared/rpc-client.js +0 -72
  128. package/dist/ui/shared/theme-adaptation.d.ts +0 -10
  129. package/dist/ui/shared/theme-adaptation.js +0 -118
  130. package/dist/ui/shared/tool-header.d.ts +0 -9
  131. package/dist/ui/shared/tool-header.js +0 -25
  132. package/dist/utils/ui-call-context.d.ts +0 -8
  133. package/dist/utils/ui-call-context.js +0 -72
  134. /package/dist/ui/config-editor/{app.d.ts → src/app.d.ts} +0 -0
  135. /package/dist/ui/config-editor/src/{App.js → app.js} +0 -0
  136. /package/dist/ui/file-preview/src/{App.d.ts → app.d.ts} +0 -0
  137. /package/dist/ui/file-preview/src/{App.js → app.js} +0 -0
@@ -1,75 +0,0 @@
1
- import { renderToolHeader } from '../../../shared/tool-header.js';
2
- function inferFilePill(payload) {
3
- if (payload.fileType === 'markdown') {
4
- return { label: 'MD', className: 'file-pill--md' };
5
- }
6
- if (payload.fileType === 'html') {
7
- return { label: 'HTML', className: 'file-pill--html' };
8
- }
9
- const extensionMatch = payload.filePath.toLowerCase().match(/\.([a-z0-9]+)$/);
10
- const extension = extensionMatch ? extensionMatch[1] : 'txt';
11
- if (extension === 'json') {
12
- return { label: 'JSON', className: 'file-pill--json' };
13
- }
14
- return { label: extension.slice(0, 4).toUpperCase(), className: 'file-pill--text' };
15
- }
16
- export function renderToolbar(payload, canCopy, htmlMode, isExpanded, canOpenInFolder) {
17
- const supportsPreview = payload.fileType !== 'unsupported';
18
- const copyDisabled = canCopy ? '' : 'disabled';
19
- const copyTitle = canCopy ? 'Copy source' : 'Copy unavailable';
20
- const copyIcon = `
21
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
22
- <path d="M8 8h10v12H8z"></path>
23
- <path d="M6 4h10v2H8v10H6z"></path>
24
- </svg>
25
- `;
26
- const folderDisabled = canOpenInFolder ? '' : 'disabled';
27
- const folderTitle = canOpenInFolder ? 'Open in folder' : 'Open in folder unavailable';
28
- const folderIcon = `
29
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
30
- <path d="M10 4l2 2h8v12H4V4z"></path>
31
- </svg>
32
- `;
33
- const previewIcon = isExpanded
34
- ? `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M7 14l5-5 5 5z"></path></svg>`
35
- : `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M7 10l5 5 5-5z"></path></svg>`;
36
- const htmlModeButton = payload.fileType === 'html'
37
- ? `
38
- <button class="icon-button icon-button--secondary" id="toggle-html-mode" title="${htmlMode === 'rendered' ? 'Show source' : 'Show rendered'}" aria-label="${htmlMode === 'rendered' ? 'Show source' : 'Show rendered'}">
39
- ${htmlMode === 'rendered'
40
- ? `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5c5.2 0 9.3 4.4 10.6 6-1.3 1.6-5.4 6-10.6 6S2.7 12.6 1.4 11C2.7 9.4 6.8 5 12 5zm0 2.2A3.8 3.8 0 1 0 12 14.8a3.8 3.8 0 0 0 0-7.6z"></path></svg>`
41
- : `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6 2 12l6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"></path></svg>`}
42
- </button>
43
- `
44
- : '';
45
- const filePill = inferFilePill(payload);
46
- const leadingActions = supportsPreview
47
- ? `
48
- <button class="icon-button" id="toggle-expand" title="${isExpanded ? 'Hide preview' : 'Show preview'}" aria-label="${isExpanded ? 'Hide preview' : 'Show preview'}">
49
- ${previewIcon}
50
- </button>
51
- `
52
- : '';
53
- const trailingActions = supportsPreview
54
- ? `
55
- ${htmlModeButton}
56
- <button class="icon-button" id="copy-source" ${copyDisabled} title="${copyTitle}" aria-label="${copyTitle}">
57
- ${copyIcon}
58
- </button>
59
- `
60
- : '';
61
- return renderToolHeader({
62
- pillLabel: filePill.label,
63
- pillClassName: filePill.className,
64
- title: payload.fileName,
65
- subtitle: payload.filePath,
66
- badges: [],
67
- actionsHtml: `
68
- ${leadingActions}
69
- <button class="icon-button" id="open-in-folder" ${folderDisabled} title="${folderTitle}" aria-label="${folderTitle}">
70
- ${folderIcon}
71
- </button>
72
- ${trailingActions}
73
- `
74
- });
75
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Floating formatting toolbar for the WYSIWYG markdown editor. Appears above
3
- * selected text and provides inline formatting: bold, italic, strikethrough,
4
- * code, link, text color, and font size.
5
- */
6
- export interface EditorToolbar {
7
- element: HTMLElement;
8
- show: (anchorRect: DOMRect, containerRect: DOMRect) => void;
9
- hide: () => void;
10
- destroy: () => void;
11
- readonly isVisible: boolean;
12
- readonly hasOpenDropdown: boolean;
13
- }
14
- export declare function createEditorToolbar(onFormatApplied?: () => void): EditorToolbar;
15
- export declare function handleFormatKeydown(e: KeyboardEvent): boolean;
@@ -1,384 +0,0 @@
1
- /**
2
- * Floating formatting toolbar for the WYSIWYG markdown editor. Appears above
3
- * selected text and provides inline formatting: bold, italic, strikethrough,
4
- * code, link, text color, and font size.
5
- */
6
- // ---------------------------------------------------------------------------
7
- // Preset palettes
8
- // ---------------------------------------------------------------------------
9
- const COLORS = [
10
- { label: 'Default', value: '' },
11
- { label: 'Red', value: '#ef4444' },
12
- { label: 'Orange', value: '#f97316' },
13
- { label: 'Amber', value: '#f59e0b' },
14
- { label: 'Green', value: '#22c55e' },
15
- { label: 'Blue', value: '#3b82f6' },
16
- { label: 'Purple', value: '#a855f7' },
17
- { label: 'Pink', value: '#ec4899' },
18
- { label: 'Gray', value: '#6b7280' },
19
- ];
20
- const FONT_SIZES = [
21
- { label: 'Small', value: '0.85em' },
22
- { label: 'Normal', value: '' },
23
- { label: 'Large', value: '1.25em' },
24
- { label: 'X-Large', value: '1.5em' },
25
- ];
26
- // ---------------------------------------------------------------------------
27
- // SVG icons (inline, 16×16, matching project style)
28
- // ---------------------------------------------------------------------------
29
- const ICONS = {
30
- bold: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg>`,
31
- italic: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>`,
32
- strike: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17.3 4.9c-1.2-.9-2.7-1.4-4.3-1.4-3.3 0-5 2-5 4 0 .8.2 1.5.5 2"/><path d="M5 12h14"/><path d="M6.7 19.1c1.2.9 2.7 1.4 4.3 1.4 3.3 0 5-2 5-4 0-.8-.2-1.5-.5-2"/></svg>`,
33
- code: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,
34
- link: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`,
35
- color: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l5.5 14H6.5z"/><line x1="4" y1="22" x2="20" y2="22" stroke-width="3"/></svg>`,
36
- fontSize: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"/><path d="M12 4v16"/><path d="M8 20h8"/></svg>`,
37
- chevron: `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`,
38
- };
39
- // ---------------------------------------------------------------------------
40
- // Selection save/restore (needed before opening dropdowns)
41
- // ---------------------------------------------------------------------------
42
- let savedRange = null;
43
- function saveSelection() {
44
- const sel = document.getSelection();
45
- if (sel && sel.rangeCount > 0 && !sel.isCollapsed) {
46
- savedRange = sel.getRangeAt(0).cloneRange();
47
- }
48
- }
49
- function restoreSelection() {
50
- if (!savedRange)
51
- return false;
52
- const sel = document.getSelection();
53
- if (!sel)
54
- return false;
55
- sel.removeAllRanges();
56
- sel.addRange(savedRange);
57
- return !sel.isCollapsed;
58
- }
59
- // ---------------------------------------------------------------------------
60
- // Format helpers
61
- // ---------------------------------------------------------------------------
62
- /**
63
- * After a format command the browser typically collapses the selection.
64
- * Force-restore the pre-command range so the user can chain operations.
65
- */
66
- function forceReselect() {
67
- // The original savedRange still points at the right text — the browser
68
- // mutated the DOM in-place (bold wraps in <b>, etc.) but the range
69
- // endpoints stay valid. Re-add it.
70
- restoreSelection();
71
- }
72
- function execFormat(command, value) {
73
- if (!restoreSelection())
74
- return;
75
- document.execCommand(command, false, value);
76
- forceReselect();
77
- }
78
- function wrapSelectionHtml(tag, attrs = '') {
79
- if (!restoreSelection())
80
- return;
81
- const sel = document.getSelection();
82
- if (!sel || sel.isCollapsed)
83
- return;
84
- const selectedText = sel.toString();
85
- const html = `<${tag}${attrs ? ' ' + attrs : ''}>${escapeForInsert(selectedText)}</${tag}>`;
86
- document.execCommand('insertHTML', false, html);
87
- forceReselect();
88
- }
89
- function escapeForInsert(text) {
90
- return text
91
- .replace(/&/g, '&amp;')
92
- .replace(/</g, '&lt;')
93
- .replace(/>/g, '&gt;');
94
- }
95
- function applyColor(color) {
96
- if (!color) {
97
- execFormat('removeFormat');
98
- return;
99
- }
100
- if (!restoreSelection())
101
- return;
102
- document.execCommand('foreColor', false, color);
103
- forceReselect();
104
- }
105
- function applyFontSize(size) {
106
- if (!size) {
107
- execFormat('removeFormat');
108
- return;
109
- }
110
- if (!restoreSelection())
111
- return;
112
- document.execCommand('fontSize', false, '7');
113
- // Find the <font size="7"> the browser just created and swap it for a span
114
- const editableRoot = document.getSelection()?.anchorNode?.parentElement?.closest('[contenteditable="true"]');
115
- if (editableRoot) {
116
- const fonts = editableRoot.querySelectorAll('font[size="7"]');
117
- fonts.forEach(font => {
118
- const span = document.createElement('span');
119
- span.style.fontSize = size;
120
- span.innerHTML = font.innerHTML;
121
- font.replaceWith(span);
122
- });
123
- }
124
- forceReselect();
125
- }
126
- function applyCode() {
127
- wrapSelectionHtml('code');
128
- }
129
- /**
130
- * Link insertion via inline input (prompt() is blocked in sandboxed iframes).
131
- * The `linkInputContainer` ref is set by the factory below after the toolbar
132
- * DOM is built.
133
- */
134
- let linkInputContainer = null;
135
- let linkApplyCallback = null;
136
- function applyLink() {
137
- if (!linkInputContainer)
138
- return;
139
- saveSelection();
140
- linkInputContainer.classList.add('md-tb-link-input--visible');
141
- const input = linkInputContainer.querySelector('input');
142
- if (input) {
143
- input.value = 'https://';
144
- input.focus();
145
- input.select();
146
- }
147
- linkApplyCallback = (url) => {
148
- if (!url)
149
- return;
150
- restoreSelection();
151
- document.execCommand('createLink', false, url);
152
- forceReselect();
153
- };
154
- }
155
- // ---------------------------------------------------------------------------
156
- // Toolbar DOM construction
157
- // ---------------------------------------------------------------------------
158
- function btn(icon, title, className = '') {
159
- return `<button class="md-tb-btn ${className}" title="${title}" aria-label="${title}" type="button" tabindex="-1">${icon}</button>`;
160
- }
161
- function buildToolbarHTML() {
162
- // Color swatches
163
- const colorSwatches = COLORS.map(c => `<button class="md-tb-swatch${c.value === '' ? ' md-tb-swatch--default' : ''}" data-color="${c.value}" title="${c.label}" style="${c.value ? `background:${c.value}` : ''}" type="button" tabindex="-1">${c.value === '' ? '&times;' : ''}</button>`).join('');
164
- // Font size options
165
- const sizeOptions = FONT_SIZES.map(s => `<button class="md-tb-size-opt${s.value === '' ? ' md-tb-size-opt--active' : ''}" data-size="${s.value}" type="button" tabindex="-1">${s.label}</button>`).join('');
166
- return `
167
- <div class="md-tb-group">
168
- ${btn(ICONS.bold, 'Bold (Ctrl+B)', 'md-tb-bold')}
169
- ${btn(ICONS.italic, 'Italic (Ctrl+I)', 'md-tb-italic')}
170
- ${btn(ICONS.strike, 'Strikethrough', 'md-tb-strike')}
171
- ${btn(ICONS.code, 'Inline code', 'md-tb-code')}
172
- ${btn(ICONS.link, 'Insert link (Ctrl+K)', 'md-tb-link')}
173
- </div>
174
- <div class="md-tb-sep"></div>
175
- <div class="md-tb-group">
176
- <div class="md-tb-dropdown">
177
- <button class="md-tb-btn md-tb-color-trigger" title="Text color" aria-label="Text color" type="button" tabindex="-1">
178
- ${ICONS.color}${ICONS.chevron}
179
- </button>
180
- <div class="md-tb-dropdown-panel md-tb-color-panel">${colorSwatches}</div>
181
- </div>
182
- <div class="md-tb-dropdown">
183
- <button class="md-tb-btn md-tb-size-trigger" title="Font size" aria-label="Font size" type="button" tabindex="-1">
184
- ${ICONS.fontSize}${ICONS.chevron}
185
- </button>
186
- <div class="md-tb-dropdown-panel md-tb-size-panel">${sizeOptions}</div>
187
- </div>
188
- </div>
189
- <div class="md-tb-link-input">
190
- <input type="text" class="md-tb-link-url" placeholder="https://" spellcheck="false" />
191
- <button class="md-tb-link-apply" type="button" tabindex="-1">Go</button>
192
- </div>
193
- `;
194
- }
195
- export function createEditorToolbar(onFormatApplied) {
196
- const el = document.createElement('div');
197
- el.className = 'md-toolbar';
198
- el.setAttribute('role', 'toolbar');
199
- el.innerHTML = buildToolbarHTML();
200
- // Wire up inline link input
201
- linkInputContainer = el.querySelector('.md-tb-link-input');
202
- const linkInput = el.querySelector('.md-tb-link-url');
203
- const linkApplyBtn = el.querySelector('.md-tb-link-apply');
204
- function commitLink() {
205
- const url = linkInput?.value.trim();
206
- linkInputContainer?.classList.remove('md-tb-link-input--visible');
207
- if (url && linkApplyCallback) {
208
- linkApplyCallback(url);
209
- linkApplyCallback = null;
210
- }
211
- }
212
- function cancelLink() {
213
- linkInputContainer?.classList.remove('md-tb-link-input--visible');
214
- linkApplyCallback = null;
215
- restoreSelection();
216
- }
217
- linkApplyBtn?.addEventListener('click', commitLink);
218
- linkInput?.addEventListener('keydown', (e) => {
219
- if (e.key === 'Enter') {
220
- e.preventDefault();
221
- commitLink();
222
- }
223
- if (e.key === 'Escape') {
224
- e.preventDefault();
225
- cancelLink();
226
- }
227
- });
228
- let visible = false;
229
- let openDropdown = null;
230
- // --- Positioning ---
231
- function show(anchorRect, containerRect) {
232
- if (!el.parentElement)
233
- return;
234
- visible = true;
235
- el.classList.add('md-toolbar--visible');
236
- // Measure toolbar
237
- const tbWidth = el.offsetWidth || 320;
238
- const tbHeight = el.offsetHeight || 40;
239
- // Center above selection
240
- let left = anchorRect.left + anchorRect.width / 2 - tbWidth / 2 - containerRect.left;
241
- let top = anchorRect.top - tbHeight - 8 - containerRect.top + (el.parentElement?.scrollTop ?? 0);
242
- // Clamp horizontal
243
- left = Math.max(4, Math.min(left, containerRect.width - tbWidth - 4));
244
- // If not enough space above, show below
245
- if (anchorRect.top - containerRect.top < tbHeight + 12) {
246
- top = anchorRect.bottom + 8 - containerRect.top + (el.parentElement?.scrollTop ?? 0);
247
- }
248
- el.style.left = `${left}px`;
249
- el.style.top = `${top}px`;
250
- }
251
- function hide() {
252
- visible = false;
253
- el.classList.remove('md-toolbar--visible');
254
- closeDropdown();
255
- linkInputContainer?.classList.remove('md-tb-link-input--visible');
256
- linkApplyCallback = null;
257
- }
258
- function closeDropdown() {
259
- if (openDropdown) {
260
- openDropdown.classList.remove('md-tb-dropdown--open');
261
- openDropdown = null;
262
- }
263
- }
264
- function toggleDropdown(dropdown) {
265
- if (openDropdown === dropdown) {
266
- closeDropdown();
267
- return;
268
- }
269
- closeDropdown();
270
- saveSelection();
271
- openDropdown = dropdown;
272
- dropdown.classList.add('md-tb-dropdown--open');
273
- }
274
- // --- Event handling ---
275
- el.addEventListener('mousedown', (e) => {
276
- const target = e.target;
277
- // Let the link URL input receive focus normally
278
- if (target.closest('.md-tb-link-input'))
279
- return;
280
- // Save the selection NOW — at mousedown time the browser hasn't
281
- // cleared it yet. Every subsequent format operation will restore
282
- // from this snapshot.
283
- saveSelection();
284
- // Prevent toolbar clicks from blurring the editable element
285
- e.preventDefault();
286
- });
287
- el.addEventListener('click', (e) => {
288
- const target = e.target;
289
- const button = target.closest('button');
290
- if (!button)
291
- return;
292
- // Dropdown triggers
293
- const dropdown = button.closest('.md-tb-dropdown');
294
- if (button.classList.contains('md-tb-color-trigger') || button.classList.contains('md-tb-size-trigger')) {
295
- if (dropdown)
296
- toggleDropdown(dropdown);
297
- return;
298
- }
299
- // Color swatch
300
- if (button.classList.contains('md-tb-swatch')) {
301
- const color = button.dataset.color ?? '';
302
- applyColor(color);
303
- closeDropdown();
304
- onFormatApplied?.();
305
- return;
306
- }
307
- // Font size option
308
- if (button.classList.contains('md-tb-size-opt')) {
309
- const size = button.dataset.size ?? '';
310
- applyFontSize(size);
311
- closeDropdown();
312
- onFormatApplied?.();
313
- return;
314
- }
315
- // Inline format buttons
316
- closeDropdown();
317
- if (button.classList.contains('md-tb-bold')) {
318
- execFormat('bold');
319
- }
320
- else if (button.classList.contains('md-tb-italic')) {
321
- execFormat('italic');
322
- }
323
- else if (button.classList.contains('md-tb-strike')) {
324
- execFormat('strikeThrough');
325
- }
326
- else if (button.classList.contains('md-tb-code')) {
327
- applyCode();
328
- }
329
- else if (button.classList.contains('md-tb-link')) {
330
- applyLink();
331
- }
332
- onFormatApplied?.();
333
- });
334
- // Close dropdown on outside click
335
- function handleDocClick(e) {
336
- if (openDropdown && !el.contains(e.target)) {
337
- closeDropdown();
338
- }
339
- }
340
- document.addEventListener('click', handleDocClick);
341
- function destroy() {
342
- document.removeEventListener('click', handleDocClick);
343
- el.remove();
344
- }
345
- return {
346
- element: el,
347
- show,
348
- hide,
349
- destroy,
350
- get isVisible() { return visible; },
351
- get hasOpenDropdown() { return openDropdown !== null; },
352
- };
353
- }
354
- // ---------------------------------------------------------------------------
355
- // Keyboard shortcut handler (call from within contentEditable keydown)
356
- // ---------------------------------------------------------------------------
357
- export function handleFormatKeydown(e) {
358
- const mod = e.metaKey || e.ctrlKey;
359
- if (!mod)
360
- return false;
361
- switch (e.key.toLowerCase()) {
362
- case 'b':
363
- e.preventDefault();
364
- saveSelection();
365
- execFormat('bold');
366
- return true;
367
- case 'i':
368
- e.preventDefault();
369
- saveSelection();
370
- execFormat('italic');
371
- return true;
372
- case 'k':
373
- e.preventDefault();
374
- applyLink();
375
- return true;
376
- case 'e':
377
- e.preventDefault();
378
- saveSelection();
379
- applyCode();
380
- return true;
381
- default:
382
- return false;
383
- }
384
- }
@@ -1,29 +0,0 @@
1
- export interface MarkdownBlock {
2
- type: 'heading' | 'paragraph' | 'list' | 'code' | 'blockquote' | 'hr' | 'table';
3
- source: string;
4
- startLine: number;
5
- endLine: number;
6
- level?: number;
7
- listType?: 'ul' | 'ol';
8
- editable: boolean;
9
- }
10
- export interface EditorCallbacks {
11
- callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
12
- updateContext: (text: string) => void;
13
- trackEvent?: (event: string, params?: Record<string, unknown>) => void;
14
- }
15
- export interface EditorHandle {
16
- /** Remove all event listeners and toolbar DOM. */
17
- cleanup: () => void;
18
- /** True when a block is actively being edited (contentEditable is on). */
19
- isEditing: () => boolean;
20
- }
21
- export declare function parseMarkdownBlocks(source: string): MarkdownBlock[];
22
- export declare function renderEditableMarkdown(source: string): string;
23
- /**
24
- * Attach WYSIWYG editing handlers to editable markdown blocks inside
25
- * the given container. All editable blocks are contentEditable from the
26
- * start — users can select and type immediately without an activation
27
- * click. Changes are committed when focus leaves the editor.
28
- */
29
- export declare function attachEditorHandlers(container: HTMLElement, source: string, filePath: string, callbacks: EditorCallbacks): EditorHandle;