vimd 0.4.1 → 0.5.1

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.
@@ -0,0 +1,389 @@
1
+ import http from 'http';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import { fileURLToPath } from 'url';
5
+ import polka from 'polka';
6
+ import { WebSocketServer as WSServer, WebSocket } from 'ws';
7
+ import { Logger } from '../../utils/logger.js';
8
+ import { SessionManager } from '../../utils/session-manager.js';
9
+ import { ThemeManager } from '../../themes/index.js';
10
+ import { ParserFactory } from '../parser/index.js';
11
+ import { PandocDetector } from '../pandoc-detector.js';
12
+ import { FileWatcher } from '../watcher.js';
13
+ import { FolderScanner } from './folder-scanner.js';
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ /**
17
+ * Folder mode server for multi-file preview
18
+ */
19
+ export class FolderModeServer {
20
+ constructor(options) {
21
+ this.httpServer = null;
22
+ this.wsServer = null;
23
+ this.clients = new Map();
24
+ this.fileTree = [];
25
+ this.renderedHtml = null;
26
+ this.options = options;
27
+ this._port = options.port;
28
+ this.scanner = new FolderScanner({ rootPath: options.rootPath });
29
+ }
30
+ /**
31
+ * Get the actual port the server is running on
32
+ */
33
+ get port() {
34
+ return this._port;
35
+ }
36
+ /**
37
+ * Get the root path
38
+ */
39
+ get rootPath() {
40
+ return this.options.rootPath;
41
+ }
42
+ /**
43
+ * Start the HTTP and WebSocket servers
44
+ */
45
+ async start() {
46
+ const requestedPort = this.options.port;
47
+ let actualPort = requestedPort;
48
+ // Check if port is available
49
+ if (!(await SessionManager.isPortAvailable(requestedPort))) {
50
+ actualPort = await SessionManager.findAvailablePort(requestedPort + 1);
51
+ Logger.warn(`Port ${requestedPort} was unavailable, using port ${actualPort}`);
52
+ }
53
+ this._port = actualPort;
54
+ // Initial scan
55
+ this.fileTree = await this.scanner.scan();
56
+ Logger.info(`Found ${this.countFiles(this.fileTree)} files in folder`);
57
+ // Render template
58
+ this.renderedHtml = await this.renderTemplate();
59
+ // Create polka app
60
+ const app = polka();
61
+ // API: Get file tree (wrapped with root folder)
62
+ app.get('/api/tree', (_req, res) => {
63
+ res.writeHead(200, { 'Content-Type': 'application/json' });
64
+ res.end(JSON.stringify(this.getWrappedTree()));
65
+ });
66
+ // Serve folder mode HTML for all routes (SPA style)
67
+ app.get('*', (req, res) => {
68
+ this.serveFolderModeHtml(req, res);
69
+ });
70
+ // Create HTTP server
71
+ this.httpServer = http.createServer(app.handler);
72
+ // Create WebSocket server
73
+ this.wsServer = new WSServer({ server: this.httpServer });
74
+ // Handle WebSocket connections
75
+ this.wsServer.on('connection', (ws) => {
76
+ this.handleConnection(ws);
77
+ });
78
+ // Start listening
79
+ await new Promise((resolve, reject) => {
80
+ const timeout = setTimeout(() => {
81
+ reject(new Error('Server start timeout'));
82
+ }, 10000);
83
+ this.httpServer.listen(actualPort, 'localhost', () => {
84
+ clearTimeout(timeout);
85
+ resolve();
86
+ });
87
+ this.httpServer.on('error', (err) => {
88
+ clearTimeout(timeout);
89
+ reject(err);
90
+ });
91
+ });
92
+ const url = `http://localhost:${actualPort}`;
93
+ Logger.success(`Folder mode server started at ${url}`);
94
+ return {
95
+ actualPort,
96
+ requestedPort,
97
+ portChanged: actualPort !== requestedPort,
98
+ };
99
+ }
100
+ /**
101
+ * Stop the server
102
+ */
103
+ async stop() {
104
+ // Terminate all WebSocket clients
105
+ for (const [client] of this.clients) {
106
+ try {
107
+ client.terminate();
108
+ }
109
+ catch {
110
+ // Ignore termination errors
111
+ }
112
+ }
113
+ this.clients.clear();
114
+ // Close WebSocket server
115
+ if (this.wsServer) {
116
+ await new Promise((resolve) => {
117
+ this.wsServer.close(() => resolve());
118
+ });
119
+ this.wsServer = null;
120
+ }
121
+ // Close HTTP server
122
+ if (this.httpServer) {
123
+ this.httpServer.closeAllConnections();
124
+ await new Promise((resolve) => {
125
+ this.httpServer.close(() => resolve());
126
+ });
127
+ this.httpServer = null;
128
+ }
129
+ Logger.info('Folder mode server stopped');
130
+ }
131
+ /**
132
+ * Handle new WebSocket connection
133
+ */
134
+ handleConnection(ws) {
135
+ // Initialize client state
136
+ this.clients.set(ws, { currentFile: null, watcher: null });
137
+ Logger.info(`WebSocket client connected (${this.clients.size} total)`);
138
+ // Send file tree on connection (wrapped with root folder)
139
+ this.sendMessage(ws, { type: 'tree', data: this.getWrappedTree() });
140
+ // Handle messages from client
141
+ ws.on('message', (data) => {
142
+ try {
143
+ const message = JSON.parse(data.toString());
144
+ this.handleClientMessage(ws, message);
145
+ }
146
+ catch (error) {
147
+ Logger.warn(`Invalid message from client: ${error}`);
148
+ }
149
+ });
150
+ ws.on('close', async () => {
151
+ // Stop watcher for this client
152
+ const state = this.clients.get(ws);
153
+ if (state?.watcher) {
154
+ await state.watcher.stop();
155
+ }
156
+ this.clients.delete(ws);
157
+ Logger.info(`WebSocket client disconnected (${this.clients.size} remaining)`);
158
+ });
159
+ ws.on('error', async (error) => {
160
+ Logger.warn(`WebSocket error: ${error.message}`);
161
+ // Stop watcher for this client
162
+ const state = this.clients.get(ws);
163
+ if (state?.watcher) {
164
+ await state.watcher.stop();
165
+ }
166
+ this.clients.delete(ws);
167
+ });
168
+ }
169
+ /**
170
+ * Handle message from client
171
+ */
172
+ handleClientMessage(ws, message) {
173
+ switch (message.type) {
174
+ case 'selectFile':
175
+ this.handleSelectFile(ws, message.path);
176
+ break;
177
+ default:
178
+ Logger.warn(`Unknown message type: ${message.type}`);
179
+ }
180
+ }
181
+ /**
182
+ * Handle file selection
183
+ */
184
+ async handleSelectFile(ws, relativePath) {
185
+ const state = this.clients.get(ws);
186
+ if (!state)
187
+ return;
188
+ // Validate path (security check)
189
+ const absolutePath = this.validatePath(relativePath);
190
+ if (!absolutePath) {
191
+ this.sendMessage(ws, {
192
+ type: 'error',
193
+ data: { type: 'invalid-path', message: 'Invalid file path' },
194
+ });
195
+ return;
196
+ }
197
+ // Check file exists
198
+ if (!(await fs.pathExists(absolutePath))) {
199
+ this.sendMessage(ws, {
200
+ type: 'error',
201
+ data: { type: 'file-not-found', message: 'File not found' },
202
+ });
203
+ return;
204
+ }
205
+ // Determine file type
206
+ const ext = path.extname(relativePath).toLowerCase();
207
+ const isLatex = ext === '.tex' || ext === '.latex';
208
+ // Check pandoc for LaTeX files
209
+ if (isLatex && !PandocDetector.check()) {
210
+ this.sendMessage(ws, {
211
+ type: 'error',
212
+ data: {
213
+ type: 'pandoc-not-found',
214
+ message: 'LaTeX files require pandoc. Please install pandoc.',
215
+ },
216
+ });
217
+ return;
218
+ }
219
+ // Stop previous watcher if exists
220
+ if (state.watcher) {
221
+ await state.watcher.stop();
222
+ state.watcher = null;
223
+ }
224
+ // Convert file
225
+ try {
226
+ const html = await this.convertFile(absolutePath, isLatex);
227
+ // Update client state
228
+ state.currentFile = relativePath;
229
+ // Send content
230
+ this.sendMessage(ws, {
231
+ type: 'content',
232
+ data: { path: relativePath, html },
233
+ });
234
+ // Start watching the file
235
+ state.watcher = new FileWatcher(absolutePath, { debounce: 300, ignored: [] });
236
+ state.watcher.onChange(async () => {
237
+ // Re-convert and send on file change
238
+ try {
239
+ const newHtml = await this.convertFile(absolutePath, isLatex);
240
+ this.sendMessage(ws, {
241
+ type: 'content',
242
+ data: { path: relativePath, html: newHtml },
243
+ });
244
+ Logger.info(`File updated: ${relativePath}`);
245
+ }
246
+ catch (err) {
247
+ const errMsg = err instanceof Error ? err.message : 'Unknown error';
248
+ Logger.error(`Failed to reconvert ${relativePath}: ${errMsg}`);
249
+ }
250
+ });
251
+ state.watcher.start();
252
+ Logger.info(`File converted and watching: ${relativePath}`);
253
+ }
254
+ catch (error) {
255
+ const message = error instanceof Error ? error.message : 'Unknown error';
256
+ Logger.error(`Failed to convert ${relativePath}: ${message}`);
257
+ this.sendMessage(ws, {
258
+ type: 'error',
259
+ data: { type: 'conversion-error', message: `Failed to convert file: ${message}` },
260
+ });
261
+ }
262
+ }
263
+ /**
264
+ * Convert a file to HTML
265
+ */
266
+ async convertFile(absolutePath, isLatex) {
267
+ const content = await fs.readFile(absolutePath, 'utf-8');
268
+ const fromFormat = isLatex ? 'latex' : 'markdown';
269
+ const parserType = isLatex ? 'pandoc' : 'markdown-it';
270
+ const parser = ParserFactory.create(parserType, {}, undefined, fromFormat);
271
+ return parser.parse(content);
272
+ }
273
+ /**
274
+ * Validate path and return absolute path if valid
275
+ */
276
+ validatePath(relativePath) {
277
+ // Normalize path
278
+ const normalized = path.normalize(relativePath);
279
+ // Reject paths with '..'
280
+ if (normalized.includes('..')) {
281
+ return null;
282
+ }
283
+ // Create absolute path
284
+ const absolutePath = path.join(this.options.rootPath, normalized);
285
+ // Ensure path is within root
286
+ if (!absolutePath.startsWith(this.options.rootPath)) {
287
+ return null;
288
+ }
289
+ return absolutePath;
290
+ }
291
+ /**
292
+ * Send message to a specific client
293
+ */
294
+ sendMessage(ws, message) {
295
+ if (ws.readyState === WebSocket.OPEN) {
296
+ ws.send(JSON.stringify(message));
297
+ }
298
+ }
299
+ /**
300
+ * Broadcast message to all clients
301
+ */
302
+ broadcast(message) {
303
+ const data = JSON.stringify(message);
304
+ for (const [client] of this.clients) {
305
+ if (client.readyState === WebSocket.OPEN) {
306
+ client.send(data);
307
+ }
308
+ }
309
+ }
310
+ /**
311
+ * Serve folder mode HTML
312
+ */
313
+ serveFolderModeHtml(_req, res) {
314
+ res.writeHead(200, {
315
+ 'Content-Type': 'text/html; charset=utf-8',
316
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
317
+ });
318
+ res.end(this.renderedHtml);
319
+ }
320
+ /**
321
+ * Render the folder mode template
322
+ */
323
+ async renderTemplate() {
324
+ // Load template
325
+ const templatePath = path.join(__dirname, '../../../templates/folder-mode.html');
326
+ let template = await fs.readFile(templatePath, 'utf-8');
327
+ // Load CSS
328
+ const cssPath = path.join(__dirname, 'assets/folder-mode.css');
329
+ const folderModeCss = await fs.readFile(cssPath, 'utf-8');
330
+ // Load JS
331
+ const jsPath = path.join(__dirname, 'assets/folder-mode.js');
332
+ const folderModeJs = await fs.readFile(jsPath, 'utf-8');
333
+ // Load theme CSS
334
+ const themeCss = await ThemeManager.getCSS(this.options.theme);
335
+ // Get folder name for display
336
+ const folderName = path.basename(this.options.rootPath);
337
+ // Replace placeholders
338
+ template = template
339
+ .replace('{{folder_name}}', this.escapeHtml(folderName))
340
+ .replace('{{folder_mode_css}}', folderModeCss)
341
+ .replace('{{theme_css}}', themeCss)
342
+ .replace('{{folder_mode_js}}', folderModeJs);
343
+ // Handle math support (enabled by default)
344
+ template = template
345
+ .replace(/\{\{#if math_enabled\}\}/g, '')
346
+ .replace(/\{\{\/if\}\}/g, '');
347
+ return template;
348
+ }
349
+ /**
350
+ * Escape HTML special characters
351
+ */
352
+ escapeHtml(text) {
353
+ return text
354
+ .replace(/&/g, '&')
355
+ .replace(/</g, '&lt;')
356
+ .replace(/>/g, '&gt;')
357
+ .replace(/"/g, '&quot;')
358
+ .replace(/'/g, '&#039;');
359
+ }
360
+ /**
361
+ * Wrap file tree with root folder for VSCode-style explorer UI
362
+ */
363
+ getWrappedTree() {
364
+ const folderName = path.basename(this.options.rootPath);
365
+ return [
366
+ {
367
+ type: 'folder',
368
+ name: folderName,
369
+ path: '',
370
+ children: this.fileTree,
371
+ },
372
+ ];
373
+ }
374
+ /**
375
+ * Count total files in tree
376
+ */
377
+ countFiles(nodes) {
378
+ let count = 0;
379
+ for (const node of nodes) {
380
+ if (node.type === 'file') {
381
+ count++;
382
+ }
383
+ else {
384
+ count += this.countFiles(node.children);
385
+ }
386
+ }
387
+ return count;
388
+ }
389
+ }
@@ -0,0 +1,34 @@
1
+ import type { TreeNode, FolderScannerOptions } from './types.js';
2
+ /**
3
+ * Folder scanner for building file tree structure
4
+ */
5
+ export declare class FolderScanner {
6
+ private rootPath;
7
+ private excludePatterns;
8
+ constructor(options: FolderScannerOptions);
9
+ /**
10
+ * Scan the folder and return tree structure
11
+ */
12
+ scan(): Promise<TreeNode[]>;
13
+ /**
14
+ * Recursively scan a directory
15
+ */
16
+ private scanDirectory;
17
+ /**
18
+ * Check if a name is hidden (starts with .)
19
+ */
20
+ private isHidden;
21
+ /**
22
+ * Check if a name matches exclude patterns
23
+ */
24
+ private shouldExclude;
25
+ /**
26
+ * Check if extension is supported
27
+ */
28
+ private isSupportedExtension;
29
+ /**
30
+ * Sort nodes: folders first, then files, both in alphabetical order
31
+ */
32
+ private sortNodes;
33
+ }
34
+ //# sourceMappingURL=folder-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-scanner.d.ts","sourceRoot":"","sources":["../../../src/core/folder-mode/folder-scanner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EAIR,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAYpB;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,eAAe,CAAW;gBAEtB,OAAO,EAAE,oBAAoB;IAQzC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAIjC;;OAEG;YACW,aAAa;IAyD3B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;OAEG;IACH,OAAO,CAAC,SAAS;CAUlB"}
@@ -0,0 +1,105 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ /**
4
+ * Default exclude patterns
5
+ */
6
+ const DEFAULT_EXCLUDE_PATTERNS = ['node_modules', '.git', 'dist', 'build'];
7
+ /**
8
+ * Supported file extensions
9
+ */
10
+ const SUPPORTED_EXTENSIONS = ['.md', '.tex', '.latex'];
11
+ /**
12
+ * Folder scanner for building file tree structure
13
+ */
14
+ export class FolderScanner {
15
+ constructor(options) {
16
+ this.rootPath = options.rootPath;
17
+ this.excludePatterns = [
18
+ ...DEFAULT_EXCLUDE_PATTERNS,
19
+ ...(options.excludePatterns || []),
20
+ ];
21
+ }
22
+ /**
23
+ * Scan the folder and return tree structure
24
+ */
25
+ async scan() {
26
+ return this.scanDirectory(this.rootPath, '');
27
+ }
28
+ /**
29
+ * Recursively scan a directory
30
+ */
31
+ async scanDirectory(dirPath, relativePath) {
32
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
33
+ const nodes = [];
34
+ for (const entry of entries) {
35
+ const entryRelativePath = relativePath === '' ? entry.name : path.join(relativePath, entry.name);
36
+ // Skip hidden files/folders (except at root level when explicitly specified)
37
+ if (relativePath !== '' && this.isHidden(entry.name)) {
38
+ continue;
39
+ }
40
+ // Skip excluded patterns
41
+ if (this.shouldExclude(entry.name)) {
42
+ continue;
43
+ }
44
+ if (entry.isDirectory()) {
45
+ // Recursively scan subdirectory
46
+ const children = await this.scanDirectory(path.join(dirPath, entry.name), entryRelativePath);
47
+ // Only add folder if it has matching files
48
+ if (children.length > 0) {
49
+ const folderNode = {
50
+ name: entry.name,
51
+ path: entryRelativePath,
52
+ type: 'folder',
53
+ children,
54
+ };
55
+ nodes.push(folderNode);
56
+ }
57
+ }
58
+ else if (entry.isFile()) {
59
+ const ext = path.extname(entry.name).toLowerCase();
60
+ // Only include supported extensions
61
+ if (this.isSupportedExtension(ext)) {
62
+ const fileNode = {
63
+ name: entry.name,
64
+ path: entryRelativePath,
65
+ type: 'file',
66
+ extension: ext,
67
+ };
68
+ nodes.push(fileNode);
69
+ }
70
+ }
71
+ }
72
+ return this.sortNodes(nodes);
73
+ }
74
+ /**
75
+ * Check if a name is hidden (starts with .)
76
+ */
77
+ isHidden(name) {
78
+ return name.startsWith('.');
79
+ }
80
+ /**
81
+ * Check if a name matches exclude patterns
82
+ */
83
+ shouldExclude(name) {
84
+ return this.excludePatterns.includes(name);
85
+ }
86
+ /**
87
+ * Check if extension is supported
88
+ */
89
+ isSupportedExtension(ext) {
90
+ return SUPPORTED_EXTENSIONS.includes(ext);
91
+ }
92
+ /**
93
+ * Sort nodes: folders first, then files, both in alphabetical order
94
+ */
95
+ sortNodes(nodes) {
96
+ return nodes.sort((a, b) => {
97
+ // Folders come first
98
+ if (a.type !== b.type) {
99
+ return a.type === 'folder' ? -1 : 1;
100
+ }
101
+ // Alphabetical order (case-insensitive)
102
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
103
+ });
104
+ }
105
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Folder mode module exports
3
+ */
4
+ export { FolderScanner } from './folder-scanner.js';
5
+ export { FolderModeServer } from './folder-mode-server.js';
6
+ export type { TreeNode, FileNode, FolderNode, SupportedExtension, FolderScannerOptions, FolderModeOptions, ServerMessage, ClientMessage, } from './types.js';
7
+ export { isFileNode, isFolderNode } from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/folder-mode/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Folder mode module exports
3
+ */
4
+ export { FolderScanner } from './folder-scanner.js';
5
+ export { FolderModeServer } from './folder-mode-server.js';
6
+ export { isFileNode, isFolderNode } from './types.js';
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Folder mode type definitions
3
+ */
4
+ /**
5
+ * Supported file extensions for folder mode
6
+ */
7
+ export type SupportedExtension = '.md' | '.tex' | '.latex';
8
+ /**
9
+ * File node in the tree structure
10
+ */
11
+ export interface FileNode {
12
+ /** File name (e.g., "plan.md") */
13
+ name: string;
14
+ /** Relative path from root (e.g., "docs/plan.md") */
15
+ path: string;
16
+ /** Node type */
17
+ type: 'file';
18
+ /** File extension */
19
+ extension: SupportedExtension;
20
+ }
21
+ /**
22
+ * Folder node in the tree structure
23
+ */
24
+ export interface FolderNode {
25
+ /** Folder name (e.g., "docs") */
26
+ name: string;
27
+ /** Relative path from root (e.g., "docs") */
28
+ path: string;
29
+ /** Node type */
30
+ type: 'folder';
31
+ /** Child nodes (sorted) */
32
+ children: TreeNode[];
33
+ }
34
+ /**
35
+ * Tree node (file or folder)
36
+ */
37
+ export type TreeNode = FileNode | FolderNode;
38
+ /**
39
+ * Type guard for FileNode
40
+ */
41
+ export declare function isFileNode(node: TreeNode): node is FileNode;
42
+ /**
43
+ * Type guard for FolderNode
44
+ */
45
+ export declare function isFolderNode(node: TreeNode): node is FolderNode;
46
+ /**
47
+ * Folder scanner options
48
+ */
49
+ export interface FolderScannerOptions {
50
+ /** Root path to scan */
51
+ rootPath: string;
52
+ /** Additional exclude patterns (glob) */
53
+ excludePatterns?: string[];
54
+ }
55
+ /**
56
+ * Folder mode server options
57
+ */
58
+ export interface FolderModeOptions {
59
+ /** Root path of the folder */
60
+ rootPath: string;
61
+ /** Server port */
62
+ port: number;
63
+ /** Theme name */
64
+ theme: string;
65
+ /** Open browser on start */
66
+ open: boolean;
67
+ }
68
+ /**
69
+ * WebSocket message from server to client
70
+ */
71
+ export type ServerMessage = {
72
+ type: 'tree';
73
+ data: TreeNode[];
74
+ } | {
75
+ type: 'content';
76
+ data: {
77
+ path: string;
78
+ html: string;
79
+ };
80
+ } | {
81
+ type: 'reload';
82
+ } | {
83
+ type: 'error';
84
+ data: {
85
+ type: string;
86
+ message: string;
87
+ };
88
+ } | {
89
+ type: 'fileDeleted';
90
+ data: {
91
+ path: string;
92
+ };
93
+ };
94
+ /**
95
+ * WebSocket message from client to server
96
+ */
97
+ export type ClientMessage = {
98
+ type: 'selectFile';
99
+ path: string;
100
+ };
101
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/folder-mode/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,IAAI,EAAE,QAAQ,CAAC;IACf,2BAA2B;IAC3B,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE7C;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,IAAI,QAAQ,CAE3D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,IAAI,UAAU,CAE/D;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,EAAE,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Folder mode type definitions
3
+ */
4
+ /**
5
+ * Type guard for FileNode
6
+ */
7
+ export function isFileNode(node) {
8
+ return node.type === 'file';
9
+ }
10
+ /**
11
+ * Type guard for FolderNode
12
+ */
13
+ export function isFolderNode(node) {
14
+ return node.type === 'folder';
15
+ }