const log = Log();
function UI() {
- const undoLog: string[] = [];
+ const undoLog: string[][] = [];
+ const redoLog: string[][] = [];
+ function perform(forward: string, reverse: string) {
+ undoLog.push([reverse, forward]);
+ return log.recordAndApply(`${clock.now()} ${forward}`);
+ }
return {
addTask: function (description: string): Element {
const now = clock.now();
- undoLog.push(`State ${now} deleted`);
+ undoLog.push([`State ${now} deleted`, `State ${now} todo`]);
return <Element>log.recordAndApply(`${now} Create ${description}`);
},
addTag: function (createTimestamp: string, tag: string) {
- undoLog.push(`Untag ${createTimestamp} ${tag}`);
- return log.recordAndApply(`${clock.now()} Tag ${createTimestamp} ${tag}`);
+ return perform(`Tag ${createTimestamp} ${tag}`, `Untag ${createTimestamp} ${tag}`);
},
edit: function (createTimestamp: string, newDescription: string, oldDescription: string) {
- undoLog.push(`Edit ${createTimestamp} ${oldDescription}`);
- return log.recordAndApply(`${clock.now()} Edit ${createTimestamp} ${newDescription}`);
+ return perform(`Edit ${createTimestamp} ${newDescription}`, `Edit ${createTimestamp} ${oldDescription}`);
},
removeTag: function (createTimestamp: string, tag: string) {
- undoLog.push(`Tag ${createTimestamp} ${tag}`);
- return log.recordAndApply(`${clock.now()} Untag ${createTimestamp} ${tag}`);
+ return perform(`Untag ${createTimestamp} ${tag}`, `Tag ${createTimestamp} ${tag}`);
},
setPriority: function (createTimestamp: string, newPriority: number, oldPriority: number) {
- undoLog.push(`Priority ${createTimestamp} ${oldPriority}`);
- return log.recordAndApply(`${clock.now()} Priority ${createTimestamp} ${newPriority}`);
+ return perform(`Priority ${createTimestamp} ${newPriority}`, `Priority ${createTimestamp} ${oldPriority}`);
},
setState: function (createTimestamp: string, newState: string, oldState: string) {
- undoLog.push(`State ${createTimestamp} ${oldState}`);
- return log.recordAndApply(`${clock.now()} State ${createTimestamp} ${newState}`);
+ return perform(`State ${createTimestamp} ${newState}`, `State ${createTimestamp} ${oldState}`);
},
undo: function () {
- if (undoLog.length > 0) {
- return log.recordAndApply(`${clock.now()} ${undoLog.pop()}`);
+ const entry = undoLog.pop();
+ if (entry) {
+ redoLog.push(entry);
+ return log.recordAndApply(`${clock.now()} ${entry[0]}`);
+ }
+ },
+ redo: function () {
+ const entry = redoLog.pop();
+ if (entry) {
+ undoLog.push(entry);
+ return log.recordAndApply(`${clock.now()} ${entry[1]}`);
}
},
};
return valid_cursor;
},
+ jumpCursor: function (position: number) {
+ const first = this.firstVisibleTask();
+ if (!first) return;
+ const dest = this.visibleTaskAtOffset(first, position - 1);
+ if (dest instanceof HTMLElement) dest.focus();
+ },
+
makeTopPriority: function (task: Element | null = null) {
if (!task) task = document.activeElement;
if (!task) return;
const ret = ui.undo();
if (ret && ret instanceof HTMLElement) ret.focus();
},
+ redo: function () {
+ const ret = ui.redo();
+ if (ret && ret instanceof HTMLElement) ret.focus();
+ },
};
}
const browserUI = BrowserUI();
VS,
}
var inputState = InputState.Root;
+var inputCount: number | null = null;
function handleKey(event: any) {
+ if (["Alt", "Control", "Meta", "Shift"].includes(event.key)) return;
if (event.target.tagName === "INPUT") {
if (event.target.id === "taskName") {
if (event.key == "Enter") return browserUI.addTask(event);
if (event.key == "Escape") return browserUI.completeEdit(event, CommitOrAbort.Abort);
}
} else {
+ if (event.ctrlKey) return; // eg: Don't redo when user refreshes the page with ctrl-R
if (inputState === InputState.Root) {
- if (event.key == "j") return browserUI.moveCursor(1);
- if (event.key == "k") return browserUI.moveCursor(-1);
- if (event.key == "J") return browserUI.moveTask(1);
- if (event.key == "K") return browserUI.moveTask(-1);
- if (event.key == "T") return browserUI.makeTopPriority();
- if (event.key == "n") return browserUI.focusTaskNameInput(event);
- if (event.key == "c") return browserUI.setState("cancelled");
- if (event.key == "d") return browserUI.setState("done");
- if (event.key == "q") return browserUI.setState("todo");
- if (event.key == "s") return (inputState = InputState.S);
- if (event.key == "w") return browserUI.setState("waiting");
- if (event.key == "X") return browserUI.setState("deleted");
- if (event.key == "x") return browserUI.removeTag();
- if (event.key == "u") return browserUI.undo();
- if (event.key == "e") return browserUI.beginEdit(event);
- if (event.key == "t") return browserUI.beginTagEdit(event);
- if (event.key == "v") return (inputState = InputState.V);
+ if ("0" <= event.key && event.key <= "9") {
+ return (inputCount = (inputCount ?? 0) * 10 + parseInt(event.key));
+ }
+ try {
+ if (event.key == "j") return browserUI.moveCursor(inputCount ?? 1);
+ if (event.key == "k") return browserUI.moveCursor(-(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 == "T") return browserUI.makeTopPriority();
+ if (event.key == "n") return browserUI.focusTaskNameInput(event);
+ if (event.key == "c") return browserUI.setState("cancelled");
+ if (event.key == "d") return browserUI.setState("done");
+ if (event.key == "q") return browserUI.setState("todo");
+ if (event.key == "s") return (inputState = InputState.S);
+ if (event.key == "w") return browserUI.setState("waiting");
+ if (event.key == "X") return browserUI.setState("deleted");
+ if (event.key == "x") return browserUI.removeTag();
+ if (event.key == "u") return browserUI.undo();
+ if (event.key == "r") return browserUI.redo();
+ if (event.key == "e") return browserUI.beginEdit(event);
+ if (event.key == "t") return browserUI.beginTagEdit(event);
+ if (event.key == "v") return (inputState = InputState.V);
+ } finally {
+ inputCount = null;
+ }
} else if (inputState === InputState.S) {
inputState = InputState.Root;
if (event.key == "m") return browserUI.setState("someday-maybe");