X-Git-Url: http://git.scottworley.com/vopamoi/blobdiff_plain/cddbdce1bb49d2568668fccc788c8356012b3fe2..bb25aeb64a51985374e378cd10e70e683be004f6:/vopamoi.ts diff --git a/vopamoi.ts b/vopamoi.ts index 24b8774..9137ff5 100644 --- a/vopamoi.ts +++ b/vopamoi.ts @@ -215,36 +215,45 @@ function Log(prefix: string = "vp-") { 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 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]}`); } }, }; @@ -369,6 +378,13 @@ function BrowserUI() { 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; @@ -465,6 +481,10 @@ function BrowserUI() { 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(); @@ -476,8 +496,10 @@ enum InputState { 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); @@ -490,24 +512,34 @@ function handleKey(event: any) { 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");