@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.
- package/dist/server.js +1 -1
- package/dist/ui/file-preview/preview-runtime.js +204 -153
- package/dist/ui/file-preview/src/markdown/controller.d.ts +7 -1
- package/dist/ui/file-preview/src/markdown/controller.js +135 -16
- package/dist/ui/file-preview/src/markdown/editor.d.ts +97 -1
- package/dist/ui/file-preview/src/markdown/editor.js +814 -26
- package/dist/ui/file-preview/src/model.d.ts +2 -1
- package/dist/utils/capture.js +1 -1
- package/dist/utils/toolHistory.d.ts +13 -0
- package/dist/utils/toolHistory.js +65 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/dist/ui/config-editor/app.js +0 -840
- package/dist/ui/config-editor/array-modal.d.ts +0 -19
- package/dist/ui/config-editor/array-modal.js +0 -185
- package/dist/ui/config-editor/main.d.ts +0 -1
- package/dist/ui/config-editor/main.js +0 -2
- package/dist/ui/config-editor/src/App.d.ts +0 -43
- package/dist/ui/config-editor/src/components/layout.d.ts +0 -4
- package/dist/ui/config-editor/src/components/layout.js +0 -83
- package/dist/ui/config-editor/src/components/toolbar.d.ts +0 -1
- package/dist/ui/config-editor/src/components/toolbar.js +0 -21
- package/dist/ui/config-editor/src/config-values.d.ts +0 -6
- package/dist/ui/config-editor/src/config-values.js +0 -61
- package/dist/ui/config-editor/src/contracts.d.ts +0 -14
- package/dist/ui/config-editor/src/contracts.js +0 -3
- package/dist/ui/config-editor/src/directory-browser.d.ts +0 -6
- package/dist/ui/config-editor/src/directory-browser.js +0 -71
- package/dist/ui/config-editor/src/layout.d.ts +0 -5
- package/dist/ui/config-editor/src/layout.js +0 -90
- package/dist/ui/config-editor/src/parsing.d.ts +0 -5
- package/dist/ui/config-editor/src/parsing.js +0 -50
- package/dist/ui/config-editor/src/toolbar.d.ts +0 -1
- package/dist/ui/config-editor/src/toolbar.js +0 -18
- package/dist/ui/config-editor/src/types.d.ts +0 -17
- package/dist/ui/config-editor/src/types.js +0 -3
- package/dist/ui/config-editor/src/utils/config-values.d.ts +0 -9
- package/dist/ui/config-editor/src/utils/config-values.js +0 -61
- package/dist/ui/config-editor/src/utils/directory-browser.d.ts +0 -31
- package/dist/ui/config-editor/src/utils/directory-browser.js +0 -201
- package/dist/ui/config-editor/src/utils/parsing.d.ts +0 -8
- package/dist/ui/config-editor/src/utils/parsing.js +0 -50
- package/dist/ui/file-preview/app.d.ts +0 -8
- package/dist/ui/file-preview/app.js +0 -2020
- package/dist/ui/file-preview/components/code-viewer.d.ts +0 -6
- package/dist/ui/file-preview/components/code-viewer.js +0 -73
- package/dist/ui/file-preview/components/highlighting.d.ts +0 -2
- package/dist/ui/file-preview/components/highlighting.js +0 -54
- package/dist/ui/file-preview/components/html-renderer.d.ts +0 -5
- package/dist/ui/file-preview/components/html-renderer.js +0 -47
- package/dist/ui/file-preview/components/markdown-renderer.d.ts +0 -1
- package/dist/ui/file-preview/components/markdown-renderer.js +0 -67
- package/dist/ui/file-preview/components/toolbar.d.ts +0 -6
- package/dist/ui/file-preview/components/toolbar.js +0 -75
- package/dist/ui/file-preview/image-preview.d.ts +0 -3
- package/dist/ui/file-preview/image-preview.js +0 -21
- package/dist/ui/file-preview/main.d.ts +0 -1
- package/dist/ui/file-preview/main.js +0 -5
- package/dist/ui/file-preview/markdown/editor.d.ts +0 -36
- package/dist/ui/file-preview/markdown/editor.js +0 -643
- package/dist/ui/file-preview/markdown/linking.d.ts +0 -9
- package/dist/ui/file-preview/markdown/linking.js +0 -210
- package/dist/ui/file-preview/markdown/outline.d.ts +0 -7
- package/dist/ui/file-preview/markdown/outline.js +0 -40
- package/dist/ui/file-preview/markdown/preview.d.ts +0 -8
- package/dist/ui/file-preview/markdown/preview.js +0 -33
- package/dist/ui/file-preview/markdown/slugify.d.ts +0 -3
- package/dist/ui/file-preview/markdown/slugify.js +0 -31
- package/dist/ui/file-preview/markdown/toc.d.ts +0 -11
- package/dist/ui/file-preview/markdown/toc.js +0 -75
- package/dist/ui/file-preview/markdown/utils.d.ts +0 -1
- package/dist/ui/file-preview/markdown/utils.js +0 -15
- package/dist/ui/file-preview/markdown/workspace-controller.d.ts +0 -25
- package/dist/ui/file-preview/markdown/workspace-controller.js +0 -40
- package/dist/ui/file-preview/src/components/CodeViewer.d.ts +0 -6
- package/dist/ui/file-preview/src/components/CodeViewer.js +0 -60
- package/dist/ui/file-preview/src/components/HtmlRenderer.d.ts +0 -8
- package/dist/ui/file-preview/src/components/HtmlRenderer.js +0 -45
- package/dist/ui/file-preview/src/components/MarkdownRenderer.d.ts +0 -1
- package/dist/ui/file-preview/src/components/MarkdownRenderer.js +0 -15
- package/dist/ui/file-preview/src/components/Toolbar.d.ts +0 -6
- package/dist/ui/file-preview/src/components/Toolbar.js +0 -75
- package/dist/ui/file-preview/src/components/editor-toolbar.d.ts +0 -15
- package/dist/ui/file-preview/src/components/editor-toolbar.js +0 -384
- package/dist/ui/file-preview/src/components/markdown-editor.d.ts +0 -29
- package/dist/ui/file-preview/src/components/markdown-editor.js +0 -535
- package/dist/ui/file-preview/src/markdown/block-merge.d.ts +0 -25
- package/dist/ui/file-preview/src/markdown/block-merge.js +0 -86
- package/dist/ui/file-preview/src/markdown/link-modal.d.ts +0 -13
- package/dist/ui/file-preview/src/markdown/link-modal.js +0 -213
- package/dist/ui/file-preview/src/markdown/raw-editor.d.ts +0 -8
- package/dist/ui/file-preview/src/markdown/raw-editor.js +0 -61
- package/dist/ui/file-preview/src/markdown/selection-toolbar.d.ts +0 -14
- package/dist/ui/file-preview/src/markdown/selection-toolbar.js +0 -128
- package/dist/ui/file-preview/src/markdown/toc.d.ts +0 -11
- package/dist/ui/file-preview/src/markdown/toc.js +0 -75
- package/dist/ui/file-preview/src/markdown-workspace/editor.d.ts +0 -36
- package/dist/ui/file-preview/src/markdown-workspace/editor.js +0 -643
- package/dist/ui/file-preview/src/markdown-workspace/linking.d.ts +0 -9
- package/dist/ui/file-preview/src/markdown-workspace/linking.js +0 -210
- package/dist/ui/file-preview/src/markdown-workspace/outline.d.ts +0 -7
- package/dist/ui/file-preview/src/markdown-workspace/outline.js +0 -40
- package/dist/ui/file-preview/src/markdown-workspace/preview.d.ts +0 -8
- package/dist/ui/file-preview/src/markdown-workspace/preview.js +0 -33
- package/dist/ui/file-preview/src/markdown-workspace/slugify.d.ts +0 -3
- package/dist/ui/file-preview/src/markdown-workspace/slugify.js +0 -31
- package/dist/ui/file-preview/src/markdown-workspace/toc.d.ts +0 -11
- package/dist/ui/file-preview/src/markdown-workspace/toc.js +0 -75
- package/dist/ui/file-preview/src/markdown-workspace/utils.d.ts +0 -1
- package/dist/ui/file-preview/src/markdown-workspace/utils.js +0 -15
- package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.d.ts +0 -25
- package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.js +0 -40
- package/dist/ui/file-preview/types.d.ts +0 -1
- package/dist/ui/file-preview/types.js +0 -1
- package/dist/ui/server-integration.d.ts +0 -13
- package/dist/ui/server-integration.js +0 -31
- package/dist/ui/shared/ToolHeader.d.ts +0 -9
- package/dist/ui/shared/ToolHeader.js +0 -29
- package/dist/ui/shared/app-bootstrap.d.ts +0 -9
- package/dist/ui/shared/app-bootstrap.js +0 -15
- package/dist/ui/shared/guards.d.ts +0 -1
- package/dist/ui/shared/guards.js +0 -3
- package/dist/ui/shared/host-lifecycle.d.ts +0 -17
- package/dist/ui/shared/host-lifecycle.js +0 -41
- package/dist/ui/shared/rpc-client.d.ts +0 -14
- package/dist/ui/shared/rpc-client.js +0 -72
- package/dist/ui/shared/theme-adaptation.d.ts +0 -10
- package/dist/ui/shared/theme-adaptation.js +0 -118
- package/dist/ui/shared/tool-header.d.ts +0 -9
- package/dist/ui/shared/tool-header.js +0 -25
- package/dist/utils/ui-call-context.d.ts +0 -8
- package/dist/utils/ui-call-context.js +0 -72
- /package/dist/ui/config-editor/{app.d.ts → src/app.d.ts} +0 -0
- /package/dist/ui/config-editor/src/{App.js → app.js} +0 -0
- /package/dist/ui/file-preview/src/{App.d.ts → app.d.ts} +0 -0
- /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, '&')
|
|
92
|
-
.replace(/</g, '<')
|
|
93
|
-
.replace(/>/g, '>');
|
|
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 === '' ? '×' : ''}</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;
|