Add quick actions toolbar for todo
This commit is contained in:
@@ -774,6 +774,15 @@ aside.sidebar:empty { display: none; }
|
|||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
padding-right: var(--space-2);
|
padding-right: var(--space-2);
|
||||||
}
|
}
|
||||||
|
/* Compact action toolbar in the widget header. Buttons are the app's standard
|
||||||
|
bracketed .btn/.btn-tool; the row just holds them together and lets them wrap
|
||||||
|
in the narrow rail rather than overflow. */
|
||||||
|
.todo-toolbar { flex-wrap: wrap; }
|
||||||
|
/* Roomier tap targets when the widget is surfaced full-screen on mobile. */
|
||||||
|
.overlay-body .todo-toolbar .btn {
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
padding: var(--space-1) var(--space-2);
|
||||||
|
}
|
||||||
.todo-unsaved { color: var(--primary-hover); font-size: var(--font-xs); }
|
.todo-unsaved { color: var(--primary-hover); font-size: var(--font-xs); }
|
||||||
/* The scrolling children run edge-to-edge (the aside has no right padding) so
|
/* The scrolling children run edge-to-edge (the aside has no right padding) so
|
||||||
their scrollbars are flush with the divider; padding-right keeps their text
|
their scrollbars are flush with the divider; padding-right keeps their text
|
||||||
|
|||||||
+80
-4
@@ -32,6 +32,7 @@
|
|||||||
var loadingBundle = false;
|
var loadingBundle = false;
|
||||||
var wantFocus = false; // user reached for the editor before it mounted
|
var wantFocus = false; // user reached for the editor before it mounted
|
||||||
var knownETag = null; // ETag of the content currently in the buffer
|
var knownETag = null; // ETag of the content currently in the buffer
|
||||||
|
var pendingAction = null; // toolbar action clicked before the editor mounted
|
||||||
|
|
||||||
fetch(TODO_URL, { credentials: 'same-origin', cache: 'no-store' }).then(function (r) {
|
fetch(TODO_URL, { credentials: 'same-origin', cache: 'no-store' }).then(function (r) {
|
||||||
if (r.status === 404) return null; // no todo.txt — no widget
|
if (r.status === 404) return null; // no todo.txt — no widget
|
||||||
@@ -47,14 +48,22 @@
|
|||||||
|
|
||||||
var head = document.createElement('div');
|
var head = document.createElement('div');
|
||||||
head.className = 'row space-between todo-rail-head';
|
head.className = 'row space-between todo-rail-head';
|
||||||
var label = document.createElement('span');
|
|
||||||
label.className = 'muted';
|
// Toolbar replaces the old static "todo.txt" label. Each button acts on
|
||||||
label.textContent = 'todo.txt';
|
// the current line of the editor via runAction; the labels render
|
||||||
|
// 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('del', 'Delete this line', actions.del, 'danger'));
|
||||||
|
|
||||||
marker = document.createElement('span');
|
marker = document.createElement('span');
|
||||||
marker.className = 'todo-unsaved';
|
marker.className = 'todo-unsaved';
|
||||||
marker.textContent = 'unsaved';
|
marker.textContent = 'unsaved';
|
||||||
marker.hidden = true;
|
marker.hidden = true;
|
||||||
head.appendChild(label);
|
head.appendChild(toolbar);
|
||||||
head.appendChild(marker);
|
head.appendChild(marker);
|
||||||
|
|
||||||
bodyEl = document.createElement('div');
|
bodyEl = document.createElement('div');
|
||||||
@@ -131,9 +140,12 @@
|
|||||||
// Only steal focus if the user actually reached for the editor — the
|
// Only steal focus if the user actually reached for the editor — the
|
||||||
// passive idle mount must not grab focus or scroll the page.
|
// passive idle mount must not grab focus or scroll the page.
|
||||||
if (wantFocus) view.focus();
|
if (wantFocus) view.focus();
|
||||||
|
// A toolbar button clicked during the mount runs now that the view exists.
|
||||||
|
if (pendingAction) { var p = pendingAction; pendingAction = null; p(view); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function mountFallback() {
|
function mountFallback() {
|
||||||
|
pendingAction = null; // the plain textarea can't run CM-based actions
|
||||||
fallback = document.createElement('textarea');
|
fallback = document.createElement('textarea');
|
||||||
fallback.className = 'input todo-fallback';
|
fallback.className = 'input todo-fallback';
|
||||||
fallback.value = readView.textContent;
|
fallback.value = readView.textContent;
|
||||||
@@ -144,6 +156,70 @@
|
|||||||
if (wantFocus) fallback.focus();
|
if (wantFocus) fallback.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 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.
|
||||||
|
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) ' : '';
|
||||||
|
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() + ' '));
|
||||||
|
},
|
||||||
|
del: function (v) { CM.deleteLine(v); },
|
||||||
|
};
|
||||||
|
|
||||||
|
function runAction(fn) {
|
||||||
|
if (view) { fn(view); view.focus(); return; }
|
||||||
|
if (fallback) return; // unstyled fallback has no CM view to act on
|
||||||
|
// Editor still mounting — remember the action and mount it now, focused.
|
||||||
|
pendingAction = fn;
|
||||||
|
wantFocus = true;
|
||||||
|
upgrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toolButton(label, title, fn, extraClass) {
|
||||||
|
var b = document.createElement('button');
|
||||||
|
b.type = 'button';
|
||||||
|
b.className = 'btn btn-tool' + (extraClass ? ' ' + extraClass : '');
|
||||||
|
b.textContent = label;
|
||||||
|
b.title = title;
|
||||||
|
// Keep the editor focused (and the mobile keyboard up) when tapping the
|
||||||
|
// toolbar, mirroring the page editor — preventDefault blocks the focus
|
||||||
|
// shift on mousedown; the click still fires.
|
||||||
|
b.addEventListener('mousedown', function (e) { e.preventDefault(); });
|
||||||
|
b.addEventListener('click', function () { runAction(fn); });
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
function currentText() {
|
function currentText() {
|
||||||
if (view) return view.state.doc.toString();
|
if (view) return view.state.doc.toString();
|
||||||
if (fallback) return fallback.value;
|
if (fallback) return fallback.value;
|
||||||
|
|||||||
Reference in New Issue
Block a user