@rooode/dsh-plugin-preview 0.1.19 → 0.1.21
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/lib/client.js +148 -22
- package/lib/index.js +52 -14
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
const STORAGE_KEY_TREE_OPEN = 'dsh:preview:tree:open:v1';
|
|
28
28
|
const STORAGE_KEY_TREE_WIDTH = 'dsh:preview:tree:width:v1';
|
|
29
29
|
const STORAGE_KEY_MANUAL_WS = 'dsh:preview:manual_workspace:v1';
|
|
30
|
+
const STORAGE_KEY_SHOW_HIDDEN = 'dsh:preview:tree:show_hidden:v1';
|
|
30
31
|
const DEFAULT_PANEL_WIDTH = 700;
|
|
31
32
|
const MIN_PANEL_WIDTH = 380;
|
|
32
33
|
const DEFAULT_TREE_WIDTH = 220;
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
let globalIsPanelOpen = false;
|
|
56
57
|
let globalIsTreeOpen = true;
|
|
57
58
|
let globalIsMaximized = false;
|
|
59
|
+
let globalShowHidden = true;
|
|
58
60
|
let globalManualWorkspace = null;
|
|
59
61
|
let globalPanelWidth = DEFAULT_PANEL_WIDTH;
|
|
60
62
|
let globalTreeWidth = DEFAULT_TREE_WIDTH;
|
|
@@ -73,6 +75,13 @@
|
|
|
73
75
|
}
|
|
74
76
|
} catch (e) {}
|
|
75
77
|
|
|
78
|
+
try {
|
|
79
|
+
const savedShowHidden = localStorage.getItem(STORAGE_KEY_SHOW_HIDDEN);
|
|
80
|
+
if (savedShowHidden !== null) {
|
|
81
|
+
globalShowHidden = savedShowHidden === 'true';
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {}
|
|
84
|
+
|
|
76
85
|
try {
|
|
77
86
|
const savedWidth = localStorage.getItem(STORAGE_KEY_WIDTH);
|
|
78
87
|
if (savedWidth) {
|
|
@@ -100,6 +109,14 @@
|
|
|
100
109
|
}
|
|
101
110
|
} catch (e) {}
|
|
102
111
|
|
|
112
|
+
function setGlobalShowHidden(show) {
|
|
113
|
+
globalShowHidden = !!show;
|
|
114
|
+
try {
|
|
115
|
+
localStorage.setItem(STORAGE_KEY_SHOW_HIDDEN, String(globalShowHidden));
|
|
116
|
+
} catch (e) {}
|
|
117
|
+
notifyStateChange();
|
|
118
|
+
}
|
|
119
|
+
|
|
103
120
|
function loadAllWorkspaceTabsFromStorage() {
|
|
104
121
|
try {
|
|
105
122
|
const raw = localStorage.getItem(STORAGE_KEY_WS_TABS);
|
|
@@ -247,10 +264,23 @@
|
|
|
247
264
|
return parts[parts.length - 1] || filePath;
|
|
248
265
|
}
|
|
249
266
|
|
|
267
|
+
const KNOWN_TEXT_DOTFILES = new Set([
|
|
268
|
+
'.gitignore', '.gitattributes', '.gitmodules', '.gitconfig',
|
|
269
|
+
'.dockerignore', 'dockerfile', '.editorconfig',
|
|
270
|
+
'.prettierrc', '.eslintrc', '.eslintignore', '.prettierignore',
|
|
271
|
+
'.npmrc', '.nvmrc', '.yarnrc', '.babelrc', '.swcrc',
|
|
272
|
+
'.cursorrules', '.commitlintrc', '.env',
|
|
273
|
+
'license', 'makefile', 'gemfile', 'procfile', 'caddyfile'
|
|
274
|
+
]);
|
|
275
|
+
|
|
250
276
|
function isPreviewableFile(filePath) {
|
|
251
277
|
if (!filePath) return false;
|
|
278
|
+
const cleanName = getFileName(filePath).toLowerCase();
|
|
279
|
+
if (KNOWN_TEXT_DOTFILES.has(cleanName) || cleanName.startsWith('.env')) return true;
|
|
252
280
|
const ext = getFileExtension(filePath);
|
|
253
|
-
|
|
281
|
+
if (SUPPORTED_EXTENSIONS.has(ext)) return true;
|
|
282
|
+
if (cleanName.startsWith('.') && !['.png', '.jpg', '.jpeg', '.gif', '.zip', '.tar', '.gz', '.exe', '.dll', '.bin', '.pdf'].includes(ext)) return true;
|
|
283
|
+
return false;
|
|
254
284
|
}
|
|
255
285
|
|
|
256
286
|
function formatBytes(bytes) {
|
|
@@ -263,8 +293,20 @@
|
|
|
263
293
|
|
|
264
294
|
function getFileIcon(node) {
|
|
265
295
|
if (node.isDir) {
|
|
296
|
+
const dName = (node.name || '').toLowerCase();
|
|
297
|
+
if (dName === '.git') return { icon: '🌿', color: '#f05032' };
|
|
298
|
+
if (dName === '.github') return { icon: '🐙', color: '#8b5cf6' };
|
|
299
|
+
if (dName === '.vscode' || dName === '.idea' || dName === '.cursor') return { icon: '⚙️', color: '#38bdf8' };
|
|
266
300
|
return { icon: '📁', color: '#eab308' };
|
|
267
301
|
}
|
|
302
|
+
const rawName = (node.name || '').toLowerCase();
|
|
303
|
+
if (rawName.startsWith('.env')) return { icon: '🔒', color: '#10b981' };
|
|
304
|
+
if (rawName.startsWith('.git')) return { icon: '🌿', color: '#f05032' };
|
|
305
|
+
if (rawName === '.dockerignore' || rawName === 'dockerfile') return { icon: '🐳', color: '#2496ed' };
|
|
306
|
+
if (rawName.startsWith('.prettier') || rawName.startsWith('.eslint')) return { icon: '✨', color: '#f59e0b' };
|
|
307
|
+
if (rawName === '.npmrc' || rawName === '.nvmrc' || rawName === '.yarnrc') return { icon: '📦', color: '#cb3837' };
|
|
308
|
+
if (rawName === '.editorconfig' || rawName === '.cursorrules' || rawName === '.commitlintrc') return { icon: '⚙️', color: '#0284c7' };
|
|
309
|
+
|
|
268
310
|
const ext = (node.extension || getFileExtension(node.name || node.path || '')).toLowerCase();
|
|
269
311
|
if (ext === '.md' || ext === '.markdown') return { icon: '📝', color: '#0284c7' };
|
|
270
312
|
if (ext === '.json') return { icon: '{ }', color: '#10b981' };
|
|
@@ -287,9 +329,16 @@
|
|
|
287
329
|
return { icon: '📄', color: '#94a3b8' };
|
|
288
330
|
}
|
|
289
331
|
|
|
290
|
-
|
|
291
332
|
function getFileTypeCategory(filePath) {
|
|
292
333
|
if (!filePath) return 'text';
|
|
334
|
+
const cleanName = getFileName(filePath).toLowerCase();
|
|
335
|
+
if (cleanName.startsWith('.env') || cleanName === '.gitignore' || cleanName === '.gitattributes' || cleanName === '.dockerignore' || cleanName === '.editorconfig' || cleanName === '.npmrc' || cleanName === '.nvmrc' || cleanName === '.cursorrules') return 'config';
|
|
336
|
+
if (cleanName.startsWith('.prettier') || cleanName.startsWith('.eslint')) {
|
|
337
|
+
if (cleanName.endsWith('.json')) return 'json';
|
|
338
|
+
if (cleanName.endsWith('.js') || cleanName.endsWith('.cjs')) return 'javascript';
|
|
339
|
+
if (cleanName.endsWith('.yml') || cleanName.endsWith('.yaml')) return 'yaml';
|
|
340
|
+
return 'json';
|
|
341
|
+
}
|
|
293
342
|
const ext = getFileExtension(filePath).toLowerCase();
|
|
294
343
|
if (['.md', '.markdown', '.mdown', '.mkdn', '.mdwn'].includes(ext)) return 'markdown';
|
|
295
344
|
if (['.json', '.jsonc', '.json5', '.geojson', '.lock'].includes(ext)) return 'json';
|
|
@@ -958,7 +1007,7 @@
|
|
|
958
1007
|
return t.split('|').map(c => c.trim());
|
|
959
1008
|
};
|
|
960
1009
|
|
|
961
|
-
const tableRegex = /^(
|
|
1010
|
+
const tableRegex = /^([ \t]*\|?.+\|.*)\n([ \t]*\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)+\|?[ \t]*)(?:\n((?:[ \t]*\|.*\|.*(?:\n|$))*))?/gm;
|
|
962
1011
|
text = text.replace(tableRegex, (match, headerLine, alignLine, bodyLines) => {
|
|
963
1012
|
const headerCells = parseTableRow(headerLine);
|
|
964
1013
|
const alignCells = parseTableRow(alignLine);
|
|
@@ -975,16 +1024,18 @@
|
|
|
975
1024
|
});
|
|
976
1025
|
tableHtml += '</tr></thead><tbody>';
|
|
977
1026
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1027
|
+
if (bodyLines) {
|
|
1028
|
+
const rows = bodyLines.trim().split('\n');
|
|
1029
|
+
for (const row of rows) {
|
|
1030
|
+
if (!row.trim()) continue;
|
|
1031
|
+
const cells = parseTableRow(row);
|
|
1032
|
+
tableHtml += '<tr>';
|
|
1033
|
+
cells.forEach((cell, i) => {
|
|
1034
|
+
const align = aligns[i] || 'left';
|
|
1035
|
+
tableHtml += `<td style="text-align:${align}">${formatInlineStyles(cell)}</td>`;
|
|
1036
|
+
});
|
|
1037
|
+
tableHtml += '</tr>';
|
|
1038
|
+
}
|
|
988
1039
|
}
|
|
989
1040
|
tableHtml += '</tbody></table></div>\n\n';
|
|
990
1041
|
return tableHtml;
|
|
@@ -1276,6 +1327,41 @@
|
|
|
1276
1327
|
);
|
|
1277
1328
|
}
|
|
1278
1329
|
|
|
1330
|
+
function IconEye() {
|
|
1331
|
+
return h('svg', {
|
|
1332
|
+
width: 13,
|
|
1333
|
+
height: 13,
|
|
1334
|
+
viewBox: '0 0 16 16',
|
|
1335
|
+
fill: 'none',
|
|
1336
|
+
stroke: 'currentColor',
|
|
1337
|
+
strokeWidth: 1.4,
|
|
1338
|
+
strokeLinecap: 'round',
|
|
1339
|
+
strokeLinejoin: 'round',
|
|
1340
|
+
style: { display: 'block', flexShrink: 0 }
|
|
1341
|
+
},
|
|
1342
|
+
h('path', { d: 'M1.5 8s2.5-4.5 6.5-4.5 6.5 4.5 6.5 4.5-2.5 4.5-6.5 4.5S1.5 8 1.5 8z' }),
|
|
1343
|
+
h('circle', { cx: 8, cy: 8, r: 2.2 })
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function IconEyeOff() {
|
|
1348
|
+
return h('svg', {
|
|
1349
|
+
width: 13,
|
|
1350
|
+
height: 13,
|
|
1351
|
+
viewBox: '0 0 16 16',
|
|
1352
|
+
fill: 'none',
|
|
1353
|
+
stroke: 'currentColor',
|
|
1354
|
+
strokeWidth: 1.4,
|
|
1355
|
+
strokeLinecap: 'round',
|
|
1356
|
+
strokeLinejoin: 'round',
|
|
1357
|
+
style: { display: 'block', flexShrink: 0 }
|
|
1358
|
+
},
|
|
1359
|
+
h('path', { d: 'M2 8s2.2-3.8 6-3.8c1.5 0 2.8.6 3.9 1.5M14 8s-2.2 3.8-6 3.8c-1.5 0-2.8-.6-3.9-1.5' }),
|
|
1360
|
+
h('circle', { cx: 8, cy: 8, r: 2.2 }),
|
|
1361
|
+
h('line', { x1: 2, y1: 2, x2: 14, y2: 14 })
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1279
1365
|
function IconChevronRight() {
|
|
1280
1366
|
return h('svg', {
|
|
1281
1367
|
width: 9,
|
|
@@ -1451,7 +1537,7 @@
|
|
|
1451
1537
|
);
|
|
1452
1538
|
}
|
|
1453
1539
|
|
|
1454
|
-
function TreeContextMenu({ x, y, node, isRoot, clipboard, onAction, onClose }) {
|
|
1540
|
+
function TreeContextMenu({ x, y, node, isRoot, showHidden, clipboard, onAction, onClose }) {
|
|
1455
1541
|
const menuRef = useRef(null);
|
|
1456
1542
|
useEffect(() => {
|
|
1457
1543
|
const handleDown = (e) => {
|
|
@@ -1533,6 +1619,12 @@
|
|
|
1533
1619
|
)
|
|
1534
1620
|
] : null,
|
|
1535
1621
|
|
|
1622
|
+
h('div', { key: 'div-hidden', className: 'dsh-menu-divider' }),
|
|
1623
|
+
h('div', { key: 'toggle-hidden', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('toggle-hidden'); } },
|
|
1624
|
+
h('span', { className: 'dsh-menu-icon' }, showHidden ? '👁️' : '🙈'),
|
|
1625
|
+
h('span', null, showHidden ? '隐藏点文件与隐藏项' : '显示点文件与隐藏项')
|
|
1626
|
+
),
|
|
1627
|
+
|
|
1536
1628
|
isRoot ? [
|
|
1537
1629
|
h('div', { key: 'refresh', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('refresh'); } },
|
|
1538
1630
|
h('span', { className: 'dsh-menu-icon' }, '🔄'),
|
|
@@ -1634,7 +1726,7 @@
|
|
|
1634
1726
|
|
|
1635
1727
|
return h(Fragment, null,
|
|
1636
1728
|
h('div', {
|
|
1637
|
-
className: `dsh-tree-node-row ${isDir ? 'is-dir' : 'is-file'} ${isActive ? 'active' : ''} ${isDropTarget ? 'drop-target' : ''} ${isDragged ? 'is-dragged' : ''} ${isCut ? 'is-cut' : ''}`,
|
|
1729
|
+
className: `dsh-tree-node-row ${isDir ? 'is-dir' : 'is-file'} ${isActive ? 'active' : ''} ${isDropTarget ? 'drop-target' : ''} ${isDragged ? 'is-dragged' : ''} ${isCut ? 'is-cut' : ''} ${node.isHidden ? 'is-hidden' : ''}`,
|
|
1638
1730
|
style: indentStyle,
|
|
1639
1731
|
onClick: handleClick,
|
|
1640
1732
|
onContextMenu: handleContextMenu,
|
|
@@ -1644,7 +1736,7 @@
|
|
|
1644
1736
|
onDragOver: handleDragOver,
|
|
1645
1737
|
onDragLeave: handleDragLeave,
|
|
1646
1738
|
onDrop: handleDrop,
|
|
1647
|
-
title: `${node.name}\n${node.path}${node.size ? ' (' + formatBytes(node.size) + ')' : ''}`,
|
|
1739
|
+
title: `${node.name}${node.isHidden ? ' (隐藏项)' : ''}\n${node.path}${node.size ? ' (' + formatBytes(node.size) + ')' : ''}`,
|
|
1648
1740
|
},
|
|
1649
1741
|
// Chevron indicator for directory
|
|
1650
1742
|
isDir ? h('span', {
|
|
@@ -1771,6 +1863,7 @@
|
|
|
1771
1863
|
const [clipboard, setClipboard] = useState(null); // { action: 'cut', path: string, name: string, isDir: boolean } | null
|
|
1772
1864
|
|
|
1773
1865
|
const [currentWs, setCurrentWs] = useState(getCurrentWorkspaceRoot());
|
|
1866
|
+
const [showHidden, setShowHidden] = useState(globalShowHidden);
|
|
1774
1867
|
|
|
1775
1868
|
useEffect(() => {
|
|
1776
1869
|
const handleStateUpdate = () => {
|
|
@@ -1778,10 +1871,13 @@
|
|
|
1778
1871
|
if (newWs !== currentWs) {
|
|
1779
1872
|
setCurrentWs(newWs);
|
|
1780
1873
|
}
|
|
1874
|
+
if (globalShowHidden !== showHidden) {
|
|
1875
|
+
setShowHidden(globalShowHidden);
|
|
1876
|
+
}
|
|
1781
1877
|
};
|
|
1782
1878
|
stateListeners.add(handleStateUpdate);
|
|
1783
1879
|
return () => stateListeners.delete(handleStateUpdate);
|
|
1784
|
-
}, [currentWs]);
|
|
1880
|
+
}, [currentWs, showHidden]);
|
|
1785
1881
|
|
|
1786
1882
|
const loadTree = useCallback(async (force = false, targetWs) => {
|
|
1787
1883
|
setIsLoading(true);
|
|
@@ -1793,26 +1889,29 @@
|
|
|
1793
1889
|
return;
|
|
1794
1890
|
}
|
|
1795
1891
|
const query = encodeURIComponent(rootPath);
|
|
1796
|
-
const res = await fetch(`/api/preview/workspace-tree?path=${query}`);
|
|
1892
|
+
const res = await fetch(`/api/preview/workspace-tree?path=${query}&showHidden=${showHidden}`);
|
|
1797
1893
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1798
1894
|
const json = await res.json();
|
|
1799
1895
|
if (!json.ok) throw new Error(json?.error?.message || '读取工作区目录失败');
|
|
1800
1896
|
setTreeData(json);
|
|
1801
|
-
// By default expand top 1 level directories
|
|
1897
|
+
// By default expand top 1 level directories if not already expanded
|
|
1802
1898
|
if (json.tree) {
|
|
1803
1899
|
const topDirs = json.tree.filter(n => n.isDir).map(n => n.path);
|
|
1804
|
-
setExpandedPaths(
|
|
1900
|
+
setExpandedPaths(prev => {
|
|
1901
|
+
if (prev.size === 0) return new Set(topDirs);
|
|
1902
|
+
return prev;
|
|
1903
|
+
});
|
|
1805
1904
|
}
|
|
1806
1905
|
} catch (err) {
|
|
1807
1906
|
setError(err.message || '加载工作空间文件树失败');
|
|
1808
1907
|
} finally {
|
|
1809
1908
|
setIsLoading(false);
|
|
1810
1909
|
}
|
|
1811
|
-
}, [currentWs]);
|
|
1910
|
+
}, [currentWs, showHidden]);
|
|
1812
1911
|
|
|
1813
1912
|
useEffect(() => {
|
|
1814
1913
|
loadTree(false, currentWs);
|
|
1815
|
-
}, [loadTree, currentWs, globalManualWorkspace]);
|
|
1914
|
+
}, [loadTree, currentWs, showHidden, globalManualWorkspace]);
|
|
1816
1915
|
|
|
1817
1916
|
const handleToggleExpand = (dirPath) => {
|
|
1818
1917
|
setExpandedPaths(prev => {
|
|
@@ -2059,11 +2158,20 @@
|
|
|
2059
2158
|
if (node) triggerOpenNative(node.path);
|
|
2060
2159
|
} else if (action === 'delete') {
|
|
2061
2160
|
if (node) handleDelete(node);
|
|
2161
|
+
} else if (action === 'toggle-hidden') {
|
|
2162
|
+
handleToggleShowHidden();
|
|
2062
2163
|
} else if (action === 'refresh') {
|
|
2063
2164
|
loadTree(true);
|
|
2064
2165
|
}
|
|
2065
2166
|
};
|
|
2066
2167
|
|
|
2168
|
+
const handleToggleShowHidden = () => {
|
|
2169
|
+
const next = !showHidden;
|
|
2170
|
+
setShowHidden(next);
|
|
2171
|
+
setGlobalShowHidden(next);
|
|
2172
|
+
showToast(next ? '已开启显示隐藏文件与点文件' : '已隐藏点文件与隐藏项', 'info');
|
|
2173
|
+
};
|
|
2174
|
+
|
|
2067
2175
|
const rootPath = treeData?.workspaceRoot || currentWs;
|
|
2068
2176
|
const isCreatingAtRoot = creatingItem && creatingItem.parentPath && rootPath && creatingItem.parentPath.replace(/\\/g, '/').toLowerCase() === rootPath.replace(/\\/g, '/').toLowerCase();
|
|
2069
2177
|
const isRootDropTarget = dragOverNode && rootPath && dragOverNode.path && dragOverNode.path.replace(/\\/g, '/').toLowerCase() === rootPath.replace(/\\/g, '/').toLowerCase();
|
|
@@ -2096,6 +2204,12 @@
|
|
|
2096
2204
|
onClick: () => handleStartCreate(rootPath, 'dir'),
|
|
2097
2205
|
title: '新建文件夹 (在根目录)',
|
|
2098
2206
|
}, h(IconNewFolder)),
|
|
2207
|
+
h('button', {
|
|
2208
|
+
type: 'button',
|
|
2209
|
+
className: `dsh-tree-action-btn ${showHidden ? 'active' : ''}`,
|
|
2210
|
+
onClick: handleToggleShowHidden,
|
|
2211
|
+
title: showHidden ? '隐藏点文件与隐藏项 (当前已显示)' : '显示点文件与隐藏项 (当前已隐藏)',
|
|
2212
|
+
}, showHidden ? h(IconEye) : h(IconEyeOff)),
|
|
2099
2213
|
h('button', {
|
|
2100
2214
|
type: 'button',
|
|
2101
2215
|
className: 'dsh-tree-action-btn',
|
|
@@ -2287,6 +2401,7 @@
|
|
|
2287
2401
|
y: contextMenu.y,
|
|
2288
2402
|
node: contextMenu.node,
|
|
2289
2403
|
isRoot: contextMenu.isRoot,
|
|
2404
|
+
showHidden,
|
|
2290
2405
|
clipboard,
|
|
2291
2406
|
onAction: handleContextMenuAction,
|
|
2292
2407
|
onClose: () => setContextMenu(null),
|
|
@@ -5607,6 +5722,10 @@
|
|
|
5607
5722
|
background: rgba(255, 255, 255, 0.1);
|
|
5608
5723
|
color: #ffffff;
|
|
5609
5724
|
}
|
|
5725
|
+
.dsh-tree-action-btn.active {
|
|
5726
|
+
color: var(--dsw-static-deepseek-500, #3b82f6);
|
|
5727
|
+
background: rgba(59, 130, 246, 0.12);
|
|
5728
|
+
}
|
|
5610
5729
|
|
|
5611
5730
|
.dsh-tree-sidebar-search {
|
|
5612
5731
|
padding: 6px 8px;
|
|
@@ -5722,6 +5841,13 @@
|
|
|
5722
5841
|
background: rgba(56, 189, 248, 0.16);
|
|
5723
5842
|
color: #38bdf8;
|
|
5724
5843
|
}
|
|
5844
|
+
.dsh-tree-node-row.is-hidden {
|
|
5845
|
+
opacity: 0.72;
|
|
5846
|
+
}
|
|
5847
|
+
.dsh-tree-node-row.is-hidden:hover,
|
|
5848
|
+
.dsh-tree-node-row.is-hidden.active {
|
|
5849
|
+
opacity: 1;
|
|
5850
|
+
}
|
|
5725
5851
|
|
|
5726
5852
|
.dsh-tree-chevron {
|
|
5727
5853
|
width: 14px;
|
package/lib/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export class PreviewService extends Service {
|
|
|
29
29
|
handler: this.handleHttpRequest.bind(this),
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
|
-
console.log('[PreviewService] Registered /api/preview route on webServer (v0.1.
|
|
32
|
+
console.log('[PreviewService] Registered /api/preview route on webServer (v0.1.21)');
|
|
33
33
|
} else {
|
|
34
34
|
console.warn('[PreviewService] webServer not available yet in ctx');
|
|
35
35
|
}
|
|
@@ -130,7 +130,19 @@ export class PreviewService extends Service {
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
isAllowedDotfile(name) {
|
|
134
|
+
const lower = name.toLowerCase();
|
|
135
|
+
if (lower.startsWith('.env')) return true;
|
|
136
|
+
if (lower.startsWith('.gitignore') || lower.startsWith('.dockerignore') || lower.startsWith('.npmignore')) return true;
|
|
137
|
+
if (lower.startsWith('.prettier') || lower.startsWith('.eslint') || lower.startsWith('.stylelint') || lower.startsWith('.editorconfig')) return true;
|
|
138
|
+
if (lower.startsWith('.babel') || lower.startsWith('.swcrc') || lower.startsWith('.postcss') || lower.startsWith('.tailwind')) return true;
|
|
139
|
+
if (lower.startsWith('.vscode') || lower.startsWith('.github') || lower.startsWith('.husky')) return true;
|
|
140
|
+
if (lower.startsWith('.cursor') || lower.startsWith('.windsurf')) return true;
|
|
141
|
+
if (lower.startsWith('.npmrc') || lower.startsWith('.yarnrc') || lower.startsWith('.nvmrc') || lower.startsWith('.node-version')) return true;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
readWorkspaceTree(targetDir, maxDepth = 4, includeAll = false, showHidden = false) {
|
|
134
146
|
try {
|
|
135
147
|
const normalizedRoot = path.resolve(targetDir);
|
|
136
148
|
if (!fs.existsSync(normalizedRoot)) {
|
|
@@ -140,10 +152,10 @@ export class PreviewService extends Service {
|
|
|
140
152
|
if (!stat.isDirectory()) {
|
|
141
153
|
return { ok: false, error: { code: 'NOT_A_DIRECTORY', message: '目标路径不是目录: ' + normalizedRoot } };
|
|
142
154
|
}
|
|
143
|
-
const
|
|
144
|
-
'node_modules', '
|
|
145
|
-
'
|
|
146
|
-
'
|
|
155
|
+
const defaultIgnoreDirs = new Set([
|
|
156
|
+
'node_modules', 'dist', 'build', 'coverage', '.next', '.nuxt', '.output', '.turbo', '.cache',
|
|
157
|
+
'__pycache__', '.pytest_cache', 'target', 'vendor', 'tmp', 'temp',
|
|
158
|
+
'.dsh', '.gemini', '.idea', '.tempmediastorage', '.git'
|
|
147
159
|
]);
|
|
148
160
|
let totalFiles = 0;
|
|
149
161
|
let totalDirs = 0;
|
|
@@ -160,10 +172,18 @@ export class PreviewService extends Service {
|
|
|
160
172
|
for (const entry of entries) {
|
|
161
173
|
const entryName = entry.name;
|
|
162
174
|
const lowerName = entryName.toLowerCase();
|
|
175
|
+
const isHidden = entryName.startsWith('.') || entryName.startsWith('~$');
|
|
176
|
+
|
|
163
177
|
if (!includeAll) {
|
|
164
|
-
if (
|
|
165
|
-
|
|
178
|
+
if (!showHidden) {
|
|
179
|
+
if (defaultIgnoreDirs.has(lowerName)) continue;
|
|
180
|
+
if (lowerName === '.git' || lowerName === '.dsh' || lowerName === '.gemini' || lowerName === '.idea') continue;
|
|
181
|
+
if (isHidden && !this.isAllowedDotfile(entryName)) continue;
|
|
182
|
+
} else {
|
|
183
|
+
if (lowerName === 'node_modules' || lowerName === '__pycache__' || lowerName === '.pytest_cache') continue;
|
|
184
|
+
}
|
|
166
185
|
}
|
|
186
|
+
|
|
167
187
|
const fullPath = path.join(dirPath, entryName);
|
|
168
188
|
const relPath = path.relative(normalizedRoot, fullPath).replace(/\\/g, '/');
|
|
169
189
|
try {
|
|
@@ -175,6 +195,7 @@ export class PreviewService extends Service {
|
|
|
175
195
|
path: fullPath,
|
|
176
196
|
relativePath: relPath,
|
|
177
197
|
isDir: true,
|
|
198
|
+
isHidden,
|
|
178
199
|
children,
|
|
179
200
|
childCount: children.length,
|
|
180
201
|
});
|
|
@@ -193,6 +214,7 @@ export class PreviewService extends Service {
|
|
|
193
214
|
path: fullPath,
|
|
194
215
|
relativePath: relPath,
|
|
195
216
|
isDir: false,
|
|
217
|
+
isHidden,
|
|
196
218
|
extension: ext,
|
|
197
219
|
size,
|
|
198
220
|
mtime,
|
|
@@ -222,21 +244,34 @@ export class PreviewService extends Service {
|
|
|
222
244
|
}
|
|
223
245
|
}
|
|
224
246
|
|
|
225
|
-
listDirectoryEntries(targetDir, includeAll = false) {
|
|
247
|
+
listDirectoryEntries(targetDir, includeAll = false, showHidden = false) {
|
|
226
248
|
try {
|
|
227
249
|
const normalized = path.resolve(targetDir);
|
|
228
250
|
if (!fs.existsSync(normalized) || !fs.statSync(normalized).isDirectory()) {
|
|
229
251
|
return { ok: false, error: { code: 'DIR_NOT_FOUND', message: '目录不存在' } };
|
|
230
252
|
}
|
|
231
|
-
const
|
|
253
|
+
const defaultIgnoreDirs = new Set([
|
|
254
|
+
'node_modules', 'dist', 'build', 'coverage', '.next', '.nuxt', '.output', '.turbo', '.cache',
|
|
255
|
+
'__pycache__', '.pytest_cache', 'target', 'vendor', 'tmp', 'temp',
|
|
256
|
+
'.dsh', '.gemini', '.idea', '.tempmediastorage', '.git'
|
|
257
|
+
]);
|
|
232
258
|
const rawEntries = fs.readdirSync(normalized, { withFileTypes: true });
|
|
233
259
|
const nodes = [];
|
|
234
260
|
for (const entry of rawEntries) {
|
|
235
261
|
const name = entry.name;
|
|
262
|
+
const lowerName = name.toLowerCase();
|
|
263
|
+
const isHidden = name.startsWith('.') || name.startsWith('~$');
|
|
264
|
+
|
|
236
265
|
if (!includeAll) {
|
|
237
|
-
if (
|
|
238
|
-
|
|
266
|
+
if (!showHidden) {
|
|
267
|
+
if (defaultIgnoreDirs.has(lowerName)) continue;
|
|
268
|
+
if (lowerName === '.git' || lowerName === '.dsh' || lowerName === '.gemini' || lowerName === '.idea') continue;
|
|
269
|
+
if (isHidden && !this.isAllowedDotfile(name)) continue;
|
|
270
|
+
} else {
|
|
271
|
+
if (lowerName === 'node_modules' || lowerName === '__pycache__' || lowerName === '.pytest_cache') continue;
|
|
272
|
+
}
|
|
239
273
|
}
|
|
274
|
+
|
|
240
275
|
const fullPath = path.join(normalized, name);
|
|
241
276
|
const isDir = entry.isDirectory();
|
|
242
277
|
let size = 0;
|
|
@@ -255,6 +290,7 @@ export class PreviewService extends Service {
|
|
|
255
290
|
path: fullPath,
|
|
256
291
|
relativePath: name,
|
|
257
292
|
isDir,
|
|
293
|
+
isHidden,
|
|
258
294
|
extension: ext,
|
|
259
295
|
size,
|
|
260
296
|
mtime,
|
|
@@ -425,7 +461,8 @@ export class PreviewService extends Service {
|
|
|
425
461
|
const targetPath = url.searchParams.get('path') || process.cwd();
|
|
426
462
|
const depth = parseInt(url.searchParams.get('depth') || '4', 10);
|
|
427
463
|
const includeAll = url.searchParams.get('includeAll') === 'true';
|
|
428
|
-
const
|
|
464
|
+
const showHidden = url.searchParams.get('showHidden') === 'true';
|
|
465
|
+
const result = this.readWorkspaceTree(targetPath, isNaN(depth) ? 4 : depth, includeAll, showHidden);
|
|
429
466
|
res.statusCode = result.ok ? 200 : 404;
|
|
430
467
|
res.end(JSON.stringify(result));
|
|
431
468
|
return;
|
|
@@ -433,7 +470,8 @@ export class PreviewService extends Service {
|
|
|
433
470
|
if (pathname === '/api/preview/list-dir' && req.method === 'GET') {
|
|
434
471
|
const targetPath = url.searchParams.get('path') || process.cwd();
|
|
435
472
|
const includeAll = url.searchParams.get('includeAll') === 'true';
|
|
436
|
-
const
|
|
473
|
+
const showHidden = url.searchParams.get('showHidden') === 'true';
|
|
474
|
+
const result = this.listDirectoryEntries(targetPath, includeAll, showHidden);
|
|
437
475
|
res.statusCode = result.ok ? 200 : 404;
|
|
438
476
|
res.end(JSON.stringify(result));
|
|
439
477
|
return;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rooode/dsh-plugin-preview",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "DeepSeek Harness Markdown 文档与工作空间文件浏览器右侧预览插件 (
|
|
3
|
+
"version": "0.1.21",
|
|
4
|
+
"description": "DeepSeek Harness Markdown 文档与工作空间文件浏览器右侧预览插件 (支持查看/切换隐藏文件与点文件、工作区文件树、文件夹创建与文件移动、JSON 交互结构树/格式化、Java/C++/Python/JS/TS/YAML/Go/Rust 多语言语法高亮与符号大纲、自动换行与多标签 FileTabs)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|