@hcu-lab.me/mcp-device 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +22 -0
- package/dist/command-manager.d.ts +8 -0
- package/dist/command-manager.js +241 -0
- package/dist/config-field-definitions.d.ts +41 -0
- package/dist/config-field-definitions.js +37 -0
- package/dist/config-manager.d.ts +102 -0
- package/dist/config-manager.js +291 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +10 -0
- package/dist/custom-stdio.d.ts +51 -0
- package/dist/custom-stdio.js +358 -0
- package/dist/data/official-app-ca.pem +12 -0
- package/dist/data/onboarding-prompts.json +70 -0
- package/dist/device/device-state.d.ts +13 -0
- package/dist/device/device-state.js +100 -0
- package/dist/device/device-status.d.ts +81 -0
- package/dist/device/device-status.js +156 -0
- package/dist/device/device.d.ts +16 -0
- package/dist/device/device.js +190 -0
- package/dist/device/execution-engine.d.ts +144 -0
- package/dist/device/execution-engine.js +195 -0
- package/dist/device/gateway-channel.d.ts +48 -0
- package/dist/device/gateway-channel.js +384 -0
- package/dist/device/gateway-config.d.ts +40 -0
- package/dist/device/gateway-config.js +207 -0
- package/dist/device/gateway-identity.d.ts +35 -0
- package/dist/device/gateway-identity.js +178 -0
- package/dist/device/gateway-pairing.d.ts +25 -0
- package/dist/device/gateway-pairing.js +105 -0
- package/dist/device/gateway-secure-transport.d.ts +25 -0
- package/dist/device/gateway-secure-transport.js +117 -0
- package/dist/device/gateway-tool-adapter.d.ts +17 -0
- package/dist/device/gateway-tool-adapter.js +284 -0
- package/dist/device/gateway-url-policy.d.ts +3 -0
- package/dist/device/gateway-url-policy.js +43 -0
- package/dist/device/linux-service.d.ts +70 -0
- package/dist/device/linux-service.js +268 -0
- package/dist/device/official-trust.d.ts +7 -0
- package/dist/device/official-trust.js +49 -0
- package/dist/device/project-inspection.d.ts +1 -0
- package/dist/device/project-inspection.js +180 -0
- package/dist/device/runtime-owner.d.ts +41 -0
- package/dist/device/runtime-owner.js +177 -0
- package/dist/device/windows-dpapi.d.ts +11 -0
- package/dist/device/windows-dpapi.js +67 -0
- package/dist/device/windows-service.d.ts +56 -0
- package/dist/device/windows-service.js +238 -0
- package/dist/error-handlers.d.ts +7 -0
- package/dist/error-handlers.js +15 -0
- package/dist/handlers/edit-search-handlers.d.ts +6 -0
- package/dist/handlers/edit-search-handlers.js +6 -0
- package/dist/handlers/filesystem-handlers.d.ts +38 -0
- package/dist/handlers/filesystem-handlers.js +440 -0
- package/dist/handlers/history-handlers.d.ts +12 -0
- package/dist/handlers/history-handlers.js +67 -0
- package/dist/handlers/index.d.ts +6 -0
- package/dist/handlers/index.js +7 -0
- package/dist/handlers/process-handlers.d.ts +9 -0
- package/dist/handlers/process-handlers.js +15 -0
- package/dist/handlers/search-handlers.d.ts +17 -0
- package/dist/handlers/search-handlers.js +224 -0
- package/dist/handlers/terminal-handlers.d.ts +21 -0
- package/dist/handlers/terminal-handlers.js +35 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +138 -0
- package/dist/mcp-device.d.ts +2 -0
- package/dist/mcp-device.js +7 -0
- package/dist/npm-scripts/remote.d.ts +3 -0
- package/dist/npm-scripts/remote.js +314 -0
- package/dist/search-manager.d.ts +147 -0
- package/dist/search-manager.js +828 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +1563 -0
- package/dist/terminal-manager.d.ts +104 -0
- package/dist/terminal-manager.js +635 -0
- package/dist/tools/config.d.ts +96 -0
- package/dist/tools/config.js +260 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +435 -0
- package/dist/tools/feedback.d.ts +8 -0
- package/dist/tools/feedback.js +132 -0
- package/dist/tools/filesystem.d.ts +79 -0
- package/dist/tools/filesystem.js +947 -0
- package/dist/tools/fuzzySearch.d.ts +12 -0
- package/dist/tools/fuzzySearch.js +74 -0
- package/dist/tools/fuzzySearchCore.d.ts +52 -0
- package/dist/tools/fuzzySearchCore.js +125 -0
- package/dist/tools/improved-process-tools.d.ts +24 -0
- package/dist/tools/improved-process-tools.js +627 -0
- package/dist/tools/mime-types.d.ts +3 -0
- package/dist/tools/mime-types.js +28 -0
- package/dist/tools/pdf/extract-images.d.ts +34 -0
- package/dist/tools/pdf/extract-images.js +132 -0
- package/dist/tools/pdf/index.d.ts +6 -0
- package/dist/tools/pdf/index.js +3 -0
- package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
- package/dist/tools/pdf/lib/pdf2md.js +76 -0
- package/dist/tools/pdf/manipulations.d.ts +13 -0
- package/dist/tools/pdf/manipulations.js +99 -0
- package/dist/tools/pdf/markdown.d.ts +26 -0
- package/dist/tools/pdf/markdown.js +275 -0
- package/dist/tools/pdf/utils.d.ts +12 -0
- package/dist/tools/pdf/utils.js +34 -0
- package/dist/tools/process.d.ts +3 -0
- package/dist/tools/process.js +56 -0
- package/dist/tools/prompts.d.ts +28 -0
- package/dist/tools/prompts.js +259 -0
- package/dist/tools/schemas.d.ts +454 -0
- package/dist/tools/schemas.js +242 -0
- package/dist/tools/usage.d.ts +5 -0
- package/dist/tools/usage.js +24 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.js +1 -0
- package/dist/ui/config-editor/config-editor-runtime.js +150 -0
- package/dist/ui/config-editor/index.html +13 -0
- package/dist/ui/config-editor/src/app.d.ts +43 -0
- package/dist/ui/config-editor/src/app.js +831 -0
- package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
- package/dist/ui/config-editor/src/array-modal.js +185 -0
- package/dist/ui/config-editor/src/main.d.ts +1 -0
- package/dist/ui/config-editor/src/main.js +2 -0
- package/dist/ui/config-editor/styles.css +587 -0
- package/dist/ui/contracts.d.ts +14 -0
- package/dist/ui/contracts.js +21 -0
- package/dist/ui/file-preview/index.html +16 -0
- package/dist/ui/file-preview/preview-runtime.js +469 -0
- package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
- package/dist/ui/file-preview/shared/preview-file-types.js +59 -0
- package/dist/ui/file-preview/src/app.d.ts +4 -0
- package/dist/ui/file-preview/src/app.js +680 -0
- package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
- package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
- package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
- package/dist/ui/file-preview/src/components/highlighting.js +54 -0
- package/dist/ui/file-preview/src/components/html-renderer.d.ts +5 -0
- package/dist/ui/file-preview/src/components/html-renderer.js +47 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.js +59 -0
- package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
- package/dist/ui/file-preview/src/directory-controller.js +240 -0
- package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
- package/dist/ui/file-preview/src/document-layout.js +109 -0
- package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
- package/dist/ui/file-preview/src/document-outline.js +97 -0
- package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
- package/dist/ui/file-preview/src/document-workspace.js +33 -0
- package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
- package/dist/ui/file-preview/src/file-type-handlers.js +109 -0
- package/dist/ui/file-preview/src/host/external-actions.d.ts +8 -0
- package/dist/ui/file-preview/src/host/external-actions.js +55 -0
- package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
- package/dist/ui/file-preview/src/host/selection-context.js +106 -0
- package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
- package/dist/ui/file-preview/src/image-preview.js +21 -0
- package/dist/ui/file-preview/src/main.d.ts +1 -0
- package/dist/ui/file-preview/src/main.js +5 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
- package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
- package/dist/ui/file-preview/src/markdown/controller.js +1043 -0
- package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
- package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
- package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
- package/dist/ui/file-preview/src/markdown/linking.js +228 -0
- package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
- package/dist/ui/file-preview/src/markdown/outline.js +16 -0
- package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
- package/dist/ui/file-preview/src/markdown/parser.js +38 -0
- package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/preview.js +20 -0
- package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
- package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
- package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/utils.js +15 -0
- package/dist/ui/file-preview/src/model.d.ts +35 -0
- package/dist/ui/file-preview/src/model.js +1 -0
- package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
- package/dist/ui/file-preview/src/panel-actions.js +182 -0
- package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
- package/dist/ui/file-preview/src/path-utils.js +64 -0
- package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
- package/dist/ui/file-preview/src/payload-utils.js +119 -0
- package/dist/ui/file-preview/src/types.d.ts +1 -0
- package/dist/ui/file-preview/src/types.js +1 -0
- package/dist/ui/file-preview/styles.css +1631 -0
- package/dist/ui/resources.d.ts +28 -0
- package/dist/ui/resources.js +86 -0
- package/dist/ui/shared/compact-row.d.ts +11 -0
- package/dist/ui/shared/compact-row.js +18 -0
- package/dist/ui/shared/escape-html.d.ts +4 -0
- package/dist/ui/shared/escape-html.js +11 -0
- package/dist/ui/shared/host-context.d.ts +15 -0
- package/dist/ui/shared/host-context.js +51 -0
- package/dist/ui/shared/tool-bridge.d.ts +30 -0
- package/dist/ui/shared/tool-bridge.js +137 -0
- package/dist/ui/shared/tool-shell.d.ts +25 -0
- package/dist/ui/shared/tool-shell.js +107 -0
- package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
- package/dist/ui/shared/ui-event-tracker.js +27 -0
- package/dist/ui/shared/widget-state.d.ts +33 -0
- package/dist/ui/shared/widget-state.js +158 -0
- package/dist/utils/ab-test.d.ts +12 -0
- package/dist/utils/ab-test.js +104 -0
- package/dist/utils/capture.d.ts +35 -0
- package/dist/utils/capture.js +481 -0
- package/dist/utils/dockerPrompt.d.ts +17 -0
- package/dist/utils/dockerPrompt.js +53 -0
- package/dist/utils/feature-flags.d.ts +59 -0
- package/dist/utils/feature-flags.js +215 -0
- package/dist/utils/files/base.d.ts +173 -0
- package/dist/utils/files/base.js +5 -0
- package/dist/utils/files/binary.d.ts +21 -0
- package/dist/utils/files/binary.js +65 -0
- package/dist/utils/files/docx.d.ts +47 -0
- package/dist/utils/files/docx.js +679 -0
- package/dist/utils/files/excel.d.ts +24 -0
- package/dist/utils/files/excel.js +437 -0
- package/dist/utils/files/factory.d.ts +41 -0
- package/dist/utils/files/factory.js +113 -0
- package/dist/utils/files/image.d.ts +21 -0
- package/dist/utils/files/image.js +78 -0
- package/dist/utils/files/index.d.ts +10 -0
- package/dist/utils/files/index.js +13 -0
- package/dist/utils/files/pdf.d.ts +32 -0
- package/dist/utils/files/pdf.js +142 -0
- package/dist/utils/files/text.d.ts +63 -0
- package/dist/utils/files/text.js +370 -0
- package/dist/utils/fuzzySearchLogger.d.ts +30 -0
- package/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/utils/lineEndingHandler.d.ts +21 -0
- package/dist/utils/lineEndingHandler.js +77 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
- package/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/utils/open-browser.d.ts +9 -0
- package/dist/utils/open-browser.js +42 -0
- package/dist/utils/process-detection.d.ts +23 -0
- package/dist/utils/process-detection.js +150 -0
- package/dist/utils/ripgrep-resolver.d.ts +9 -0
- package/dist/utils/ripgrep-resolver.js +74 -0
- package/dist/utils/system-info.d.ts +71 -0
- package/dist/utils/system-info.js +712 -0
- package/dist/utils/toolHistory.d.ts +86 -0
- package/dist/utils/toolHistory.js +257 -0
- package/dist/utils/trackTools.d.ts +6 -0
- package/dist/utils/trackTools.js +54 -0
- package/dist/utils/unsupportedParams.d.ts +24 -0
- package/dist/utils/unsupportedParams.js +66 -0
- package/dist/utils/usageTracker.d.ts +133 -0
- package/dist/utils/usageTracker.js +477 -0
- package/dist/utils/welcome-onboarding.d.ts +11 -0
- package/dist/utils/welcome-onboarding.js +99 -0
- package/dist/utils/withTimeout.d.ts +28 -0
- package/dist/utils/withTimeout.js +85 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code/text viewer renderer responsible for escaping, optional formatting, and syntax-highlight output wrappers. It provides a safe default for non-rich preview content.
|
|
3
|
+
*/
|
|
4
|
+
import { highlightSource } from './highlighting.js';
|
|
5
|
+
const EXTENSION_LANGUAGE_MAP = {
|
|
6
|
+
js: 'javascript',
|
|
7
|
+
cjs: 'javascript',
|
|
8
|
+
mjs: 'javascript',
|
|
9
|
+
ts: 'typescript',
|
|
10
|
+
jsx: 'javascript',
|
|
11
|
+
tsx: 'typescript',
|
|
12
|
+
json: 'json',
|
|
13
|
+
yaml: 'yaml',
|
|
14
|
+
yml: 'yaml',
|
|
15
|
+
toml: 'toml',
|
|
16
|
+
ini: 'ini',
|
|
17
|
+
xml: 'xml',
|
|
18
|
+
html: 'html',
|
|
19
|
+
htm: 'html',
|
|
20
|
+
css: 'css',
|
|
21
|
+
scss: 'css',
|
|
22
|
+
less: 'css',
|
|
23
|
+
sh: 'bash',
|
|
24
|
+
bash: 'bash',
|
|
25
|
+
zsh: 'bash',
|
|
26
|
+
py: 'python',
|
|
27
|
+
rb: 'ruby',
|
|
28
|
+
java: 'java',
|
|
29
|
+
go: 'go',
|
|
30
|
+
rs: 'rust',
|
|
31
|
+
sql: 'sql',
|
|
32
|
+
md: 'markdown',
|
|
33
|
+
markdown: 'markdown'
|
|
34
|
+
};
|
|
35
|
+
function getFileExtension(filePath) {
|
|
36
|
+
const match = filePath.toLowerCase().match(/\.([a-z0-9]+)$/);
|
|
37
|
+
return match ? match[1] : '';
|
|
38
|
+
}
|
|
39
|
+
export function inferLanguageFromPath(filePath) {
|
|
40
|
+
const extension = getFileExtension(filePath);
|
|
41
|
+
return EXTENSION_LANGUAGE_MAP[extension] ?? 'text';
|
|
42
|
+
}
|
|
43
|
+
export function formatJsonIfPossible(content, filePath) {
|
|
44
|
+
if (inferLanguageFromPath(filePath) !== 'json') {
|
|
45
|
+
return { content };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return {
|
|
49
|
+
content: `${JSON.stringify(JSON.parse(content), null, 2)}\n`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return {
|
|
54
|
+
content,
|
|
55
|
+
notice: 'Invalid JSON. Showing raw source.'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function renderCodeViewer(code, language = 'text', startLine = 1) {
|
|
60
|
+
const normalizedLanguage = language || 'text';
|
|
61
|
+
const highlighted = highlightSource(code, normalizedLanguage);
|
|
62
|
+
// Wrap each line with line number gutter
|
|
63
|
+
const lines = highlighted.split('\n');
|
|
64
|
+
// Remove trailing empty line from trailing newline
|
|
65
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
66
|
+
lines.pop();
|
|
67
|
+
}
|
|
68
|
+
const lineHtml = lines.map((line, i) => {
|
|
69
|
+
const lineNum = startLine + i;
|
|
70
|
+
return `<tr class="code-line" data-line="${lineNum}"><td class="line-num" data-line="${lineNum}">${lineNum}</td><td class="line-content">${line || ' '}</td></tr>`;
|
|
71
|
+
}).join('\n');
|
|
72
|
+
return `<pre class="code-viewer"><table class="code-table"><tbody>${lineHtml}</tbody></table></pre>`;
|
|
73
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax highlighting integration layer around highlight.js. It registers supported languages and provides safe helpers for highlighted or plain escaped output.
|
|
3
|
+
*/
|
|
4
|
+
import { escapeHtml as sharedEscapeHtml } from '../../../shared/escape-html.js';
|
|
5
|
+
import hljs from 'highlight.js/lib/core';
|
|
6
|
+
import bash from 'highlight.js/lib/languages/bash';
|
|
7
|
+
import css from 'highlight.js/lib/languages/css';
|
|
8
|
+
import go from 'highlight.js/lib/languages/go';
|
|
9
|
+
import java from 'highlight.js/lib/languages/java';
|
|
10
|
+
import javascript from 'highlight.js/lib/languages/javascript';
|
|
11
|
+
import json from 'highlight.js/lib/languages/json';
|
|
12
|
+
import markdown from 'highlight.js/lib/languages/markdown';
|
|
13
|
+
import python from 'highlight.js/lib/languages/python';
|
|
14
|
+
import ruby from 'highlight.js/lib/languages/ruby';
|
|
15
|
+
import rust from 'highlight.js/lib/languages/rust';
|
|
16
|
+
import sql from 'highlight.js/lib/languages/sql';
|
|
17
|
+
import typescript from 'highlight.js/lib/languages/typescript';
|
|
18
|
+
import xml from 'highlight.js/lib/languages/xml';
|
|
19
|
+
import yaml from 'highlight.js/lib/languages/yaml';
|
|
20
|
+
hljs.registerLanguage('bash', bash);
|
|
21
|
+
hljs.registerLanguage('css', css);
|
|
22
|
+
hljs.registerLanguage('go', go);
|
|
23
|
+
hljs.registerLanguage('java', java);
|
|
24
|
+
hljs.registerLanguage('javascript', javascript);
|
|
25
|
+
hljs.registerLanguage('json', json);
|
|
26
|
+
hljs.registerLanguage('markdown', markdown);
|
|
27
|
+
hljs.registerLanguage('python', python);
|
|
28
|
+
hljs.registerLanguage('ruby', ruby);
|
|
29
|
+
hljs.registerLanguage('rust', rust);
|
|
30
|
+
hljs.registerLanguage('sql', sql);
|
|
31
|
+
hljs.registerLanguage('typescript', typescript);
|
|
32
|
+
hljs.registerLanguage('xml', xml);
|
|
33
|
+
hljs.registerLanguage('html', xml);
|
|
34
|
+
hljs.registerLanguage('yaml', yaml);
|
|
35
|
+
hljs.registerLanguage('toml', yaml);
|
|
36
|
+
hljs.registerLanguage('ini', yaml);
|
|
37
|
+
export function escapeHtml(input) {
|
|
38
|
+
return sharedEscapeHtml(input);
|
|
39
|
+
}
|
|
40
|
+
export function highlightSource(code, language) {
|
|
41
|
+
const normalizedLanguage = (language || '').toLowerCase();
|
|
42
|
+
if (!normalizedLanguage || normalizedLanguage === 'text') {
|
|
43
|
+
return escapeHtml(code);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
if (hljs.getLanguage(normalizedLanguage)) {
|
|
47
|
+
return hljs.highlight(code, { language: normalizedLanguage, ignoreIllegals: true }).value;
|
|
48
|
+
}
|
|
49
|
+
return hljs.highlightAuto(code).value;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return escapeHtml(code);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML preview renderer with display mode control. It handles rendered HTML versus
|
|
3
|
+
* source text display and ensures fallback behavior is predictable.
|
|
4
|
+
*
|
|
5
|
+
* The rendered preview runs inside a nested sandboxed iframe, which is itself inside
|
|
6
|
+
* the MCP app's sandboxed iframe chain. Scripts and external resources (CDNs) are
|
|
7
|
+
* allowed since the sandbox isolation prevents any escape.
|
|
8
|
+
*/
|
|
9
|
+
import { renderCodeViewer } from './code-viewer.js';
|
|
10
|
+
import { escapeHtml } from './highlighting.js';
|
|
11
|
+
function resolveThemeFrameStyles() {
|
|
12
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
13
|
+
return {
|
|
14
|
+
background: 'Canvas',
|
|
15
|
+
text: 'CanvasText',
|
|
16
|
+
fontFamily: 'system-ui, sans-serif',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const rootStyles = window.getComputedStyle(document.documentElement);
|
|
20
|
+
const background = rootStyles.getPropertyValue('--panel').trim() || 'Canvas';
|
|
21
|
+
const text = rootStyles.getPropertyValue('--text').trim() || 'CanvasText';
|
|
22
|
+
const fontFamily = rootStyles.getPropertyValue('--font-sans').trim() || 'system-ui, sans-serif';
|
|
23
|
+
return { background, text, fontFamily };
|
|
24
|
+
}
|
|
25
|
+
function renderSandboxedHtmlFrame(content) {
|
|
26
|
+
const palette = resolveThemeFrameStyles();
|
|
27
|
+
const frameDocument = `<!doctype html><html><head><meta charset="utf-8" /><style>html,body{margin:0;padding:0;background:${palette.background};color:${palette.text};}body{font-family:${palette.fontFamily};padding:16px;line-height:1.5;}img{max-width:100%;height:auto;}</style></head><body>${content}</body></html>`;
|
|
28
|
+
return `<iframe class="html-rendered-frame" title="Rendered HTML preview" sandbox="allow-scripts allow-forms allow-popups" referrerpolicy="no-referrer" srcdoc="${escapeHtml(frameDocument)}"></iframe>`;
|
|
29
|
+
}
|
|
30
|
+
export function renderHtmlPreview(content, mode) {
|
|
31
|
+
if (mode === 'source') {
|
|
32
|
+
return {
|
|
33
|
+
html: `<div class="panel-content source-content">${renderCodeViewer(content, 'html')}</div>`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return {
|
|
38
|
+
html: `<div class="panel-content html-content">${renderSandboxedHtmlFrame(content)}</div>`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return {
|
|
43
|
+
html: `<div class="panel-content source-content">${renderCodeViewer(content, 'html')}</div>`,
|
|
44
|
+
notice: 'HTML renderer failed. Showing source instead.'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function renderMarkdown(content: string): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown rendering pipeline for preview mode. It configures markdown-it and highlighting so markdown content is rendered consistently with code block support.
|
|
3
|
+
*/
|
|
4
|
+
import { highlightSource } from './highlighting.js';
|
|
5
|
+
import { createMarkdownIt, prepareMarkdownSource, readHeadingProjection } from '../markdown/parser.js';
|
|
6
|
+
import { createSlugTracker } from '../markdown/slugify.js';
|
|
7
|
+
const markdown = createMarkdownIt({
|
|
8
|
+
highlight(code, language) {
|
|
9
|
+
const normalizedLanguage = (language || 'text').toLowerCase();
|
|
10
|
+
const highlighted = highlightSource(code, normalizedLanguage);
|
|
11
|
+
return `<pre class="code-viewer"><code class="hljs language-${normalizedLanguage}">${highlighted}</code></pre>`;
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
const renderHeadingOpen = markdown.renderer.rules.heading_open;
|
|
15
|
+
markdown.renderer.rules.heading_open = (...args) => {
|
|
16
|
+
const tokens = args[0];
|
|
17
|
+
const index = args[1];
|
|
18
|
+
const options = args[2];
|
|
19
|
+
const environment = args[3] ?? {};
|
|
20
|
+
const self = args[4];
|
|
21
|
+
const nextSlug = typeof environment.nextSlug === 'function'
|
|
22
|
+
? environment.nextSlug
|
|
23
|
+
: createSlugTracker();
|
|
24
|
+
environment.nextSlug = nextSlug;
|
|
25
|
+
const heading = readHeadingProjection(tokens, index, nextSlug);
|
|
26
|
+
const token = tokens[index];
|
|
27
|
+
if (heading) {
|
|
28
|
+
token.attrSet?.('id', heading.id);
|
|
29
|
+
token.attrSet?.('data-heading-id', heading.id);
|
|
30
|
+
}
|
|
31
|
+
if (typeof renderHeadingOpen === 'function') {
|
|
32
|
+
return renderHeadingOpen(...args);
|
|
33
|
+
}
|
|
34
|
+
return self.renderToken(tokens, index, options);
|
|
35
|
+
};
|
|
36
|
+
const renderLinkOpen = markdown.renderer.rules.link_open;
|
|
37
|
+
markdown.renderer.rules.link_open = (...args) => {
|
|
38
|
+
const tokens = args[0];
|
|
39
|
+
const index = args[1];
|
|
40
|
+
const options = args[2];
|
|
41
|
+
const self = args[4];
|
|
42
|
+
const token = tokens[index];
|
|
43
|
+
token.attrSet?.('data-markdown-link', 'true');
|
|
44
|
+
const title = token.attrGet?.('title');
|
|
45
|
+
if (title?.startsWith('mcp-wiki:')) {
|
|
46
|
+
const rawWikiLink = decodeURIComponent(title.slice('mcp-wiki:'.length));
|
|
47
|
+
token.attrSet?.('data-wiki-link', rawWikiLink);
|
|
48
|
+
if (Array.isArray(token.attrs)) {
|
|
49
|
+
token.attrs = token.attrs.filter(([name]) => name !== 'title');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (typeof renderLinkOpen === 'function') {
|
|
53
|
+
return renderLinkOpen(...args);
|
|
54
|
+
}
|
|
55
|
+
return self.renderToken(tokens, index, options);
|
|
56
|
+
};
|
|
57
|
+
export function renderMarkdown(content) {
|
|
58
|
+
return markdown.render(prepareMarkdownSource(content), { nextSlug: createSlugTracker() });
|
|
59
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RenderBodyResult, RenderPayload } from './model.js';
|
|
2
|
+
export declare function renderDirectoryBody(content: string, rootPath: string): RenderBodyResult;
|
|
3
|
+
export declare function attachDirectoryHandlers(options: {
|
|
4
|
+
container: HTMLElement;
|
|
5
|
+
callTool?: (name: string, args: Record<string, unknown>) => Promise<unknown | undefined>;
|
|
6
|
+
buildOpenInFolderCommand: (filePath: string) => string | undefined;
|
|
7
|
+
onOpenPayload: (payload: RenderPayload) => void;
|
|
8
|
+
}): void;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { escapeHtml } from './components/highlighting.js';
|
|
2
|
+
import { buildRenderPayload, extractToolText } from './payload-utils.js';
|
|
3
|
+
function parseDirectoryEntries(content) {
|
|
4
|
+
const lines = content.split('\n');
|
|
5
|
+
const hintLines = [];
|
|
6
|
+
const entryLines = [];
|
|
7
|
+
for (const line of lines) {
|
|
8
|
+
if (/^\[(DIR|FILE|DENIED|NOT_FOUND|WARNING)\]/.test(line.trim())) {
|
|
9
|
+
entryLines.push(line.trim());
|
|
10
|
+
}
|
|
11
|
+
else if (entryLines.length === 0) {
|
|
12
|
+
hintLines.push(line);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const flat = [];
|
|
16
|
+
for (const line of entryLines) {
|
|
17
|
+
if (line.startsWith('[WARNING]')) {
|
|
18
|
+
const warnBody = line.replace(/^\[WARNING\]\s*/, '');
|
|
19
|
+
const colonIdx = warnBody.indexOf(':');
|
|
20
|
+
const dirName = colonIdx >= 0 ? warnBody.slice(0, colonIdx).trim() : '';
|
|
21
|
+
const msg = colonIdx >= 0 ? warnBody.slice(colonIdx + 1).trim() : warnBody;
|
|
22
|
+
const parts = dirName.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
23
|
+
flat.push({
|
|
24
|
+
name: dirName,
|
|
25
|
+
fullPath: dirName,
|
|
26
|
+
isDir: false,
|
|
27
|
+
isDenied: false,
|
|
28
|
+
isNotFound: false,
|
|
29
|
+
isWarning: true,
|
|
30
|
+
warningText: msg,
|
|
31
|
+
depth: parts.length,
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const isDir = line.startsWith('[DIR]');
|
|
36
|
+
const isDenied = line.startsWith('[DENIED]');
|
|
37
|
+
const isNotFound = line.startsWith('[NOT_FOUND]');
|
|
38
|
+
const name = line.replace(/^\[(DIR|FILE|DENIED|NOT_FOUND)\]\s*/, '');
|
|
39
|
+
const parts = name.replace(/\\/g, '/').split('/');
|
|
40
|
+
flat.push({
|
|
41
|
+
name,
|
|
42
|
+
fullPath: name,
|
|
43
|
+
isDir,
|
|
44
|
+
isDenied,
|
|
45
|
+
isNotFound,
|
|
46
|
+
isWarning: false,
|
|
47
|
+
warningText: '',
|
|
48
|
+
depth: parts.length - 1,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const root = [];
|
|
52
|
+
const stack = [root];
|
|
53
|
+
for (const item of flat) {
|
|
54
|
+
const baseName = item.fullPath.replace(/\\/g, '/').split('/').pop() ?? item.fullPath;
|
|
55
|
+
const entry = {
|
|
56
|
+
name: baseName,
|
|
57
|
+
isDir: item.isDir,
|
|
58
|
+
isDenied: item.isDenied,
|
|
59
|
+
isNotFound: item.isNotFound,
|
|
60
|
+
isWarning: item.isWarning,
|
|
61
|
+
warningText: item.warningText,
|
|
62
|
+
children: [],
|
|
63
|
+
relativePath: item.fullPath,
|
|
64
|
+
};
|
|
65
|
+
while (stack.length > item.depth + 1) {
|
|
66
|
+
stack.pop();
|
|
67
|
+
}
|
|
68
|
+
const parent = stack[stack.length - 1];
|
|
69
|
+
parent.push(entry);
|
|
70
|
+
if (item.isDir) {
|
|
71
|
+
stack.push(entry.children);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { hint: hintLines.join('\n').trim(), entries: root };
|
|
75
|
+
}
|
|
76
|
+
let dirEntryIdCounter = 0;
|
|
77
|
+
function renderDirTree(entries, rootPath) {
|
|
78
|
+
if (entries.length === 0) {
|
|
79
|
+
return '<div class="dir-tree"><span class="dir-empty">Empty directory</span></div>';
|
|
80
|
+
}
|
|
81
|
+
function renderEntries(items) {
|
|
82
|
+
return items.map((item) => {
|
|
83
|
+
const id = `de-${dirEntryIdCounter++}`;
|
|
84
|
+
const fullPath = `${rootPath}/${item.relativePath.replace(/\\/g, '/')}`;
|
|
85
|
+
const escapedPath = escapeHtml(fullPath);
|
|
86
|
+
if (item.isWarning) {
|
|
87
|
+
return `<div class="dir-entry"><button class="dir-row dir-row-warning dir-load-more" data-loadpath="${escapedPath}"><span class="dir-warning-icon">⚠️</span> <span class="dir-warning-text">${escapeHtml(item.warningText)} — click to load all</span></button></div>`;
|
|
88
|
+
}
|
|
89
|
+
if (item.isDenied) {
|
|
90
|
+
return `<div class="dir-entry"><span class="dir-icon">🚫</span> <span class="dir-name-denied">${escapeHtml(item.name)}</span></div>`;
|
|
91
|
+
}
|
|
92
|
+
if (item.isNotFound) {
|
|
93
|
+
return `<div class="dir-entry"><span class="dir-icon">❓</span> <span class="dir-name-denied">${escapeHtml(item.name)}</span></div>`;
|
|
94
|
+
}
|
|
95
|
+
if (item.isDir) {
|
|
96
|
+
const hasChildren = item.children.length > 0;
|
|
97
|
+
const chevron = `<span class="dir-chevron${hasChildren ? ' expanded' : ''}">${hasChildren ? '▼' : '▶'}</span>`;
|
|
98
|
+
const openButton = `<button class="dir-open-btn" data-openpath="${escapedPath}" title="Open in Finder">📂</button>`;
|
|
99
|
+
const childrenHtml = hasChildren ? `<div class="dir-children" id="${id}-ch">${renderEntries(item.children)}</div>` : '';
|
|
100
|
+
return `<div class="dir-entry-group" id="${id}"><div class="dir-row dir-row-folder" data-path="${escapedPath}" data-eid="${id}" data-loaded="${hasChildren}">${chevron} <span class="dir-icon">📁</span> <span class="dir-name">${escapeHtml(item.name)}</span>${openButton}</div>${childrenHtml}</div>`;
|
|
101
|
+
}
|
|
102
|
+
return `<div class="dir-entry"><div class="dir-row dir-row-file" data-path="${escapedPath}"><span class="file-icon">📄</span> <span class="file-name">${escapeHtml(item.name)}</span></div></div>`;
|
|
103
|
+
}).join('');
|
|
104
|
+
}
|
|
105
|
+
return `<div class="dir-tree">${renderEntries(entries)}</div>`;
|
|
106
|
+
}
|
|
107
|
+
export function renderDirectoryBody(content, rootPath) {
|
|
108
|
+
dirEntryIdCounter = 0;
|
|
109
|
+
const { hint, entries } = parseDirectoryEntries(content);
|
|
110
|
+
return {
|
|
111
|
+
notice: hint || undefined,
|
|
112
|
+
html: `<div class="panel-content directory-content">${renderDirTree(entries, rootPath)}</div>`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function attachDirectoryHandlers(options) {
|
|
116
|
+
const tree = options.container.querySelector('.dir-tree');
|
|
117
|
+
if (!tree) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
tree.addEventListener('click', async (event) => {
|
|
121
|
+
const openBtn = event.target.closest('.dir-open-btn');
|
|
122
|
+
if (openBtn) {
|
|
123
|
+
event.stopPropagation();
|
|
124
|
+
const openPath = openBtn.dataset.openpath;
|
|
125
|
+
if (!openPath) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const command = options.buildOpenInFolderCommand(openPath);
|
|
129
|
+
if (command) {
|
|
130
|
+
try {
|
|
131
|
+
await options.callTool?.('start_process', { command, timeout_ms: 12000, origin: 'ui' });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Keep UI stable if opening folder fails.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const loadMoreBtn = event.target.closest('.dir-load-more');
|
|
140
|
+
if (loadMoreBtn) {
|
|
141
|
+
event.stopPropagation();
|
|
142
|
+
const loadPath = loadMoreBtn.dataset.loadpath;
|
|
143
|
+
if (!loadPath) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
loadMoreBtn.querySelector('.dir-warning-text').textContent = 'Loading…';
|
|
147
|
+
loadMoreBtn.disabled = true;
|
|
148
|
+
try {
|
|
149
|
+
const result = await options.callTool?.('list_directory', { path: loadPath, depth: 1, origin: 'ui' });
|
|
150
|
+
const text = extractToolText(result) ?? '';
|
|
151
|
+
if (text) {
|
|
152
|
+
const parsed = parseDirectoryEntries(text);
|
|
153
|
+
const html = renderDirTree(parsed.entries, loadPath);
|
|
154
|
+
const parentChildren = loadMoreBtn.closest('.dir-children');
|
|
155
|
+
if (parentChildren) {
|
|
156
|
+
const temp = document.createElement('div');
|
|
157
|
+
temp.innerHTML = html;
|
|
158
|
+
const inner = temp.querySelector('.dir-tree');
|
|
159
|
+
parentChildren.innerHTML = inner ? inner.innerHTML : '';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
loadMoreBtn.querySelector('.dir-warning-text').textContent = 'Failed to load';
|
|
165
|
+
loadMoreBtn.disabled = false;
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const target = event.target.closest('.dir-row');
|
|
170
|
+
if (!target) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const fullPath = target.dataset.path;
|
|
174
|
+
if (!fullPath) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (target.classList.contains('dir-row-folder')) {
|
|
178
|
+
const entryId = target.dataset.eid;
|
|
179
|
+
if (!entryId) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const childrenEl = document.getElementById(`${entryId}-ch`);
|
|
183
|
+
const chevron = target.querySelector('.dir-chevron');
|
|
184
|
+
if (childrenEl) {
|
|
185
|
+
const hidden = childrenEl.classList.toggle('dir-collapsed');
|
|
186
|
+
chevron?.classList.toggle('expanded', !hidden);
|
|
187
|
+
if (chevron)
|
|
188
|
+
chevron.textContent = hidden ? '▶' : '▼';
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (target.dataset.loaded === 'true') {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (chevron)
|
|
195
|
+
chevron.textContent = '⏳';
|
|
196
|
+
try {
|
|
197
|
+
const result = await options.callTool?.('list_directory', { path: fullPath, depth: 2, origin: 'ui' });
|
|
198
|
+
const text = extractToolText(result) ?? '';
|
|
199
|
+
if (text) {
|
|
200
|
+
target.dataset.loaded = 'true';
|
|
201
|
+
const parsed = parseDirectoryEntries(text);
|
|
202
|
+
const html = renderDirTree(parsed.entries, fullPath);
|
|
203
|
+
const wrapper = document.createElement('div');
|
|
204
|
+
wrapper.className = 'dir-children';
|
|
205
|
+
wrapper.id = `${entryId}-ch`;
|
|
206
|
+
const temp = document.createElement('div');
|
|
207
|
+
temp.innerHTML = html;
|
|
208
|
+
const inner = temp.querySelector('.dir-tree');
|
|
209
|
+
wrapper.innerHTML = inner ? inner.innerHTML : '<span class="dir-empty">Empty</span>';
|
|
210
|
+
target.parentElement?.appendChild(wrapper);
|
|
211
|
+
chevron?.classList.add('expanded');
|
|
212
|
+
if (chevron)
|
|
213
|
+
chevron.textContent = '▼';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
if (chevron)
|
|
218
|
+
chevron.textContent = '⚠';
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (target.classList.contains('dir-row-file')) {
|
|
223
|
+
target.classList.add('dir-loading');
|
|
224
|
+
try {
|
|
225
|
+
const result = await options.callTool?.('read_file', { path: fullPath, origin: 'ui' });
|
|
226
|
+
if (!result || typeof result !== 'object' || result === null) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const structuredContent = result.structuredContent;
|
|
230
|
+
if (structuredContent && typeof structuredContent === 'object') {
|
|
231
|
+
const text = extractToolText(result) ?? '';
|
|
232
|
+
options.onOpenPayload(buildRenderPayload(structuredContent, text));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
target.classList.remove('dir-loading');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FileTypeCapabilities, MarkdownWorkspaceState, RenderBodyResult, RenderPayload } from './model.js';
|
|
2
|
+
export declare function buildDocumentLayout(options: {
|
|
3
|
+
payload: RenderPayload;
|
|
4
|
+
body: RenderBodyResult;
|
|
5
|
+
capabilities: FileTypeCapabilities;
|
|
6
|
+
fileExtension: string;
|
|
7
|
+
htmlMode: 'rendered' | 'source';
|
|
8
|
+
currentDisplayMode: string | null;
|
|
9
|
+
isExpanded: boolean;
|
|
10
|
+
hideSummaryRow: boolean;
|
|
11
|
+
markdownWorkspace?: MarkdownWorkspaceState;
|
|
12
|
+
canGoFullscreen: boolean;
|
|
13
|
+
isMarkdownUndoAvailable: boolean;
|
|
14
|
+
defaultMarkdownEditorName?: string;
|
|
15
|
+
markdownEditorAppIcon: string;
|
|
16
|
+
hasDirectoryBackButton: boolean;
|
|
17
|
+
}): {
|
|
18
|
+
html: string;
|
|
19
|
+
effectiveExpanded: boolean;
|
|
20
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { renderCompactRow } from '../../shared/compact-row.js';
|
|
2
|
+
import { escapeHtml } from '../../shared/escape-html.js';
|
|
3
|
+
import { parseReadRange, stripReadStatusLine } from './document-workspace.js';
|
|
4
|
+
import { renderMarkdownCopyButton, renderMarkdownModeToggle } from './markdown/editor.js';
|
|
5
|
+
import { buildBreadcrumb, countContentLines } from './payload-utils.js';
|
|
6
|
+
function renderCopyIcon() {
|
|
7
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
8
|
+
}
|
|
9
|
+
function renderFolderIcon() {
|
|
10
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
|
|
11
|
+
}
|
|
12
|
+
function renderUndoIcon() {
|
|
13
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14 4 9l5-5"/><path d="M4 9h11a5 5 0 1 1 0 10h-1"/></svg>';
|
|
14
|
+
}
|
|
15
|
+
function renderExpandIcon() {
|
|
16
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>';
|
|
17
|
+
}
|
|
18
|
+
function renderMarkdownSaveStatus(workspace) {
|
|
19
|
+
if (workspace.fileDeleted) {
|
|
20
|
+
return '<span class="panel-save-status panel-save-status--saved">File deleted</span>';
|
|
21
|
+
}
|
|
22
|
+
if (workspace.saveIndicator !== 'saved') {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
const variant = workspace.saving ? 'saving' : 'saved';
|
|
26
|
+
return `<span class="panel-save-status panel-save-status--${variant}">Saved</span>`;
|
|
27
|
+
}
|
|
28
|
+
export function buildDocumentLayout(options) {
|
|
29
|
+
const range = parseReadRange(options.payload.content);
|
|
30
|
+
const notice = options.body.notice ? `<div class="notice">${options.body.notice}</div>` : '';
|
|
31
|
+
const breadcrumb = buildBreadcrumb(options.payload.filePath);
|
|
32
|
+
const lineCount = range ? range.toLine - range.fromLine + 1 : countContentLines(options.payload.content);
|
|
33
|
+
const fileTypeLabel = options.payload.fileType === 'markdown' ? 'MARKDOWN'
|
|
34
|
+
: options.payload.fileType === 'html' ? 'HTML'
|
|
35
|
+
: options.payload.fileType === 'image' ? 'IMAGE'
|
|
36
|
+
: options.payload.fileType === 'directory' ? 'DIRECTORY'
|
|
37
|
+
: options.fileExtension !== 'none' ? options.fileExtension.toUpperCase()
|
|
38
|
+
: 'TEXT';
|
|
39
|
+
const compactLabel = range?.isPartial
|
|
40
|
+
? `View lines ${range.fromLine}–${range.toLine}`
|
|
41
|
+
: options.payload.fileType === 'directory' ? 'View directory'
|
|
42
|
+
: 'View file';
|
|
43
|
+
let footerLabel = range?.isPartial
|
|
44
|
+
? `${fileTypeLabel} • LINES ${range.fromLine}–${range.toLine} OF ${range.totalLines}`
|
|
45
|
+
: `${fileTypeLabel} • ${lineCount} LINE${lineCount !== 1 ? 'S' : ''}`;
|
|
46
|
+
if (options.markdownWorkspace) {
|
|
47
|
+
const source = stripReadStatusLine(options.markdownWorkspace.draftContent);
|
|
48
|
+
const markdownWordCount = source.trim().split(/\s+/).filter(Boolean).length;
|
|
49
|
+
const markdownLineCount = countContentLines(source);
|
|
50
|
+
footerLabel = `${fileTypeLabel} • EDIT MODE • ${markdownLineCount} LINES • ${markdownWordCount} WORDS`;
|
|
51
|
+
}
|
|
52
|
+
const isFullscreen = options.currentDisplayMode === 'fullscreen';
|
|
53
|
+
const htmlToggle = options.payload.fileType === 'html'
|
|
54
|
+
? `<button class="panel-action" id="toggle-html-mode">${options.htmlMode === 'rendered' ? 'Source' : 'Rendered'}</button>`
|
|
55
|
+
: '';
|
|
56
|
+
const backButton = options.hasDirectoryBackButton && options.payload.fileType !== 'directory'
|
|
57
|
+
? '<button class="panel-action dir-back-btn" id="dir-back" title="Back to directory">← Back</button>'
|
|
58
|
+
: '';
|
|
59
|
+
const isMarkdown = options.payload.fileType === 'markdown';
|
|
60
|
+
const isMarkdownEdit = isMarkdown && !!options.markdownWorkspace;
|
|
61
|
+
const revertDisabled = isMarkdownEdit && (options.markdownWorkspace.fileDeleted || options.markdownWorkspace.loadingDocument || !options.isMarkdownUndoAvailable);
|
|
62
|
+
const fileDeleted = isMarkdownEdit && options.markdownWorkspace.fileDeleted;
|
|
63
|
+
const hasMissingBefore = range?.isPartial && range.fromLine > 1;
|
|
64
|
+
const hasMissingAfter = range?.isPartial && range.toLine < range.totalLines && (range.totalLines - range.toLine) > 1;
|
|
65
|
+
const loadBeforeBanner = hasMissingBefore
|
|
66
|
+
? `<button class="load-lines-banner" id="load-before">↑ Load lines 1–${range.fromLine - 1}</button>`
|
|
67
|
+
: '';
|
|
68
|
+
const loadAfterBanner = hasMissingAfter
|
|
69
|
+
? `<button class="load-lines-banner" id="load-after">↓ Load lines ${range.toLine + 1}–${range.totalLines}</button>`
|
|
70
|
+
: '';
|
|
71
|
+
const effectiveExpanded = options.isExpanded || isFullscreen;
|
|
72
|
+
const canOpenInFolder = options.capabilities.canOpenInFolder;
|
|
73
|
+
const canCopy = options.capabilities.canCopy;
|
|
74
|
+
return {
|
|
75
|
+
effectiveExpanded,
|
|
76
|
+
html: `
|
|
77
|
+
<main id="tool-shell" class="shell tool-shell ${effectiveExpanded ? 'expanded' : 'collapsed'}${options.hideSummaryRow ? ' host-framed' : ''}${isFullscreen ? ' fullscreen' : ''}">
|
|
78
|
+
${isFullscreen ? '' : renderCompactRow({ id: 'compact-toggle', label: compactLabel, filename: options.payload.fileName, variant: 'ready', expandable: true, expanded: options.isExpanded, interactive: true })}
|
|
79
|
+
<section class="panel">
|
|
80
|
+
<div class="panel-topbar">
|
|
81
|
+
${backButton}
|
|
82
|
+
${options.hideSummaryRow ? '' : `<span class="panel-breadcrumb" title="${escapeHtml(options.payload.filePath)}">${breadcrumb}</span>`}
|
|
83
|
+
<span class="panel-topbar-actions">
|
|
84
|
+
${isMarkdownEdit ? renderMarkdownSaveStatus(options.markdownWorkspace) : ''}
|
|
85
|
+
${htmlToggle}
|
|
86
|
+
${isMarkdownEdit && isFullscreen ? renderMarkdownModeToggle(options.markdownWorkspace.editorView) : ''}
|
|
87
|
+
${isMarkdown && !isFullscreen && options.canGoFullscreen ? `<button class="panel-action" id="expand-fullscreen" title="Expand" aria-label="Expand">${renderExpandIcon()}</button>` : ''}
|
|
88
|
+
${isMarkdownEdit ? `<button class="panel-action" id="revert-markdown" ${isFullscreen ? '' : 'title="Undo" aria-label="Undo" '}${revertDisabled ? 'disabled' : ''}>${renderUndoIcon()}${isFullscreen ? ' Undo' : ''}</button>` : ''}
|
|
89
|
+
${isMarkdownEdit && isFullscreen ? renderMarkdownCopyButton() : ''}
|
|
90
|
+
${isMarkdown && !isFullscreen ? `<button class="panel-action" id="copy-active-markdown" title="Copy" aria-label="Copy">${renderCopyIcon()}</button>` : ''}
|
|
91
|
+
${canCopy && options.capabilities.supportsPreview && !isMarkdown ? `<button class="panel-action" id="copy-source" title="Copy source" aria-label="Copy source">${renderCopyIcon()}</button>` : ''}
|
|
92
|
+
${canOpenInFolder && isMarkdownEdit && isFullscreen ? `<button class="panel-action" id="open-in-editor" ${fileDeleted ? 'disabled' : ''}>${options.markdownEditorAppIcon} Open in ${escapeHtml(options.defaultMarkdownEditorName ?? 'editor')}</button>` : ''}
|
|
93
|
+
${canOpenInFolder && !(isMarkdownEdit && isFullscreen) ? `<button class="panel-action" id="open-in-folder" title="Open in folder" aria-label="Open in folder" ${isMarkdownEdit && fileDeleted ? 'disabled' : ''}>${renderFolderIcon()}</button>` : ''}
|
|
94
|
+
</span>
|
|
95
|
+
</div>
|
|
96
|
+
${notice}
|
|
97
|
+
<div class="panel-content-wrapper">
|
|
98
|
+
${loadBeforeBanner}
|
|
99
|
+
${options.body.html}
|
|
100
|
+
${loadAfterBanner}
|
|
101
|
+
</div>
|
|
102
|
+
<div class="panel-footer">
|
|
103
|
+
<span>${footerLabel}</span>
|
|
104
|
+
</div>
|
|
105
|
+
</section>
|
|
106
|
+
</main>
|
|
107
|
+
`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface DocumentOutlineItem {
|
|
2
|
+
id: string;
|
|
3
|
+
text: string;
|
|
4
|
+
level: number;
|
|
5
|
+
line?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DocumentOutlineHandle {
|
|
8
|
+
dispose: () => void;
|
|
9
|
+
refresh: (outline: DocumentOutlineItem[], activeHeadingId?: string | null) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function renderDocumentOutline(outline: DocumentOutlineItem[], activeHeadingId?: string | null): string;
|
|
12
|
+
export declare function attachDocumentOutline(options: {
|
|
13
|
+
shell: HTMLElement;
|
|
14
|
+
outline: DocumentOutlineItem[];
|
|
15
|
+
scrollContainer: HTMLElement;
|
|
16
|
+
onSelect: (headingId: string) => void;
|
|
17
|
+
}): DocumentOutlineHandle | null;
|