return tag;
}
}
- task.appendChild(tag);
+ task.insertBefore(tag, task.getElementsByClassName("desc")[0]!);
return tag;
},
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.firstChild as HTMLInputElement;
+ const input = target.getElementsByTagName("input")[0]!;
if (
input.value === target.getAttribute("data-description") &&
input.selectionStart === input.value.length &&
}
},
+ 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 target = this.getTask(createTimestamp);
if (!target) return null;
target.setAttribute("data-priority", `${priority}`);
- for (const task of document.getElementsByClassName("task")) {
- if (task !== target && this.getPriority(task) < priority) {
- task.parentElement!.insertBefore(target, task);
- return target;
- }
- }
- document.getElementById("tasks")!.appendChild(target);
+ this.insertInPriorityOrder(target, target.parentElement!);
return target;
},
},
replay: function () {
+ document.getElementById("tasks")!.style.display = "none";
while (true) {
const entry = window.localStorage.getItem(`${prefix}${next_log_index}`);
if (entry === null) {
this.apply(entry);
next_log_index++;
}
+ document.getElementById("tasks")!.style.display = "";
},
};
}
todo: "White",
waiting: "MediumOrchid",
};
+ var currentTagView: string | null = null;
var currentViewState = "todo";
var taskFocusedBeforeJumpingToInput: HTMLElement | null = null;
var lastTagNameEntered = "";
},
beginEdit: function (event: Event) {
- var task = document.activeElement;
+ const task = this.currentTask();
if (!task) return;
- if (task.classList.contains("tag")) task = task.parentElement!;
const input = document.createElement("input");
const desc = task.getElementsByClassName("desc")[0];
const oldDescription = desc.textContent!;
},
beginTagEdit: function (event: Event) {
- const task = document.activeElement;
+ const task = this.currentTask();
if (!task) return;
const input = document.createElement("input");
input.classList.add("tag");
return target;
},
- firstVisibleTask: function () {
- for (const task of document.getElementsByClassName("task")) {
+ currentTask: function (): HTMLElement | null {
+ var target = document.activeElement;
+ if (!target) return null;
+ if (target.classList.contains("tag")) target = target.parentElement!;
+ return target as HTMLElement;
+ },
+
+ firstVisibleTask: function (root: Element | null = null) {
+ if (root === null) root = document.body;
+ for (const task of root.getElementsByClassName("task")) {
const state = task.getAttribute("data-state");
- if (task instanceof HTMLElement && (state === currentViewState || (currentViewState === "all" && state !== "deleted"))) {
+ if (
+ task instanceof HTMLElement &&
+ (state === currentViewState || (currentViewState === "all" && state !== "deleted")) &&
+ !task.classList.contains("hide")
+ ) {
return task;
}
}
},
focusTaskNameInput: function (event: Event) {
- if (document.activeElement instanceof HTMLElement) {
- taskFocusedBeforeJumpingToInput = document.activeElement;
- }
+ taskFocusedBeforeJumpingToInput = this.currentTask();
document.getElementById("taskName")!.focus();
window.scroll(0, 0);
event.preventDefault();
cursor = increment > 0 ? cursor.nextElementSibling : cursor.previousElementSibling;
if (!cursor || !(cursor instanceof HTMLElement)) break;
const state = cursor.getAttribute("data-state")!;
- if (state === currentViewState || (currentViewState === "all" && state !== "deleted")) {
+ if (
+ (state === currentViewState || (currentViewState === "all" && state !== "deleted")) &&
+ !cursor.classList.contains("hide")
+ ) {
offset -= increment;
valid_cursor = cursor;
}
},
makeBottomPriority: function (task: Element | null = null) {
- if (!task) task = document.activeElement;
+ if (!task) task = this.currentTask();
if (!task) return;
this.setPriority(task, document.getElementById("tasks")!.lastElementChild, null);
},
makeTopPriority: function (task: Element | null = null) {
- if (!task) task = document.activeElement;
+ if (!task) task = this.currentTask();
if (!task) return;
ui.setPriority(task.getAttribute("data-created")!, clock.now(), Model.getPriority(task));
task instanceof HTMLElement && task.focus();
},
- moveCursor: function (offset: number): boolean {
- const active = document.activeElement;
+ moveCursorLeft: function () {
+ const active = this.currentTask();
+ if (!active) return false;
+ if (active.parentElement!.classList.contains("task")) {
+ active.parentElement!.focus();
+ }
+ },
+
+ moveCursorRight: function () {
+ const active = this.currentTask();
+ if (!active) return false;
+ (this.firstVisibleTask(active) as HTMLElement | null)?.focus();
+ },
+
+ moveCursorVertically: function (offset: number): boolean {
+ const active = this.currentTask();
if (!active) return false;
const dest = this.visibleTaskAtOffset(active, offset);
if (dest !== active && dest instanceof HTMLElement) {
},
moveTask: function (offset: number) {
- const active = document.activeElement;
+ const active = this.currentTask();
if (!active) return;
const dest = this.visibleTaskAtOffset(active, offset);
if (dest === active) return; // Already extremal
ui.removeTag(target.parentElement!.getAttribute("data-created")!, target.textContent!);
},
+ resetTagView: function () {
+ currentTagView = null;
+ const taskList = document.getElementById("tasks")!;
+ for (const task of Array.from(document.getElementsByClassName("task"))) {
+ task.classList.remove("hide");
+ if (task.parentElement !== taskList) {
+ Model.insertInPriorityOrder(task, taskList);
+ }
+ }
+ },
+
+ resetView: function () {
+ this.setView("todo");
+ this.resetTagView();
+ },
+
returnFocusAfterInput: function (): boolean {
if (taskFocusedBeforeJumpingToInput) {
taskFocusedBeforeJumpingToInput.focus();
},
setState: function (newState: string) {
- const task = document.activeElement;
+ const task = this.currentTask();
if (!task) return;
const oldState = task.getAttribute("data-state")!;
if (newState === oldState) return;
const createTimestamp = task.getAttribute("data-created")!;
if (currentViewState !== "all" || newState == "deleted") {
- this.moveCursor(1) || this.moveCursor(-1);
+ this.moveCursorVertically(1) || this.moveCursorVertically(-1);
}
return ui.setState(createTimestamp, newState, oldState);
},
+ setTagView: function (tag: string | null = null) {
+ if (tag === null) {
+ const target = this.currentTag();
+ if (!target) return;
+ tag = target.textContent!;
+ }
+
+ if (currentTagView !== null) {
+ this.resetTagView();
+ }
+
+ const tasksWithTag = new Map();
+ for (const task of document.getElementsByClassName("task")) {
+ if (Model.hasTag(task, tag)) {
+ tasksWithTag.set(task.getElementsByClassName("desc")[0].textContent, [Model.getPriority(task), task]);
+ }
+ }
+
+ function highestPrioritySuperTask(t: Element) {
+ var maxPriority = -1;
+ var superTask = null;
+ for (const child of t.getElementsByClassName("tag")) {
+ const e = tasksWithTag.get(child.textContent);
+ if (e !== undefined && e[0] > maxPriority) {
+ maxPriority = e[0];
+ superTask = e[1];
+ }
+ }
+ return superTask;
+ }
+
+ for (const task of Array.from(document.getElementsByClassName("task"))) {
+ if (Model.hasTag(task, tag)) {
+ task.classList.remove("hide");
+ } else {
+ const superTask = highestPrioritySuperTask(task);
+ if (superTask !== null) {
+ Model.insertInPriorityOrder(task, superTask);
+ } else {
+ task.classList.add("hide");
+ }
+ }
+ }
+
+ currentTagView = tag;
+ },
+
setView: function (state: string) {
const sheet = (document.getElementById("viewStyle") as HTMLStyleElement).sheet!;
if (state === "all") {
sheet.removeRule(2);
sheet.removeRule(2);
currentViewState = state;
- if (document.activeElement?.getAttribute("data-state") !== state) {
+ if (this.currentTask()?.getAttribute("data-state") !== state) {
this.firstVisibleTask()?.focus();
}
},
+ setUntaggedView: function () {
+ if (currentTagView !== null) {
+ this.resetTagView();
+ }
+ for (const task of document.getElementsByClassName("task")) {
+ if (task.getElementsByClassName("tag").length === 0) {
+ task.classList.remove("hide");
+ } else {
+ task.classList.add("hide");
+ }
+ }
+ },
+
undo: function () {
const ret = ui.undo();
if (ret && ret instanceof HTMLElement) ret.focus();
if (event.key == "e") return window.scrollBy(0, (inputCount ?? 1) * scrollIncrement);
if (event.key == "y") return window.scrollBy(0, (inputCount ?? 1) * -scrollIncrement);
} else {
- if (event.key == "j") return browserUI.moveCursor(inputCount ?? 1);
- if (event.key == "k") return browserUI.moveCursor(-(inputCount ?? 1));
+ if (event.key == "h") return browserUI.moveCursorLeft();
+ if (event.key == "l") return browserUI.moveCursorRight();
+ if (event.key == "j") return browserUI.moveCursorVertically(inputCount ?? 1);
+ if (event.key == "k") return browserUI.moveCursorVertically(-(inputCount ?? 1));
if (event.key == "J") return browserUI.moveTask(inputCount ?? 1);
if (event.key == "K") return browserUI.moveTask(-(inputCount ?? 1));
if (event.key == "G") return browserUI.jumpCursor(inputCount ?? MAX_SAFE_INTEGER);
if (event.key == "a") return browserUI.setView("all");
if (event.key == "c") return browserUI.setView("cancelled");
if (event.key == "d") return browserUI.setView("done");
+ if (event.key == "i") return browserUI.setUntaggedView();
+ if (event.key == "p") return browserUI.setTagView("Project");
if (event.key == "q") return browserUI.setView("todo");
if (event.key == "s") return (inputState = InputState.VS);
- if (event.key == "v") return browserUI.setView("todo");
+ if (event.key == "T") return browserUI.resetTagView();
+ if (event.key == "t") return browserUI.setTagView();
+ if (event.key == "u") return browserUI.setUntaggedView();
+ if (event.key == "v") return browserUI.resetView();
if (event.key == "w") return browserUI.setView("waiting");
if (event.key == "x") return browserUI.setView("deleted");
} else if (inputState === InputState.VS) {