X-Git-Url: http://git.scottworley.com/vopamoi/blobdiff_plain/7b5b90b9a161fdeeb9842029a369d0484146ef4d..360beccbbfadcc25606af08d13d64fd878469ecc:/vopamoi.ts diff --git a/vopamoi.ts b/vopamoi.ts index 5ac82ef..31a4cdc 100644 --- a/vopamoi.ts +++ b/vopamoi.ts @@ -29,10 +29,19 @@ function Clock() { } const clock = Clock(); +// Returns a promise for a hue based on a hash of the string +function hashHue(str: string) { + // Using crypto for this is overkill + return crypto.subtle.digest("SHA-256", new TextEncoder().encode(str)).then((buf) => (new Uint16Array(buf)[0] * 360) / 2 ** 16); +} + const Model = { addTask: function (timestamp: string, description: string): Element { const task = document.createElement("div"); - task.appendChild(document.createTextNode(description)); + 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); @@ -44,10 +53,19 @@ const Model = { 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.appendChild(tag); return tag; }, @@ -72,11 +90,20 @@ const Model = { target.setAttribute("data-description", newDescription); } } else { - target.textContent = newDescription; + target.getElementsByClassName("desc")[0].textContent = newDescription; } return target; }, + hasTag: function (task: Element, tag: string): Element | null { + for (const child of task.getElementsByClassName("tag")) { + if (child.textContent === tag) { + return child; + } + } + return null; + }, + getPriority: function (task: Element): number { if (task.hasAttribute("data-priority")) { return parseFloat(task.getAttribute("data-priority")!); @@ -204,6 +231,7 @@ enum CommitOrAbort { function BrowserUI() { var currentViewState = "todo"; var taskFocusedBeforeJumpingToInput: HTMLElement | null = null; + var lastTagNameEntered = ""; return { addTask: function (event: KeyboardEvent) { const input = document.getElementById("taskName"); @@ -226,11 +254,12 @@ function BrowserUI() { const task = document.activeElement; if (!task) return; const input = document.createElement("input"); - const oldDescription = task.textContent!; + const desc = task.getElementsByClassName("desc")[0]; + const oldDescription = desc.textContent!; task.setAttribute("data-description", oldDescription); input.value = oldDescription; input.addEventListener("blur", this.completeEdit, { once: true }); - task.textContent = ""; + desc.textContent = ""; task.insertBefore(input, task.firstChild); input.focus(); event.preventDefault(); @@ -242,14 +271,17 @@ function BrowserUI() { const input = document.createElement("input"); input.classList.add("tag"); input.addEventListener("blur", this.completeTagEdit, { once: true }); + input.value = lastTagNameEntered; task.appendChild(input); input.focus(); + input.select(); event.preventDefault(); }, completeEdit: function (event: Event, resolution: CommitOrAbort = CommitOrAbort.Commit) { const input = event.target as HTMLInputElement; const task = input.parentElement!; + const desc = task.getElementsByClassName("desc")[0]; const oldDescription = task.getAttribute("data-description")!; const newDescription = input.value; input.removeEventListener("blur", this.completeEdit); @@ -257,7 +289,7 @@ function BrowserUI() { task.removeAttribute("data-description"); task.focus(); if (newDescription === oldDescription || resolution === CommitOrAbort.Abort) { - task.textContent = oldDescription; + desc.textContent = oldDescription; } else { ui.edit(task.getAttribute("data-created")!, newDescription, oldDescription); } @@ -270,7 +302,10 @@ function BrowserUI() { input.removeEventListener("blur", this.completeTagEdit); task.removeChild(input); task.focus(); - ui.addTag(task.getAttribute("data-created")!, newTagName); + if (resolution === CommitOrAbort.Commit && newTagName && !Model.hasTag(task, newTagName)) { + ui.addTag(task.getAttribute("data-created")!, newTagName); + lastTagNameEntered = newTagName; + } }, firstVisibleTask: function () {