1
0
silverbullet/web/cm_plugins/task.ts

74 lines
2.2 KiB
TypeScript
Raw Normal View History

import { Decoration, NodeType, syntaxTree, WidgetType } from "../deps.ts";
import { decoratorStateField, isCursorInRange } from "./util.ts";
/**
* Widget to render checkbox for a task list item.
*/
class CheckboxWidget extends WidgetType {
constructor(
public checked: boolean,
readonly pos: number,
readonly clickCallback: (pos: number) => void,
) {
super();
}
toDOM(): HTMLElement {
const wrap = document.createElement("span");
wrap.classList.add("sb-checkbox");
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = this.checked;
checkbox.addEventListener("click", (e) => {
// Let the click handler handle this
e.stopPropagation();
this.clickCallback(this.pos);
});
wrap.appendChild(checkbox);
return wrap;
}
}
export function taskListPlugin(
{ onCheckboxClick }: { onCheckboxClick: (pos: number) => void },
) {
return decoratorStateField((state) => {
const widgets: any[] = [];
syntaxTree(state).iterate({
enter({ type, from, to, node }) {
if (type.name !== "Task") return;
let checked = false;
// Iterate inside the task node to find the checkbox
node.toTree().iterate({
enter: (ref) => iterateInner(ref.type, ref.from, ref.to),
});
if (checked) {
widgets.push(
Decoration.mark({
tagName: "span",
class: "cm-task-checked",
}).range(from, to),
);
}
function iterateInner(type: NodeType, nfrom: number, nto: number) {
if (type.name !== "TaskMarker") return;
if (isCursorInRange(state, [from + nfrom, from + nto])) return;
const checkbox = state.sliceDoc(from + nfrom, from + nto);
// Checkbox is checked if it has a 'x' in between the []
if ("xX".includes(checkbox[1])) checked = true;
const dec = Decoration.replace({
widget: new CheckboxWidget(
checked,
from + nfrom + 1,
onCheckboxClick,
),
});
widgets.push(dec.range(from + nfrom, from + nto));
}
},
});
return Decoration.set(widgets, true);
});
}