@rooode/dsh-plugin-preview 0.1.20 → 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 +135 -11
- 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';
|
|
@@ -1278,6 +1327,41 @@
|
|
|
1278
1327
|
);
|
|
1279
1328
|
}
|
|
1280
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
|
+
|
|
1281
1365
|
function IconChevronRight() {
|
|
1282
1366
|
return h('svg', {
|
|
1283
1367
|
width: 9,
|
|
@@ -1453,7 +1537,7 @@
|
|
|
1453
1537
|
);
|
|
1454
1538
|
}
|
|
1455
1539
|
|
|
1456
|
-
function TreeContextMenu({ x, y, node, isRoot, clipboard, onAction, onClose }) {
|
|
1540
|
+
function TreeContextMenu({ x, y, node, isRoot, showHidden, clipboard, onAction, onClose }) {
|
|
1457
1541
|
const menuRef = useRef(null);
|
|
1458
1542
|
useEffect(() => {
|
|
1459
1543
|
const handleDown = (e) => {
|
|
@@ -1535,6 +1619,12 @@
|
|
|
1535
1619
|
)
|
|
1536
1620
|
] : null,
|
|
1537
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
|
+
|
|
1538
1628
|
isRoot ? [
|
|
1539
1629
|
h('div', { key: 'refresh', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('refresh'); } },
|
|
1540
1630
|
h('span', { className: 'dsh-menu-icon' }, '🔄'),
|
|
@@ -1636,7 +1726,7 @@
|
|
|
1636
1726
|
|
|
1637
1727
|
return h(Fragment, null,
|
|
1638
1728
|
h('div', {
|
|
1639
|
-
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' : ''}`,
|
|
1640
1730
|
style: indentStyle,
|
|
1641
1731
|
onClick: handleClick,
|
|
1642
1732
|
onContextMenu: handleContextMenu,
|
|
@@ -1646,7 +1736,7 @@
|
|
|
1646
1736
|
onDragOver: handleDragOver,
|
|
1647
1737
|
onDragLeave: handleDragLeave,
|
|
1648
1738
|
onDrop: handleDrop,
|
|
1649
|
-
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) + ')' : ''}`,
|
|
1650
1740
|
},
|
|
1651
1741
|
// Chevron indicator for directory
|
|
1652
1742
|
isDir ? h('span', {
|
|
@@ -1773,6 +1863,7 @@
|
|
|
1773
1863
|
const [clipboard, setClipboard] = useState(null); // { action: 'cut', path: string, name: string, isDir: boolean } | null
|
|
1774
1864
|
|
|
1775
1865
|
const [currentWs, setCurrentWs] = useState(getCurrentWorkspaceRoot());
|
|
1866
|
+
const [showHidden, setShowHidden] = useState(globalShowHidden);
|
|
1776
1867
|
|
|
1777
1868
|
useEffect(() => {
|
|
1778
1869
|
const handleStateUpdate = () => {
|
|
@@ -1780,10 +1871,13 @@
|
|
|
1780
1871
|
if (newWs !== currentWs) {
|
|
1781
1872
|
setCurrentWs(newWs);
|
|
1782
1873
|
}
|
|
1874
|
+
if (globalShowHidden !== showHidden) {
|
|
1875
|
+
setShowHidden(globalShowHidden);
|
|
1876
|
+
}
|
|
1783
1877
|
};
|
|
1784
1878
|
stateListeners.add(handleStateUpdate);
|
|
1785
1879
|
return () => stateListeners.delete(handleStateUpdate);
|
|
1786
|
-
}, [currentWs]);
|
|
1880
|
+
}, [currentWs, showHidden]);
|
|
1787
1881
|
|
|
1788
1882
|
const loadTree = useCallback(async (force = false, targetWs) => {
|
|
1789
1883
|
setIsLoading(true);
|
|
@@ -1795,26 +1889,29 @@
|
|
|
1795
1889
|
return;
|
|
1796
1890
|
}
|
|
1797
1891
|
const query = encodeURIComponent(rootPath);
|
|
1798
|
-
const res = await fetch(`/api/preview/workspace-tree?path=${query}`);
|
|
1892
|
+
const res = await fetch(`/api/preview/workspace-tree?path=${query}&showHidden=${showHidden}`);
|
|
1799
1893
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1800
1894
|
const json = await res.json();
|
|
1801
1895
|
if (!json.ok) throw new Error(json?.error?.message || '读取工作区目录失败');
|
|
1802
1896
|
setTreeData(json);
|
|
1803
|
-
// By default expand top 1 level directories
|
|
1897
|
+
// By default expand top 1 level directories if not already expanded
|
|
1804
1898
|
if (json.tree) {
|
|
1805
1899
|
const topDirs = json.tree.filter(n => n.isDir).map(n => n.path);
|
|
1806
|
-
setExpandedPaths(
|
|
1900
|
+
setExpandedPaths(prev => {
|
|
1901
|
+
if (prev.size === 0) return new Set(topDirs);
|
|
1902
|
+
return prev;
|
|
1903
|
+
});
|
|
1807
1904
|
}
|
|
1808
1905
|
} catch (err) {
|
|
1809
1906
|
setError(err.message || '加载工作空间文件树失败');
|
|
1810
1907
|
} finally {
|
|
1811
1908
|
setIsLoading(false);
|
|
1812
1909
|
}
|
|
1813
|
-
}, [currentWs]);
|
|
1910
|
+
}, [currentWs, showHidden]);
|
|
1814
1911
|
|
|
1815
1912
|
useEffect(() => {
|
|
1816
1913
|
loadTree(false, currentWs);
|
|
1817
|
-
}, [loadTree, currentWs, globalManualWorkspace]);
|
|
1914
|
+
}, [loadTree, currentWs, showHidden, globalManualWorkspace]);
|
|
1818
1915
|
|
|
1819
1916
|
const handleToggleExpand = (dirPath) => {
|
|
1820
1917
|
setExpandedPaths(prev => {
|
|
@@ -2061,11 +2158,20 @@
|
|
|
2061
2158
|
if (node) triggerOpenNative(node.path);
|
|
2062
2159
|
} else if (action === 'delete') {
|
|
2063
2160
|
if (node) handleDelete(node);
|
|
2161
|
+
} else if (action === 'toggle-hidden') {
|
|
2162
|
+
handleToggleShowHidden();
|
|
2064
2163
|
} else if (action === 'refresh') {
|
|
2065
2164
|
loadTree(true);
|
|
2066
2165
|
}
|
|
2067
2166
|
};
|
|
2068
2167
|
|
|
2168
|
+
const handleToggleShowHidden = () => {
|
|
2169
|
+
const next = !showHidden;
|
|
2170
|
+
setShowHidden(next);
|
|
2171
|
+
setGlobalShowHidden(next);
|
|
2172
|
+
showToast(next ? '已开启显示隐藏文件与点文件' : '已隐藏点文件与隐藏项', 'info');
|
|
2173
|
+
};
|
|
2174
|
+
|
|
2069
2175
|
const rootPath = treeData?.workspaceRoot || currentWs;
|
|
2070
2176
|
const isCreatingAtRoot = creatingItem && creatingItem.parentPath && rootPath && creatingItem.parentPath.replace(/\\/g, '/').toLowerCase() === rootPath.replace(/\\/g, '/').toLowerCase();
|
|
2071
2177
|
const isRootDropTarget = dragOverNode && rootPath && dragOverNode.path && dragOverNode.path.replace(/\\/g, '/').toLowerCase() === rootPath.replace(/\\/g, '/').toLowerCase();
|
|
@@ -2098,6 +2204,12 @@
|
|
|
2098
2204
|
onClick: () => handleStartCreate(rootPath, 'dir'),
|
|
2099
2205
|
title: '新建文件夹 (在根目录)',
|
|
2100
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)),
|
|
2101
2213
|
h('button', {
|
|
2102
2214
|
type: 'button',
|
|
2103
2215
|
className: 'dsh-tree-action-btn',
|
|
@@ -2289,6 +2401,7 @@
|
|
|
2289
2401
|
y: contextMenu.y,
|
|
2290
2402
|
node: contextMenu.node,
|
|
2291
2403
|
isRoot: contextMenu.isRoot,
|
|
2404
|
+
showHidden,
|
|
2292
2405
|
clipboard,
|
|
2293
2406
|
onAction: handleContextMenuAction,
|
|
2294
2407
|
onClose: () => setContextMenu(null),
|
|
@@ -5609,6 +5722,10 @@
|
|
|
5609
5722
|
background: rgba(255, 255, 255, 0.1);
|
|
5610
5723
|
color: #ffffff;
|
|
5611
5724
|
}
|
|
5725
|
+
.dsh-tree-action-btn.active {
|
|
5726
|
+
color: var(--dsw-static-deepseek-500, #3b82f6);
|
|
5727
|
+
background: rgba(59, 130, 246, 0.12);
|
|
5728
|
+
}
|
|
5612
5729
|
|
|
5613
5730
|
.dsh-tree-sidebar-search {
|
|
5614
5731
|
padding: 6px 8px;
|
|
@@ -5724,6 +5841,13 @@
|
|
|
5724
5841
|
background: rgba(56, 189, 248, 0.16);
|
|
5725
5842
|
color: #38bdf8;
|
|
5726
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
|
+
}
|
|
5727
5851
|
|
|
5728
5852
|
.dsh-tree-chevron {
|
|
5729
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",
|