-const Model = {
- addTask: function (timestamp: string, description: string): Element {
- const task = document.createElement("div");
- const desc = document.createElement("span");
- desc.textContent = description;
- desc.classList.add("desc");
- task.appendChild(desc);
- task.classList.add("task");
- task.setAttribute("tabindex", "0");
- task.setAttribute("data-created", timestamp);
- task.setAttribute("data-state", "todo");
- const tasks = document.getElementById("tasks")!;
- tasks.insertBefore(task, tasks.firstElementChild);
- return task;
- },
-
- addTag: function (createTimestamp: string, tagName: string): Element | null {
- const task = this.getTask(createTimestamp);
- if (!task) return null;
- const existingTag = this.hasTag(task, tagName);
- if (existingTag) return existingTag;
- const tag = document.createElement("span");
- tag.appendChild(document.createTextNode(tagName));
- tag.classList.add("tag");
- tag.setAttribute("tabindex", "0");
- hashHue(tagName).then((hue) => (tag.style.backgroundColor = `hsl(${hue},90%,45%)`));
- for (const child of task.getElementsByClassName("tag")) {
- if (tagName > child.textContent!) {
- task.insertBefore(tag, child);
- return tag;
+function Model() {
+ return {
+ addTask: function (timestamp: string, description: string): Element {
+ const task = document.createElement("div");
+ const desc = document.createElement("span");
+ desc.textContent = description;
+ desc.classList.add("desc");
+ task.appendChild(desc);
+ task.classList.add("task");
+ task.setAttribute("tabindex", "0");
+ task.setAttribute("data-created", timestamp);
+ task.setAttribute("data-state", "todo");
+ const tasks = document.getElementById("tasks")!;
+ tasks.insertBefore(task, tasks.firstElementChild);
+ return task;
+ },
+
+ addTag: function (createTimestamp: string, tagName: string): Element | null {
+ const task = this.getTask(createTimestamp);
+ if (!task) return null;
+ const existingTag = this.hasTag(task, tagName);
+ if (existingTag) return existingTag;
+ const tag = document.createElement("span");
+ tag.appendChild(document.createTextNode(tagName));
+ tag.classList.add("tag");
+ tag.setAttribute("tabindex", "0");
+ hashHue(tagName).then((hue) => (tag.style.backgroundColor = `hsl(${hue},90%,45%)`));
+ for (const child of task.getElementsByClassName("tag")) {
+ if (tagName > child.textContent!) {
+ task.insertBefore(tag, child);
+ return tag;
+ }
- }
- task.insertBefore(tag, task.getElementsByClassName("desc")[0]!);
- return tag;
- },
-
- edit: function (createTimestamp: string, newDescription: string): Element | null {
- const target = this.getTask(createTimestamp);
- if (!target) return null;
- if (target.hasAttribute("data-description")) {
- // Oh no: An edit has arrived from a replica while a local edit is in progress.
- const input = target.getElementsByTagName("input")[0]!;
- if (
- input.value === target.getAttribute("data-description") &&
- input.selectionStart === input.value.length &&
- input.selectionEnd === input.value.length
- ) {
- // No local changes have actually been made yet. Change the contents of the edit box!
- input.value = newDescription;
+ task.insertBefore(tag, task.getElementsByClassName("desc")[0]!);
+ return tag;
+ },
+
+ edit: function (createTimestamp: string, newDescription: string): Element | null {
+ const target = this.getTask(createTimestamp);
+ if (!target) return null;
+ if (target.hasAttribute("data-description")) {
+ // Oh no: An edit has arrived from a replica while a local edit is in progress.
+ const input = target.getElementsByTagName("input")[0]!;
+ if (
+ input.value === target.getAttribute("data-description") &&
+ input.selectionStart === input.value.length &&
+ input.selectionEnd === input.value.length
+ ) {
+ // No local changes have actually been made yet. Change the contents of the edit box!
+ input.value = newDescription;
+ } else {
+ // No great options.
+ // Prefer not to interrupt the local user's edit.
+ // The remote edit is mostly lost; this mostly becomes last-write-wins.
+ target.setAttribute("data-description", newDescription);
+ }
- getPriority: function (task: Element): number {
- if (task.hasAttribute("data-priority")) {
- return parseFloat(task.getAttribute("data-priority")!);
- }
- return parseFloat(task.getAttribute("data-created")!);
- },
+ getPriority: function (task: Element): number {
+ if (task.hasAttribute("data-priority")) {
+ return parseFloat(task.getAttribute("data-priority")!);
+ }
+ return parseFloat(task.getAttribute("data-created")!);
+ },
+
+ getTask: function (createTimestamp: string) {
+ for (const task of document.getElementsByClassName("task")) {
+ if (task.getAttribute("data-created") === createTimestamp) {
+ return task;
+ }
+ }
+ },
- getTask: function (createTimestamp: string) {
- for (const task of document.getElementsByClassName("task")) {
- if (task.getAttribute("data-created") === createTimestamp) {
- return task;
+ insertInPriorityOrder: function (task: Element, dest: Element) {
+ const priority = this.getPriority(task);
+ for (const t of dest.children) {
+ if (t !== task && this.getPriority(t) < priority) {
+ dest.insertBefore(task, t);
+ return;
+ }
+ dest.appendChild(task);
+ },
+
+ removeTag: function (createTimestamp: string, tagName: string) {
+ const task = this.getTask(createTimestamp);
+ if (!task) return null;
+ const tag = this.hasTag(task, tagName);
+ if (!tag) return;
+ task.removeChild(tag);
+ if (task instanceof HTMLElement) task.focus();
+ },
+
+ setPriority: function (createTimestamp: string, priority: number): Element | null {
+ const target = this.getTask(createTimestamp);
+ if (!target) return null;
+ target.setAttribute("data-priority", `${priority}`);
+ this.insertInPriorityOrder(target, target.parentElement!);
+ return target;
+ },
- insertInPriorityOrder: function (task: Element, dest: Element) {
- const priority = this.getPriority(task);
- for (const t of dest.children) {
- if (t !== task && this.getPriority(t) < priority) {
- dest.insertBefore(task, t);
+ setState: function (stateTimestamp: string, createTimestamp: string, state: string) {
+ const task = this.getTask(createTimestamp);
+ if (!task) return;
+ task.setAttribute("data-state", state);
+ var date = task.getElementsByClassName("statedate")[0];
+ if (state === "todo") {
+ task.removeChild(date);
- }
- dest.appendChild(task);
- },
-
- removeTag: function (createTimestamp: string, tagName: string) {
- const task = this.getTask(createTimestamp);
- if (!task) return null;
- const tag = this.hasTag(task, tagName);
- if (!tag) return;
- task.removeChild(tag);
- if (task instanceof HTMLElement) task.focus();
- },
-
- setPriority: function (createTimestamp: string, priority: number): Element | null {
- const target = this.getTask(createTimestamp);
- if (!target) return null;
- target.setAttribute("data-priority", `${priority}`);
- this.insertInPriorityOrder(target, target.parentElement!);
- return target;
- },
-
- setState: function (stateTimestamp: string, createTimestamp: string, state: string) {
- const task = this.getTask(createTimestamp);
- if (!task) return;
- task.setAttribute("data-state", state);
- var date = task.getElementsByClassName("statedate")[0];
- if (state === "todo") {
- task.removeChild(date);
- return;
- }
- if (!date) {
- date = document.createElement("span");
- date.classList.add("statedate");
- task.insertBefore(date, task.firstChild);
- }
- const d = new Date(parseInt(stateTimestamp));
- date.textContent = `${d.getFullYear()}-${`${d.getMonth() + 1}`.padStart(2, "0")}-${`${d.getDate()}`.padStart(2, "0")}`;
- },
-};
+ if (!date) {
+ date = document.createElement("span");
+ date.classList.add("statedate");
+ task.insertBefore(date, task.firstChild);
+ }
+ const d = new Date(parseInt(stateTimestamp));
+ date.textContent = `${d.getFullYear()}-${`${d.getMonth() + 1}`.padStart(2, "0")}-${`${d.getDate()}`.padStart(2, "0")}`;
+ },
+ };
+}
+const model = Model();
apply: function (entry: string) {
const [timestamp, command, data] = splitN(entry, " ", 2);
if (command == "Create") {
apply: function (entry: string) {
const [timestamp, command, data] = splitN(entry, " ", 2);
if (command == "Create") {
console.assert(aPriority > bPriority, aPriority, ">", bPriority);
const span = aPriority - bPriority;
const newPriority = bPriority + 0.1 * span + 0.8 * span * Math.random();
console.assert(aPriority > newPriority && newPriority > bPriority, aPriority, ">", newPriority, ">", bPriority);
const newPriorityRounded = Math.round(newPriority);
const okToRound = aPriority > newPriorityRounded && newPriorityRounded > bPriority;
console.assert(aPriority > bPriority, aPriority, ">", bPriority);
const span = aPriority - bPriority;
const newPriority = bPriority + 0.1 * span + 0.8 * span * Math.random();
console.assert(aPriority > newPriority && newPriority > bPriority, aPriority, ">", newPriority, ">", bPriority);
const newPriorityRounded = Math.round(newPriority);
const okToRound = aPriority > newPriorityRounded && newPriorityRounded > bPriority;