diff --git a/assets/todo-rail.js b/assets/todo-rail.js index e35c8d7..c5e4c4e 100644 --- a/assets/todo-rail.js +++ b/assets/todo-rail.js @@ -54,9 +54,8 @@ // bracketed ([done] [pri] …) to match the app's button aesthetic. var toolbar = document.createElement('div'); toolbar.className = 'row gap-1 todo-toolbar'; - toolbar.appendChild(toolButton('done', 'Toggle done (x) on this line', actions.done)); - toolbar.appendChild(toolButton('pri', 'Cycle priority (A → B → C → none)', actions.priority)); - toolbar.appendChild(toolButton('date', "Insert today's date at the cursor", actions.date)); + toolbar.appendChild(toolButton('pri', 'Cycle priority (A → B → C → D → none)', actions.priority)); + toolbar.appendChild(toolButton('sort', 'Sort by priority, then context, then text', actions.sort)); toolbar.appendChild(toolButton('del', 'Delete this line', actions.del, 'danger')); marker = document.createElement('span'); @@ -158,41 +157,39 @@ // --- Toolbar --------------------------------------------------------- - function pad2(n) { return (n < 10 ? '0' : '') + n; } - - // Local ISO date (YYYY-MM-DD), matching the wiki's date convention. - function todayISO() { - var d = new Date(); - return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()); - } - function mainLine(v) { return v.state.doc.lineAt(v.state.selection.main.head); } // Each action operates on the current line of the CM view. Kept deliberately // simple (string edits at the line start / cursor) — no task parsing, per // the todo.txt widget's highlighting-only remit. var actions = { - // Toggle the `x ` done prefix (with completion date). Stripping also - // removes the optional date that followed it. - done: function (v) { - var line = mainLine(v); - var m = /^x (?:\d{4}-\d{2}-\d{2} )?/.exec(line.text); - if (m) { - v.dispatch({ changes: { from: line.from, to: line.from + m[0].length } }); - } else { - v.dispatch({ changes: { from: line.from, insert: 'x ' + todayISO() + ' ' } }); - } - }, - // Cycle the leading priority: none → (A) → (B) → (C) → none. + // Cycle the leading priority: none → (A) → (B) → (C) → (D) → none. priority: function (v) { var line = mainLine(v); var m = /^\(([A-Z])\) /.exec(line.text); - var next = !m ? '(A) ' : m[1] === 'A' ? '(B) ' : m[1] === 'B' ? '(C) ' : ''; + var next = !m ? '(A) ' : m[1] === 'A' ? '(B) ' : m[1] === 'B' ? '(C) ' + : m[1] === 'C' ? '(D) ' : ''; v.dispatch({ changes: { from: line.from, to: line.from + (m ? m[0].length : 0), insert: next } }); }, - // Insert today's date at the cursor. - date: function (v) { - v.dispatch(v.state.replaceSelection(todayISO() + ' ')); + // Sort the whole list. A plain case-insensitive line sort yields the + // desired priority → context → text order for free: "(A) " sorts ahead + // of everything (the "(" leads), so prioritised lines rise to the top in + // A/B/C/D order; the "@context" written right after the priority breaks + // ties, and the remaining text breaks those. Completed "x …" lines fall + // to the bottom (x sorts late). Blank lines are dropped so they don't + // float to the top. + sort: function (v) { + var doc = v.state.doc.toString(); + var trailingNL = /\n$/.test(doc); + var lines = doc.split('\n').filter(function (l) { return l.trim() !== ''; }); + lines.sort(function (a, b) { + var la = a.toLowerCase(), lb = b.toLowerCase(); + if (la !== lb) return la < lb ? -1 : 1; + return a < b ? -1 : a > b ? 1 : 0; + }); + var out = lines.join('\n') + (trailingNL ? '\n' : ''); + if (out === doc) return; + v.dispatch({ changes: { from: 0, to: v.state.doc.length, insert: out } }); }, del: function (v) { CM.deleteLine(v); }, };