// Brain Dump Plugin - Quickly capture many tasks at once var _bdIntervals = { save: null, check: null }; function escapeHtml(str) { var div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } function countTasks(textarea) { if (!textarea || !textarea.value) return 0; return textarea.value.split('\n').filter(function (line) { return line.trim(); }).length; } function parseTasksWithSubTasks(text) { if (!text || typeof text !== 'string') { return null; } var lines = text.split('\n').filter(function (line) { return line.trim().length > 0; }); if (lines.length === 0) { return null; } var parsedLines = []; var plainTextCount = 0; // Parse all lines for (var l = 0; l < lines.length; l++) { var parsed = parseLineStructure(lines[l]); if (parsed) { parsedLines.push(parsed); if (!parsed.isBullet) { plainTextCount++; } } } // Check if we have mixed input (both bullets and plain text) var hasBullets = parsedLines.some(function (p) { return p.isBullet; }); var hasPlainText = parsedLines.some(function (p) { return !p.isBullet; }); if (hasBullets && hasPlainText) { PluginAPI.showSnack({ msg: 'Warning: ' + plainTextCount + ' plain-text line(s) detected. These will be added as regular tasks.', type: 'WARNING', }); } // Find the minimum indentation level to normalize var bulletLines = parsedLines.filter(function (p) { return p.isBullet; }); if (bulletLines.length > 0) { var minIndentLevel = Math.min.apply( Math, bulletLines.map(function (line) { return line.indentLevel; }), ); // Normalize indentation levels parsedLines.forEach(function (line) { if (line.isBullet) { line.indentLevel -= minIndentLevel; } }); } var mainTasks = []; var i = 0; var deeplyNestedWarnings = []; while (i < parsedLines.length) { var currentLine = parsedLines[i]; // Process main tasks (indent level 0 or plain text) if ( (currentLine.isBullet && currentLine.indentLevel === 0) || !currentLine.isBullet ) { var task = { title: currentLine.content, isCompleted: currentLine.isCompleted, subTasks: [], }; // Look ahead for sub-tasks (only if current is a bullet). // Plain-text lines between the parent and its indented bullets must // not terminate the sub-task scan — skip over them so the indented // bullet still attaches to this parent. The plain-text line itself // will be picked up as its own top-level task by the outer loop. if (currentLine.isBullet) { var j = i + 1; while (j < parsedLines.length) { var subLine = parsedLines[j]; // Stop at the next top-level bullet — a new main task starts here. if (subLine.isBullet && subLine.indentLevel === 0) break; if (subLine.isBullet && subLine.indentLevel > 0) { if (subLine.indentLevel > 1) { deeplyNestedWarnings.push({ title: subLine.content, depth: subLine.indentLevel, }); } task.subTasks.push({ title: subLine.content, isCompleted: subLine.isCompleted, }); } // Plain-text lines are intentionally skipped here; they are handled // by the outer loop on its next iteration. j++; } } mainTasks.push(task); i++; } else { // Orphan indented bullet (already consumed as sub-task above, or no parent). i++; } } // Show warning for deeply-nested items if (deeplyNestedWarnings.length > 0) { PluginAPI.showSnack({ msg: deeplyNestedWarnings.length + ' deeply-nested item(s) flattened to sub-task level. Sub-tasks in Super Productivity do not support nesting.', type: 'INFO', }); } return mainTasks; } function parseLineStructure(line) { // Calculate indentation level var indentMatch = line.match(/^(\s*)/); var indentLevel = 0; if (indentMatch && indentMatch[1]) { var whitespace = indentMatch[1]; var tabCount = (whitespace.match(/\t/g) || []).length; var spaceCount = (whitespace.match(/ /g) || []).length; // Support both 2-space and 4-space conventions // For 4-space indent: Math.floor(4/4) = 1 // For 2-space indent: Math.floor(2/2) = 1 indentLevel = tabCount + Math.floor(spaceCount / 4) || Math.floor(spaceCount / 2); } var trimmedLine = line.trim(); if (trimmedLine.length === 0) { return null; } // Check for checkbox list items: - [ ] or - [x] var checkboxMatch = trimmedLine.match(/^-\s*\[([ x])\]\s*(.+)$/); if (checkboxMatch) { return { indentLevel: indentLevel, content: checkboxMatch[2].trim(), isCompleted: checkboxMatch[1] === 'x', isBullet: true, }; } // Check for bullet list items: - or * var bulletMatch = trimmedLine.match(/^[-*]\s+(.+)$/); if (bulletMatch) { return { indentLevel: indentLevel, content: bulletMatch[1].trim(), isCompleted: false, isBullet: true, }; } // Plain text lines no longer silently dropped return { indentLevel: 0, content: trimmedLine, isCompleted: false, isBullet: false, }; } function todayStr() { var d = new Date(); return ( d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0') ); } function _bdCleanup() { clearInterval(_bdIntervals.save); clearInterval(_bdIntervals.check); _bdIntervals.save = null; _bdIntervals.check = null; } async function openBrainDump() { var projectsPromise = PluginAPI.getAllProjects(); var dataPromise = PluginAPI.loadSyncedData(); var results = await Promise.all([projectsPromise, dataPromise]); var projects = results[0]; var savedData = results[1]; var draft = {}; if (savedData) { try { draft = JSON.parse(savedData); } catch (e) { /* ignore corrupt draft */ } } var savedText = draft.text || ''; var inboxProject = projects.find(function (p) { return p.id === 'INBOX_PROJECT'; }); var savedProjectId = draft.projectId || (inboxProject ? inboxProject.id : ''); var savedDueDay = draft.dueDay !== undefined ? draft.dueDay : todayStr(); var activeProjects = projects.filter(function (p) { return !p.isArchived; }); var projectOptions = activeProjects .map(function (p) { return ( '' ); }) .join(''); // SUGGESTION: Expose expected format to users var placeholderText = 'One task per line. For sub-tasks, indent with 4 spaces:\n\n- Main task\n - Sub task\n - Another sub task\n\nPlain text tasks are also supported.'; var html = '