@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,97 @@
|
|
|
1
|
+
import { escapeHtml } from '../../shared/escape-html.js';
|
|
2
|
+
function setActiveItem(nav, activeId) {
|
|
3
|
+
const buttons = Array.from(nav.querySelectorAll('[data-toc-id]'));
|
|
4
|
+
buttons.forEach((button) => {
|
|
5
|
+
const isActive = button.dataset.tocId === activeId;
|
|
6
|
+
button.classList.toggle('is-active', isActive);
|
|
7
|
+
button.setAttribute('aria-current', isActive ? 'location' : 'false');
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function renderDocumentOutlineItems(outline, activeHeadingId) {
|
|
11
|
+
return outline.map((item) => {
|
|
12
|
+
const activeClass = item.id === activeHeadingId ? ' is-active' : '';
|
|
13
|
+
return `<button class="document-outline-link markdown-toc-link${activeClass}" type="button" data-toc-id="${escapeHtml(item.id)}" data-level="${item.level}" aria-current="${item.id === activeHeadingId ? 'location' : 'false'}">${escapeHtml(item.text)}</button>`;
|
|
14
|
+
}).join('');
|
|
15
|
+
}
|
|
16
|
+
function renderCollapseIcon() {
|
|
17
|
+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>';
|
|
18
|
+
}
|
|
19
|
+
function renderExpandIcon() {
|
|
20
|
+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>';
|
|
21
|
+
}
|
|
22
|
+
export function renderDocumentOutline(outline, activeHeadingId) {
|
|
23
|
+
if (outline.length === 0) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
return `
|
|
27
|
+
<aside class="document-outline-shell markdown-toc-shell" aria-label="Table of contents">
|
|
28
|
+
<div class="document-outline-header markdown-toc-header">
|
|
29
|
+
<span class="document-outline-title markdown-toc-title">Contents</span>
|
|
30
|
+
<button class="document-outline-toggle" id="toc-toggle" type="button" title="Collapse sidebar" aria-label="Collapse sidebar">${renderCollapseIcon()}</button>
|
|
31
|
+
</div>
|
|
32
|
+
<nav class="document-outline-nav markdown-toc-nav">${renderDocumentOutlineItems(outline, activeHeadingId)}</nav>
|
|
33
|
+
</aside>
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
export function attachDocumentOutline(options) {
|
|
37
|
+
const nav = options.shell.querySelector('.document-outline-nav');
|
|
38
|
+
if (!nav) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
let currentOutline = options.outline;
|
|
42
|
+
const handleClick = (event) => {
|
|
43
|
+
const target = event.target;
|
|
44
|
+
const button = target?.closest('[data-toc-id]');
|
|
45
|
+
const headingId = button?.dataset.tocId;
|
|
46
|
+
if (!headingId) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
options.onSelect(headingId);
|
|
50
|
+
setActiveItem(nav, headingId);
|
|
51
|
+
};
|
|
52
|
+
const updateActiveHeading = () => {
|
|
53
|
+
const headings = currentOutline
|
|
54
|
+
.map((item) => {
|
|
55
|
+
const element = document.getElementById(item.id);
|
|
56
|
+
return element ? { item, element } : null;
|
|
57
|
+
})
|
|
58
|
+
.filter((entry) => entry !== null);
|
|
59
|
+
if (headings.length === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const scrollTop = options.scrollContainer.scrollTop;
|
|
63
|
+
const nextActive = headings.reduce((activeId, current) => {
|
|
64
|
+
if (current.element.offsetTop - scrollTop <= 96) {
|
|
65
|
+
return current.item.id;
|
|
66
|
+
}
|
|
67
|
+
return activeId;
|
|
68
|
+
}, headings[0].item.id);
|
|
69
|
+
setActiveItem(nav, nextActive);
|
|
70
|
+
};
|
|
71
|
+
const toggleButton = options.shell.querySelector('#toc-toggle');
|
|
72
|
+
const handleToggle = () => {
|
|
73
|
+
const isCollapsed = options.shell.classList.toggle('markdown-toc-collapsed');
|
|
74
|
+
if (toggleButton) {
|
|
75
|
+
toggleButton.innerHTML = isCollapsed ? renderExpandIcon() : renderCollapseIcon();
|
|
76
|
+
toggleButton.title = isCollapsed ? 'Expand sidebar' : 'Collapse sidebar';
|
|
77
|
+
toggleButton.setAttribute('aria-label', isCollapsed ? 'Expand sidebar' : 'Collapse sidebar');
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
toggleButton?.addEventListener('click', handleToggle);
|
|
81
|
+
nav.addEventListener('click', handleClick);
|
|
82
|
+
options.scrollContainer.addEventListener('scroll', updateActiveHeading, { passive: true });
|
|
83
|
+
updateActiveHeading();
|
|
84
|
+
return {
|
|
85
|
+
dispose: () => {
|
|
86
|
+
nav.removeEventListener('click', handleClick);
|
|
87
|
+
options.scrollContainer.removeEventListener('scroll', updateActiveHeading);
|
|
88
|
+
toggleButton?.removeEventListener('click', handleToggle);
|
|
89
|
+
},
|
|
90
|
+
refresh: (outline, activeHeadingId) => {
|
|
91
|
+
currentOutline = outline;
|
|
92
|
+
nav.innerHTML = renderDocumentOutlineItems(currentOutline, activeHeadingId);
|
|
93
|
+
setActiveItem(nav, activeHeadingId ?? null);
|
|
94
|
+
updateActiveHeading();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ReadRange {
|
|
2
|
+
fromLine: number;
|
|
3
|
+
toLine: number;
|
|
4
|
+
totalLines: number;
|
|
5
|
+
isPartial: boolean;
|
|
6
|
+
/** 0-based offset for read_file calls. "from start" → 0, "from line N" → N. */
|
|
7
|
+
readOffset: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function stripReadStatusLine(content: string): string;
|
|
10
|
+
export declare function parseReadRange(content: string): ReadRange | undefined;
|
|
11
|
+
export declare function getDocumentFullscreenAvailability(options: {
|
|
12
|
+
availableDisplayModes?: string[];
|
|
13
|
+
}): {
|
|
14
|
+
canFullscreen: true;
|
|
15
|
+
} | {
|
|
16
|
+
canFullscreen: false;
|
|
17
|
+
reason: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function shouldAutoLoadDocumentOnEnterFullscreen(content: string): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function stripReadStatusLine(content) {
|
|
2
|
+
return content.replace(/^\[Reading [^\]]+\]\r?\n(?:\r?\n)?/, '');
|
|
3
|
+
}
|
|
4
|
+
export function parseReadRange(content) {
|
|
5
|
+
const match = content.match(/^\[Reading (\d+) lines from (?:line )?(\d+|start) \(total: (\d+) lines/);
|
|
6
|
+
if (!match) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const count = Number.parseInt(match[1], 10);
|
|
10
|
+
const isFromStart = match[2] === 'start';
|
|
11
|
+
const readOffset = isFromStart ? 0 : Number.parseInt(match[2], 10);
|
|
12
|
+
const fromLine = isFromStart ? 1 : readOffset;
|
|
13
|
+
const totalLines = Number.parseInt(match[3], 10);
|
|
14
|
+
return {
|
|
15
|
+
fromLine,
|
|
16
|
+
toLine: fromLine + count - 1,
|
|
17
|
+
totalLines,
|
|
18
|
+
isPartial: count < totalLines,
|
|
19
|
+
readOffset,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function getDocumentFullscreenAvailability(options) {
|
|
23
|
+
if (!options.availableDisplayModes?.includes('fullscreen')) {
|
|
24
|
+
return {
|
|
25
|
+
canFullscreen: false,
|
|
26
|
+
reason: 'Fullscreen editing is unavailable in this host.',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { canFullscreen: true };
|
|
30
|
+
}
|
|
31
|
+
export function shouldAutoLoadDocumentOnEnterFullscreen(content) {
|
|
32
|
+
return parseReadRange(content)?.isPartial === true;
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FileTypeCapabilities, RenderBodyResult, RenderPayload } from './model.js';
|
|
2
|
+
import type { MarkdownController } from './markdown/controller.js';
|
|
3
|
+
import type { HtmlPreviewMode } from './types.js';
|
|
4
|
+
export declare function getFileTypeCapabilities(payload: RenderPayload): FileTypeCapabilities;
|
|
5
|
+
export declare function renderPayloadBody(options: {
|
|
6
|
+
payload: RenderPayload;
|
|
7
|
+
htmlMode: HtmlPreviewMode;
|
|
8
|
+
startLine: number;
|
|
9
|
+
markdownController: MarkdownController;
|
|
10
|
+
}): RenderBodyResult;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { formatJsonIfPossible, inferLanguageFromPath, renderCodeViewer } from './components/code-viewer.js';
|
|
2
|
+
import { escapeHtml } from './components/highlighting.js';
|
|
3
|
+
import { renderHtmlPreview } from './components/html-renderer.js';
|
|
4
|
+
import { renderDirectoryBody } from './directory-controller.js';
|
|
5
|
+
import { stripReadStatusLine } from './document-workspace.js';
|
|
6
|
+
import { isAllowedImageMimeType, normalizeImageMimeType } from './image-preview.js';
|
|
7
|
+
import { isLikelyUrl } from './payload-utils.js';
|
|
8
|
+
function renderRawFallback(source) {
|
|
9
|
+
return `<pre class="code-viewer"><code class="hljs language-text">${escapeHtml(source)}</code></pre>`;
|
|
10
|
+
}
|
|
11
|
+
function renderImageBody(payload) {
|
|
12
|
+
const mimeType = normalizeImageMimeType(payload.mimeType);
|
|
13
|
+
if (!isAllowedImageMimeType(mimeType)) {
|
|
14
|
+
return {
|
|
15
|
+
notice: 'Preview is unavailable for this image format.',
|
|
16
|
+
html: '<div class="panel-content source-content"></div>',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (!payload.content || payload.content.trim().length === 0) {
|
|
20
|
+
return {
|
|
21
|
+
notice: 'Preview is unavailable because image data is missing.',
|
|
22
|
+
html: '<div class="panel-content source-content"></div>',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const src = `data:${mimeType};base64,${payload.content}`;
|
|
26
|
+
return {
|
|
27
|
+
html: `<div class="panel-content image-content"><div class="image-preview"><img src="${escapeHtml(src)}" alt="${escapeHtml(payload.fileName)}" loading="eager" decoding="async"></div></div>`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function buildPreviewCapabilities(payload, canCopy) {
|
|
31
|
+
return {
|
|
32
|
+
supportsPreview: true,
|
|
33
|
+
canCopy,
|
|
34
|
+
canOpenInFolder: !isLikelyUrl(payload.filePath),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const handlerRegistry = {
|
|
38
|
+
directory: {
|
|
39
|
+
getCapabilities: (payload) => buildPreviewCapabilities(payload, false),
|
|
40
|
+
renderBody: ({ payload }) => renderDirectoryBody(stripReadStatusLine(payload.content), payload.filePath),
|
|
41
|
+
},
|
|
42
|
+
html: {
|
|
43
|
+
getCapabilities: (payload) => buildPreviewCapabilities(payload, true),
|
|
44
|
+
renderBody: ({ payload, htmlMode }) => renderHtmlPreview(stripReadStatusLine(payload.content), htmlMode),
|
|
45
|
+
},
|
|
46
|
+
image: {
|
|
47
|
+
getCapabilities: (payload) => buildPreviewCapabilities(payload, false),
|
|
48
|
+
renderBody: ({ payload }) => renderImageBody(payload),
|
|
49
|
+
},
|
|
50
|
+
markdown: {
|
|
51
|
+
getCapabilities: (payload) => buildPreviewCapabilities(payload, false),
|
|
52
|
+
renderBody: ({ payload, markdownController }) => {
|
|
53
|
+
try {
|
|
54
|
+
return markdownController.buildBody(payload);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {
|
|
58
|
+
notice: 'Markdown renderer failed. Showing raw source instead.',
|
|
59
|
+
html: `<div class="panel-content source-content">${renderRawFallback(stripReadStatusLine(payload.content))}</div>`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
text: {
|
|
65
|
+
getCapabilities: (payload) => buildPreviewCapabilities(payload, true),
|
|
66
|
+
renderBody: ({ payload, startLine }) => {
|
|
67
|
+
const cleanedContent = stripReadStatusLine(payload.content);
|
|
68
|
+
const detectedLanguage = inferLanguageFromPath(payload.filePath);
|
|
69
|
+
const formatted = formatJsonIfPossible(cleanedContent, payload.filePath);
|
|
70
|
+
return {
|
|
71
|
+
notice: formatted.notice,
|
|
72
|
+
html: `<div class="panel-content source-content">${renderCodeViewer(formatted.content, detectedLanguage, startLine)}</div>`,
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
unsupported: {
|
|
77
|
+
getCapabilities: (payload) => {
|
|
78
|
+
const hasRawContent = stripReadStatusLine(payload.content).trim().length > 0;
|
|
79
|
+
return {
|
|
80
|
+
supportsPreview: hasRawContent,
|
|
81
|
+
canCopy: hasRawContent,
|
|
82
|
+
canOpenInFolder: !isLikelyUrl(payload.filePath),
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
renderBody: ({ payload }) => {
|
|
86
|
+
const rawContent = stripReadStatusLine(payload.content);
|
|
87
|
+
if (rawContent.trim().length === 0) {
|
|
88
|
+
return {
|
|
89
|
+
notice: 'Preview is not available for this file type.',
|
|
90
|
+
html: '<div class="panel-content source-content"></div>',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
html: `<div class="panel-content source-content">${renderRawFallback(rawContent)}</div>`,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
export function getFileTypeCapabilities(payload) {
|
|
100
|
+
return handlerRegistry[payload.fileType]?.getCapabilities(payload) ?? {
|
|
101
|
+
supportsPreview: false,
|
|
102
|
+
canCopy: false,
|
|
103
|
+
canOpenInFolder: !isLikelyUrl(payload.filePath),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function renderPayloadBody(options) {
|
|
107
|
+
const handler = handlerRegistry[options.payload.fileType] ?? handlerRegistry.text;
|
|
108
|
+
return handler.renderBody(options);
|
|
109
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function shellQuote(value: string): string;
|
|
2
|
+
export declare function encodePowerShellCommand(script: string): string;
|
|
3
|
+
export declare function buildOpenInFolderCommand(filePath: string, isLikelyUrl: (filePath: string) => boolean): string | undefined;
|
|
4
|
+
export declare function buildOpenInEditorCommand(filePath: string, isLikelyUrl: (filePath: string) => boolean, editorAppCache: Map<string, {
|
|
5
|
+
appName: string;
|
|
6
|
+
appPath?: string;
|
|
7
|
+
}>): string | undefined;
|
|
8
|
+
export declare function renderMarkdownEditorAppIcon(): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getParentDirectory } from '../path-utils.js';
|
|
2
|
+
export function shellQuote(value) {
|
|
3
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4
|
+
}
|
|
5
|
+
export function encodePowerShellCommand(script) {
|
|
6
|
+
const utf16leBytes = [];
|
|
7
|
+
for (let index = 0; index < script.length; index += 1) {
|
|
8
|
+
const codeUnit = script.charCodeAt(index);
|
|
9
|
+
utf16leBytes.push(codeUnit & 0xff, codeUnit >> 8);
|
|
10
|
+
}
|
|
11
|
+
let binary = '';
|
|
12
|
+
for (const byte of utf16leBytes) {
|
|
13
|
+
binary += String.fromCharCode(byte);
|
|
14
|
+
}
|
|
15
|
+
return btoa(binary);
|
|
16
|
+
}
|
|
17
|
+
export function buildOpenInFolderCommand(filePath, isLikelyUrl) {
|
|
18
|
+
const trimmedPath = filePath.trim();
|
|
19
|
+
if (!trimmedPath || isLikelyUrl(trimmedPath)) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
23
|
+
if (userAgent.includes('win')) {
|
|
24
|
+
const escapedForPowerShell = trimmedPath.replace(/'/g, "''");
|
|
25
|
+
const script = `Start-Process -FilePath explorer.exe -ArgumentList @('/select,','${escapedForPowerShell}')`;
|
|
26
|
+
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encodePowerShellCommand(script)}`;
|
|
27
|
+
}
|
|
28
|
+
if (userAgent.includes('mac')) {
|
|
29
|
+
return `open -R ${shellQuote(trimmedPath)}`;
|
|
30
|
+
}
|
|
31
|
+
return `xdg-open ${shellQuote(getParentDirectory(trimmedPath))}`;
|
|
32
|
+
}
|
|
33
|
+
export function buildOpenInEditorCommand(filePath, isLikelyUrl, editorAppCache) {
|
|
34
|
+
const trimmedPath = filePath.trim();
|
|
35
|
+
if (!trimmedPath || isLikelyUrl(trimmedPath)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const cachedApp = editorAppCache.get(trimmedPath);
|
|
39
|
+
if (cachedApp?.appPath && navigator.userAgent.toLowerCase().includes('mac')) {
|
|
40
|
+
return `open -a ${shellQuote(cachedApp.appPath)} ${shellQuote(trimmedPath)}`;
|
|
41
|
+
}
|
|
42
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
43
|
+
if (userAgent.includes('win')) {
|
|
44
|
+
const escapedForPowerShell = trimmedPath.replace(/'/g, "''");
|
|
45
|
+
const script = `Start-Process -FilePath '${escapedForPowerShell}'`;
|
|
46
|
+
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encodePowerShellCommand(script)}`;
|
|
47
|
+
}
|
|
48
|
+
if (userAgent.includes('mac')) {
|
|
49
|
+
return `open ${shellQuote(trimmedPath)}`;
|
|
50
|
+
}
|
|
51
|
+
return `xdg-open ${shellQuote(trimmedPath)}`;
|
|
52
|
+
}
|
|
53
|
+
export function renderMarkdownEditorAppIcon() {
|
|
54
|
+
return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 1 1 3 3L7 19l-4 1 1-4Z"/></svg>';
|
|
55
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderPayload } from '../model.js';
|
|
2
|
+
export declare function attachSelectionContext(options: {
|
|
3
|
+
payload: RenderPayload;
|
|
4
|
+
isMarkdownEditing: boolean;
|
|
5
|
+
updateContext?: (text: string) => void;
|
|
6
|
+
trackUiEvent?: (event: string, params?: Record<string, unknown>) => void;
|
|
7
|
+
getFileExtensionForAnalytics: (filePath: string) => string;
|
|
8
|
+
previousAbortController: AbortController | null;
|
|
9
|
+
}): AbortController | null;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export function attachSelectionContext(options) {
|
|
2
|
+
if (options.isMarkdownEditing) {
|
|
3
|
+
if (options.previousAbortController) {
|
|
4
|
+
options.previousAbortController.abort();
|
|
5
|
+
}
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const contentWrapper = document.querySelector('.panel-content-wrapper');
|
|
9
|
+
if (!contentWrapper) {
|
|
10
|
+
return options.previousAbortController;
|
|
11
|
+
}
|
|
12
|
+
const wrapper = contentWrapper;
|
|
13
|
+
options.previousAbortController?.abort();
|
|
14
|
+
const abortController = new AbortController();
|
|
15
|
+
let hintEl = null;
|
|
16
|
+
let lastSelectedText = '';
|
|
17
|
+
let hideTimer = null;
|
|
18
|
+
function positionHint(selection) {
|
|
19
|
+
if (!hintEl)
|
|
20
|
+
return;
|
|
21
|
+
const range = selection.getRangeAt(0);
|
|
22
|
+
const rect = range.getBoundingClientRect();
|
|
23
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
24
|
+
let left = rect.left + rect.width / 2 - wrapperRect.left;
|
|
25
|
+
let top = rect.top - wrapperRect.top + wrapper.scrollTop - 32;
|
|
26
|
+
const hintWidth = hintEl.offsetWidth || 200;
|
|
27
|
+
left = Math.max(8, Math.min(left - hintWidth / 2, wrapper.clientWidth - hintWidth - 8));
|
|
28
|
+
top = Math.max(4, top);
|
|
29
|
+
hintEl.style.left = `${left}px`;
|
|
30
|
+
hintEl.style.top = `${top}px`;
|
|
31
|
+
}
|
|
32
|
+
function showHint(selection) {
|
|
33
|
+
if (hideTimer) {
|
|
34
|
+
clearTimeout(hideTimer);
|
|
35
|
+
hideTimer = null;
|
|
36
|
+
}
|
|
37
|
+
if (!hintEl) {
|
|
38
|
+
hintEl = document.createElement('div');
|
|
39
|
+
hintEl.className = 'selection-hint';
|
|
40
|
+
hintEl.textContent = 'AI can see your selection';
|
|
41
|
+
wrapper.appendChild(hintEl);
|
|
42
|
+
}
|
|
43
|
+
hintEl.classList.add('visible');
|
|
44
|
+
positionHint(selection);
|
|
45
|
+
}
|
|
46
|
+
function hideHint() {
|
|
47
|
+
if (!hintEl)
|
|
48
|
+
return;
|
|
49
|
+
hintEl.classList.remove('visible');
|
|
50
|
+
hideTimer = setTimeout(() => {
|
|
51
|
+
hintEl?.remove();
|
|
52
|
+
hintEl = null;
|
|
53
|
+
}, 200);
|
|
54
|
+
}
|
|
55
|
+
function getLineInfo(selection) {
|
|
56
|
+
const anchorRow = selection.anchorNode?.parentElement?.closest('.code-line');
|
|
57
|
+
const focusRow = selection.focusNode?.parentElement?.closest('.code-line');
|
|
58
|
+
if (anchorRow && focusRow) {
|
|
59
|
+
const anchorLine = parseInt(anchorRow.dataset.line ?? '', 10);
|
|
60
|
+
const focusLine = parseInt(focusRow.dataset.line ?? '', 10);
|
|
61
|
+
if (!isNaN(anchorLine) && !isNaN(focusLine)) {
|
|
62
|
+
const low = Math.min(anchorLine, focusLine);
|
|
63
|
+
const high = Math.max(anchorLine, focusLine);
|
|
64
|
+
return low === high ? `line ${low}` : `lines ${low}–${high}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
document.addEventListener('selectionchange', () => {
|
|
70
|
+
const selection = document.getSelection();
|
|
71
|
+
if (!selection || selection.isCollapsed) {
|
|
72
|
+
if (lastSelectedText) {
|
|
73
|
+
lastSelectedText = '';
|
|
74
|
+
options.updateContext?.('');
|
|
75
|
+
hideHint();
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const text = selection.toString().trim();
|
|
80
|
+
if (!text || text === lastSelectedText) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const anchorInContent = wrapper.contains(selection.anchorNode);
|
|
84
|
+
const focusInContent = wrapper.contains(selection.focusNode);
|
|
85
|
+
if (!anchorInContent && !focusInContent) {
|
|
86
|
+
if (lastSelectedText) {
|
|
87
|
+
lastSelectedText = '';
|
|
88
|
+
options.updateContext?.('');
|
|
89
|
+
hideHint();
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
lastSelectedText = text;
|
|
94
|
+
const lineInfo = getLineInfo(selection);
|
|
95
|
+
const locationPart = lineInfo ? ` (${lineInfo})` : '';
|
|
96
|
+
const context = `User selected text from file ${options.payload.filePath}${locationPart}:\n\`\`\`\n${text}\n\`\`\``;
|
|
97
|
+
options.updateContext?.(context);
|
|
98
|
+
showHint(selection);
|
|
99
|
+
options.trackUiEvent?.('text_selected', {
|
|
100
|
+
file_type: options.payload.fileType,
|
|
101
|
+
file_extension: options.getFileExtensionForAnalytics(options.payload.filePath),
|
|
102
|
+
char_count: text.length,
|
|
103
|
+
});
|
|
104
|
+
}, { signal: abortController.signal });
|
|
105
|
+
return abortController;
|
|
106
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const ALLOWED_IMAGE_MIME_TYPES = new Set([
|
|
2
|
+
'image/png',
|
|
3
|
+
'image/jpeg',
|
|
4
|
+
'image/gif',
|
|
5
|
+
'image/webp',
|
|
6
|
+
'image/bmp',
|
|
7
|
+
// Intentional product decision: allow SVG rendering in preview UI.
|
|
8
|
+
// This is currently unsanitized and should be revisited if threat model changes.
|
|
9
|
+
'image/svg',
|
|
10
|
+
'image/svg+xml'
|
|
11
|
+
]);
|
|
12
|
+
export function normalizeImageMimeType(mimeType) {
|
|
13
|
+
if (!mimeType) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
return mimeType.toLowerCase().split(';', 1)[0].trim();
|
|
17
|
+
}
|
|
18
|
+
export function isAllowedImageMimeType(mimeType) {
|
|
19
|
+
const normalizedMimeType = normalizeImageMimeType(mimeType);
|
|
20
|
+
return normalizedMimeType.length > 0 && ALLOWED_IMAGE_MIME_TYPES.has(normalizedMimeType);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "file changed on disk" conflict resolver.
|
|
3
|
+
*
|
|
4
|
+
* Shown when saveDocument detected that disk differs from what the editor
|
|
5
|
+
* thought it had. The editor has already re-synced its sourceContent to the
|
|
6
|
+
* fresh disk content with keepDraft: true — so the dialog's two actions map
|
|
7
|
+
* onto concrete state transitions:
|
|
8
|
+
*
|
|
9
|
+
* "Use disk version" — replace the draft with disk content
|
|
10
|
+
* (syncStateFromContent without keepDraft).
|
|
11
|
+
* Destroys unsaved edits.
|
|
12
|
+
*
|
|
13
|
+
* "Save my changes" — close the dialog and re-run saveDocument.
|
|
14
|
+
* computeEditBlocks will now diff against the fresh
|
|
15
|
+
* disk content, so non-overlapping edits merge in
|
|
16
|
+
* and overlapping edits win over disk for the
|
|
17
|
+
* lines the user actually touched.
|
|
18
|
+
*
|
|
19
|
+
* The dialog is modal (dimmed backdrop, keyboard-trapped, click-outside does
|
|
20
|
+
* not dismiss). Escape and the ✕ button both close it without taking either
|
|
21
|
+
* action — equivalent to "I'll deal with this later"; the save button stays
|
|
22
|
+
* dirty so the user can retry or keep editing.
|
|
23
|
+
*/
|
|
24
|
+
export interface OpenConflictDialogOptions {
|
|
25
|
+
fileName: string;
|
|
26
|
+
onUseDiskVersion: () => void;
|
|
27
|
+
onSaveMyChanges: () => void;
|
|
28
|
+
onCancel?: () => void;
|
|
29
|
+
}
|
|
30
|
+
export interface ConflictDialogController {
|
|
31
|
+
open: (options: OpenConflictDialogOptions) => void;
|
|
32
|
+
close: () => void;
|
|
33
|
+
isOpen: () => boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function renderConflictDialogMarkup(): string;
|
|
36
|
+
interface CreateConflictDialogOptions {
|
|
37
|
+
container: ParentNode;
|
|
38
|
+
}
|
|
39
|
+
export declare function createConflictDialogController(options: CreateConflictDialogOptions): ConflictDialogController;
|
|
40
|
+
export {};
|