Inline question choices, compact one-click inbox cards, drop snooze

This commit is contained in:
Ian Gulliver
2026-10-14 23:34:39 -07:00
parent 4dc86e5479
commit c7cfad88a1
30 changed files with 1022 additions and 647 deletions
+51 -10
View File
@@ -1,4 +1,8 @@
import { h } from "./dom.js";
const choiceMarker = /^(\([ *]\)|\[[ xX]\])\s+(\S.*?)\s*$/;
const detailsOpen = /^\s*<details>\s*(?:<summary>(.*?)<\/summary>)?\s*$/i;
const summaryLine = /^\s*<summary>(.*?)<\/summary>\s*$/i;
const detailsClose = /^\s*<\/details>\s*$/i;
const fence = /^\s*(```|~~~)/;
const heading = /^(#{1,6})\s+(.*)$/;
const bullet = /^(\s*)[-*+]\s+(.*)$/;
@@ -6,13 +10,13 @@ const ordered = /^(\s*)(\d+)[.)]\s+(.*)$/;
const quote = /^\s*>\s?(.*)$/;
const rule = /^\s*([-*_])(\s*\1){2,}\s*$/;
const tableSep = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;
export function markdown(src, className = "md") {
return h("div", { class: className }, ...blocks(src.replace(/\r\n?/g, "\n").split("\n")));
export function markdown(src, className = "md", choice) {
return h("div", { class: className }, ...blocks(src.replace(/\r\n?/g, "\n").split("\n"), choice));
}
export function inlineMarkdown(src) {
return h("span", { class: "md-inline" }, ...inline(src));
}
function blocks(lines) {
function blocks(lines, choice) {
const out = [];
let i = 0;
while (i < lines.length) {
@@ -34,6 +38,34 @@ function blocks(lines) {
out.push(h("pre", { class: "md-code" }, h("code", {}, body.join("\n"))));
continue;
}
if (detailsClose.test(line)) {
i++;
continue;
}
const d = detailsOpen.exec(line);
if (d) {
let title = d[1];
i++;
if (title === undefined && i < lines.length && summaryLine.test(lines[i])) {
title = summaryLine.exec(lines[i])[1];
i++;
}
const body = [];
let depth = 1;
while (i < lines.length) {
const l = lines[i];
i++;
if (detailsOpen.test(l)) {
depth++;
}
else if (detailsClose.test(l) && --depth === 0) {
break;
}
body.push(l);
}
out.push(h("details", { class: "md-details" }, h("summary", {}, ...inline(title?.trim() || "Details")), h("div", { class: "md-details-body" }, ...blocks(body, choice))));
continue;
}
const hd = heading.exec(line);
if (hd) {
const level = Math.min(hd[1].length + 2, 6);
@@ -56,7 +88,7 @@ function blocks(lines) {
continue;
}
if (bullet.test(line) || ordered.test(line)) {
const [list, next] = parseList(lines, i);
const [list, next] = parseList(lines, i, choice);
out.push(list);
i = next;
continue;
@@ -83,6 +115,8 @@ function blocks(lines) {
function startsBlock(lines, i) {
const line = lines[i];
return (fence.test(line) ||
detailsOpen.test(line) ||
detailsClose.test(line) ||
heading.test(line) ||
rule.test(line) ||
quote.test(line) ||
@@ -103,7 +137,7 @@ function joinLines(lines) {
function indentOf(line) {
return line.length - line.trimStart().length;
}
function parseList(lines, start) {
function parseList(lines, start, choice) {
const first = lines[start];
const isOrdered = !bullet.test(first);
const base = indentOf(first);
@@ -141,27 +175,34 @@ function parseList(lines, start) {
body.push(indentOf(next) > base ? next.slice(Math.min(indentOf(next), base + 2)) : next.trim());
i++;
}
list.append(item(body));
list.append(item(body, isOrdered ? undefined : choice));
while (i < lines.length && lines[i].trim() === "" && lines[i + 1] !== undefined && indentOf(lines[i + 1]) === base && (isOrdered ? ordered : bullet).test(lines[i + 1])) {
i++;
}
}
return [list, i];
}
function item(body) {
function item(body, choice) {
const li = h("li");
const m = choice && choiceMarker.exec(body[0]);
const picker = m ? choice(m[2]) : null;
if (picker) {
li.className = "choice";
li.append(picker);
}
const first = picker ? [] : [body[0]];
const rest = body.slice(1);
if (rest.every((l) => !startsBlockLine(l)) && !rest.includes("")) {
li.append(...joinLines(body.map((l) => l.trim())));
li.append(...joinLines([...first, ...rest].map((l) => l.trim())));
return li;
}
const head = [body[0]];
const head = first;
let k = 1;
while (k < body.length && body[k] !== "" && !startsBlockLine(body[k])) {
head.push(body[k].trim());
k++;
}
li.append(...joinLines(head), ...blocks(body.slice(k)));
li.append(...joinLines(head), ...blocks(body.slice(k), choice));
return li;
}
function startsBlockLine(line) {