class MindmapModule { constructor() { this.commands = {}; this.terminal = null; this.root = null; this.colors = ['red', 'green', 'yellow', 'blue', 'cyan', 'magenta', 'white']; this._colorIdx = 0; } getHelpInfo() { return { 'mindmap': 'mindmap [...] — Create and manage interactive mindmaps' }; } _nextColor() { const c = this.colors[this._colorIdx % this.colors.length]; this._colorIdx++; return c; } _genId() { return Math.random().toString(36).slice(2, 6); } _findNode(id, node) { if (node.id === id) return node; for (const child of (node.children || [])) { const found = this._findNode(id, child); if (found) return found; } return null; } _treeLines(node, prefix, isLast) { const lines = []; const connector = isLast ? '\u2514\u2500\u2500 ' : '\u251c\u2500\u2500 '; const tagStr = (node.tags && node.tags.length) ? ' [' + node.tags.join(', ') + ']' : ''; const colorTag = node.color ? '[' + node.color + ']' : ''; lines.push(prefix + connector + colorTag + node.text + ' (' + node.id + ')' + tagStr); if (node.children) { const sorted = node.children; sorted.forEach((child, i) => { const subPrefix = prefix + (isLast ? ' ' : '\u2502 '); this._treeLines(child, subPrefix, i === sorted.length - 1).forEach(l => lines.push(l)); }); } return lines; } registerCommands() { const self = this; this.commands['mindmap'] = (args) => { if (!args[0] || args[0] === '--help' || args[0] === '-h') { this.terminal.print('Usage: mindmap [...]', 'info'); this.terminal.print(' new [-t tags] — Create a new mindmap', 'info'); this.terminal.print(' add [-t tags] — Add a child node', 'info'); this.terminal.print(' show — Display the mindmap tree', 'info'); this.terminal.print(' export — Print as indented text', 'info'); this.terminal.print(' save [name] — Save to VFS', 'info'); this.terminal.print(' load — Load from VFS', 'info'); this.terminal.print(' list — List saved mindmaps', 'info'); this.terminal.print(' delete — Remove a node', 'info'); this.terminal.print(' rename ', 'info'); this.terminal.print(' color ', 'info'); this.terminal.print(' tag [tag1,tag2...]', 'info'); return; } const sub = args[0]; const rest = args.slice(1); switch (sub) { case '--help': case '-h': case 'help': this.terminal.print('Usage: mindmap [...]', 'info'); return; case 'new': return this._new(rest); case 'add': return this._add(rest); case 'show': return this._show(rest); case 'export': return this._export(rest); case 'save': return this._save(rest); case 'load': return this._load(rest); case 'list': return this._list(); case 'delete': return this._delete(rest); case 'rename': return this._rename(rest); case 'color': return this._color(rest); case 'tag': return this._tag(rest); default: this.terminal.print('Unknown subcommand: ' + sub + '. Try: new, add, show, export, save, load, list, delete, rename, color, tag', 'warning'); } }; } _parseTags(args) { const tagIdx = args.indexOf('-t'); let tags = []; if (tagIdx >= 0) { tags = args[tagIdx + 1] ? args[tagIdx + 1].split(',').map(t => t.trim()).filter(Boolean) : []; args.splice(tagIdx, 2); } return { args, tags }; } _new(args) { const { args: clean, tags } = this._parseTags([...args]); if (!clean.length) return this.terminal.print('Usage: mindmap new [-t tag1,tag2]', 'warning'); if (this.root) return this.terminal.print('A mindmap already exists. Use mindmap add to extend it.', 'warning'); this.root = { id: this._genId(), text: clean.join(' '), color: this._nextColor(), tags: tags, children: [] }; this.terminal.print('Created root: [' + this.root.color + '] ' + this.root.text + ' (' + this.root.id + ')', 'success'); } _add(args) { const { args: clean, tags } = this._parseTags([...args]); if (clean.length < 2) return this.terminal.print('Usage: mindmap add [-t tag1,tag2]', 'warning'); if (!this.root) return this.terminal.print('No mindmap. Create one with mindmap new first.', 'warning'); const parentId = clean[0]; const text = clean.slice(1).join(' '); const parent = this._findNode(parentId, this.root); if (!parent) return this.terminal.print('Node not found: ' + parentId, 'error'); const node = { id: this._genId(), text: text, color: this._nextColor(), tags: tags, children: [] }; parent.children.push(node); this.terminal.print('Added [' + node.color + '] ' + text + ' (' + node.id + ')', 'success'); } _show(args) { if (!this.root) return this.terminal.print('No mindmap. Create one with mindmap new first.', 'warning'); let node = this.root; if (args.length && args[0]) { const found = this._findNode(args[0], this.root); if (!found) return this.terminal.print('Node not found: ' + args[0], 'error'); node = found; } const lines = this._treeLines(node, '', true); lines.forEach(l => this.terminal.print(l)); this.terminal.print('(' + node.text + ' — ' + this._countNodes(node) + ' nodes total)', 'info'); } _countNodes(node) { let c = 1; for (const child of (node.children || [])) c += this._countNodes(child); return c; } _export(args) { if (!this.root) return this.terminal.print('No mindmap. Create one with mindmap new first.', 'warning'); let node = this.root; if (args.length && args[0]) { const found = this._findNode(args[0], this.root); if (!found) return this.terminal.print('Node not found: ' + args[0], 'error'); node = found; } const md = this._toMarkdown(node, 0); this.terminal.print(md); this.terminal.print('(Markdown copied to clipboard)', 'info'); if (navigator.clipboard) { navigator.clipboard.writeText(md); } } _toMarkdown(node, depth) { const prefix = ' '.repeat(depth) + '- '; const tagStr = (node.tags && node.tags.length) ? ' *[' + node.tags.join(', ') + ']*' : ''; let out = prefix + '**' + node.text + '**' + tagStr + '\n'; if (node.children) { node.children.forEach(c => { out += this._toMarkdown(c, depth + 1); }); } return out; } _save(args) { if (!args[0]) return this.terminal.print('Usage: mindmap save ', 'warning'); if (!this.root) return this.terminal.print('No mindmap to save.', 'warning'); try { const fs = this.terminal.fs; fs.mkdirRecursive('/mindmaps'); fs.writeFile('/mindmaps/' + args[0] + '.json', JSON.stringify(this.root, null, 2)); this.terminal.print('Mindmap saved as: ' + args[0], 'success'); } catch (e) { this.terminal.print('Failed to save: ' + e.message, 'error'); } } _load(args) { if (!args[0]) return this.terminal.print('Usage: mindmap load ', 'warning'); try { const fs = this.terminal.fs; const data = fs.readFile('/mindmaps/' + args[0] + '.json'); if (!data) return this.terminal.print('No saved mindmap: ' + args[0], 'error'); this.root = JSON.parse(data); this.terminal.print('Loaded mindmap: ' + args[0] + ' (' + this._countNodes(this.root) + ' nodes)', 'success'); } catch (e) { this.terminal.print('Failed to load: ' + e.message, 'error'); } } _list() { const fs = this.terminal.fs; const files = fs.readDir('/mindmaps') || []; const keys = files.filter(f => f.name.endsWith('.json')).map(f => f.name.replace('.json', '')); if (!keys.length) return this.terminal.print('No saved mindmaps.', 'info'); this.terminal.print('=== Saved Mindmaps ===', 'info'); keys.forEach(k => this.terminal.print(' ' + k)); } _delete(args) { if (!args[0]) return this.terminal.print('Usage: mindmap delete ', 'warning'); const fs = this.terminal.fs; fs.deleteFile('/mindmaps/' + args[0] + '.json'); this.terminal.print('Deleted mindmap: ' + args[0], 'success'); } _rename(args) { if (args.length < 2) return this.terminal.print('Usage: mindmap rename ', 'warning'); if (!this.root) return this.terminal.print('No mindmap.', 'warning'); const id = args[0]; const text = args.slice(1).join(' '); const node = this._findNode(id, this.root); if (!node) return this.terminal.print('Node not found: ' + id, 'error'); node.text = text; this.terminal.print('Renamed ' + id + ' to: ' + text, 'success'); } _color(args) { if (args.length < 2) return this.terminal.print('Usage: mindmap color ', 'warning'); if (!this.root) return this.terminal.print('No mindmap.', 'warning'); const id = args[0]; const color = args[1].toLowerCase(); if (!this.colors.includes(color)) return this.terminal.print('Available colors: ' + this.colors.join(', '), 'warning'); const node = this._findNode(id, this.root); if (!node) return this.terminal.print('Node not found: ' + id, 'error'); node.color = color; this.terminal.print('Set color for ' + id + ' to ' + color, 'success'); } _tag(args) { if (args.length < 2) return this.terminal.print('Usage: mindmap tag ', 'warning'); if (!this.root) return this.terminal.print('No mindmap.', 'warning'); const id = args[0]; const tag = args.slice(1).join(' '); const node = this._findNode(id, this.root); if (!node) return this.terminal.print('Node not found: ' + id, 'error'); if (!node.tags) node.tags = []; node.tags.push(tag); this.terminal.print('Added tag "' + tag + '" to ' + id, 'success'); } } if (typeof window.webx11 !== 'undefined' && window.webx11) { webx11.moduleManager.registerModule(new MindmapModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/mindmap.js'); }