aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/mindmap.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/ext/mindmap.js')
-rw-r--r--modules/ext/mindmap.js217
1 files changed, 217 insertions, 0 deletions
diff --git a/modules/ext/mindmap.js b/modules/ext/mindmap.js
new file mode 100644
index 0000000..012bae7
--- /dev/null
+++ b/modules/ext/mindmap.js
@@ -0,0 +1,217 @@
+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 <new|add|show|export|save|load|list|delete|rename|color|tag> [...] — 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]) return this.terminal.print('Usage: mindmap <new|add|show|export|save|load|list|delete|rename|color|tag> [...]', 'warning');
+ const sub = args[0];
+ const rest = args.slice(1);
+ switch (sub) {
+ 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 <topic> [-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 <parent-id> <text> [-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 <name>', 'warning');
+ if (!this.root) return this.terminal.print('No mindmap to save.', 'warning');
+ try {
+ localStorage.setItem('webx11_mindmap_' + args[0], JSON.stringify(this.root));
+ 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 <name>', 'warning');
+ try {
+ const data = localStorage.getItem('webx11_mindmap_' + args[0]);
+ 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 keys = [];
+ for (let i = 0; i < localStorage.length; i++) {
+ const k = localStorage.key(i);
+ if (k && k.startsWith('webx11_mindmap_')) keys.push(k.replace('webx11_mindmap_', ''));
+ }
+ 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 <name>', 'warning');
+ localStorage.removeItem('webx11_mindmap_' + args[0]);
+ this.terminal.print('Deleted mindmap: ' + args[0], 'success');
+ }
+ _rename(args) {
+ if (args.length < 2) return this.terminal.print('Usage: mindmap rename <id> <text>', '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 <id> <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 <id> <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/chatstudio/newos4/modules/mindmap.js');
+}