mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-03 16:41:27 +01:00
agenda: a card's date and list without going to its board
In the agenda, `D` asks for the highlighted card's due date on the command line (`:due` now works there as well as on the board), and `H` / `L` send the card to the list left or right of the one it is in, on its own board. `L` into the last list is done, as on the board. The command line now rises above an open overlay, and while it is up the overlay's own keys stay out of its way, so typing a date does not walk the agenda.
This commit is contained in:
1 parent
cb76aaf43a
commit
811e09416f
7 files changed
+64
-13
No files matched your search
@@ -230,7 +230,8 @@ file is refused.
|
|||||||
due within the week, or sitting in a column between the inbox and done — the
|
due within the week, or sitting in a column between the inbox and done — the
|
||||||
work already in hand — and says whether the diary has an entry for today. `j`/`k`
|
work already in hand — and says whether the diary has an entry for today. `j`/`k`
|
||||||
walk it, `Enter` goes to the card on its own board, `Space` finishes it where
|
walk it, `Enter` goes to the card on its own board, `Space` finishes it where
|
||||||
it stands, and `T` writes today's entry. The bar shows `late` and `today` counts
|
it stands, `D` sets its due date (`:due` on the line), `H` / `L` send it to
|
||||||
|
the list left / right on its board, and `T` writes today's entry. The bar shows `late` and `today` counts
|
||||||
whenever either is above zero, and the agenda opens by itself once a day when
|
whenever either is above zero, and the agenda opens by itself once a day when
|
||||||
something is late or due, so nothing has to be remembered to be looked at.
|
something is late or due, so nothing has to be remembered to be looked at.
|
||||||
|
|
||||||
|
|||||||
+18
-5
@@ -307,6 +307,17 @@
|
|||||||
case ' ':
|
case ' ':
|
||||||
if (card) void workspace.guard(() => workspace.finishCard(card))
|
if (card) void workspace.guard(() => workspace.finishCard(card))
|
||||||
return true
|
return true
|
||||||
|
case 'D':
|
||||||
|
// The agenda stays up behind the line, so `:due` knows which card.
|
||||||
|
if (card) {
|
||||||
|
cmdGlobal = false
|
||||||
|
cmdline = ':due '
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case 'H':
|
||||||
|
case 'L':
|
||||||
|
if (card) void workspace.guard(() => workspace.sendAgendaCard(card, event.key === 'H' ? -1 : 1))
|
||||||
|
return true
|
||||||
case 'T':
|
case 'T':
|
||||||
workspace.overlay = 'none'
|
workspace.overlay = 'none'
|
||||||
void runCommand('today')
|
void runCommand('today')
|
||||||
@@ -526,25 +537,27 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workspace.overlay === 'overview' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
// The command line over an overlay — `D` in the agenda — keeps its keys.
|
||||||
|
const listening = cmdline === null && !isTypingTarget(event.target)
|
||||||
|
if (workspace.overlay === 'overview' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
||||||
if (overviewKey(event)) {
|
if (overviewKey(event)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (workspace.overlay === 'agenda' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
if (workspace.overlay === 'agenda' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
||||||
if (agendaKey(event)) {
|
if (agendaKey(event)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (workspace.overlay === 'media' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
if (workspace.overlay === 'media' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
||||||
if (mediaKey(event)) {
|
if (mediaKey(event)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (workspace.overlay === 'outline' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
if (workspace.overlay === 'outline' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
||||||
if (outlineKey(event)) {
|
if (outlineKey(event)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
@@ -567,7 +580,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workspace.overlay !== 'none') {
|
if (workspace.overlay !== 'none' && listening) {
|
||||||
if (event.key === 'q' || event.key === '?') {
|
if (event.key === 'q' || event.key === '?') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
workspace.overlay = 'none'
|
workspace.overlay = 'none'
|
||||||
|
|||||||
@@ -87,6 +87,10 @@
|
|||||||
<span class="gap"></span>
|
<span class="gap"></span>
|
||||||
<span class="kbd">␣</span> done
|
<span class="kbd">␣</span> done
|
||||||
<span class="gap"></span>
|
<span class="gap"></span>
|
||||||
|
<span class="kbd">D</span> due
|
||||||
|
<span class="gap"></span>
|
||||||
|
<span class="kbd">H</span><span class="kbd">L</span> list
|
||||||
|
<span class="gap"></span>
|
||||||
<span class="kbd">T</span> diary
|
<span class="kbd">T</span> diary
|
||||||
<span class="gap"></span>
|
<span class="gap"></span>
|
||||||
<span class="kbd">t</span><span class="kbd">esc</span> close
|
<span class="kbd">t</span><span class="kbd">esc</span> close
|
||||||
|
|||||||
@@ -241,7 +241,8 @@
|
|||||||
left: calc(var(--frame) + 12px);
|
left: calc(var(--frame) + 12px);
|
||||||
right: calc(var(--rail-w) + 12px);
|
right: calc(var(--rail-w) + 12px);
|
||||||
bottom: calc(var(--status-h) - 1px);
|
bottom: calc(var(--status-h) - 1px);
|
||||||
z-index: 20;
|
/* Above the overlays, so `D` in the agenda can ask for a date. */
|
||||||
|
z-index: 35;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: color-mix(in oklab, var(--hull) 97%, transparent);
|
background: color-mix(in oklab, var(--hull) 97%, transparent);
|
||||||
|
|||||||
@@ -70,6 +70,8 @@
|
|||||||
['j / k', 'down / up the cards'],
|
['j / k', 'down / up the cards'],
|
||||||
['Enter', 'to the card, on its own board'],
|
['Enter', 'to the card, on its own board'],
|
||||||
['Space', 'done, where it stands'],
|
['Space', 'done, where it stands'],
|
||||||
|
['D', 'due date: fri, tm, +3d … — :due on the line'],
|
||||||
|
['H / L', 'to the list left / right on its board'],
|
||||||
['T', "today's diary entry"],
|
['T', "today's diary entry"],
|
||||||
['t · q · Esc', 'close the agenda'],
|
['t · q · Esc', 'close the agenda'],
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -425,17 +425,18 @@ export const commands: CommandSpec[] = [
|
|||||||
name: 'due',
|
name: 'due',
|
||||||
args: '<when|->',
|
args: '<when|->',
|
||||||
group: 'board',
|
group: 'board',
|
||||||
help: 'due date of the card: today, tm, fri, +3d, +2w, 10-31, 2026-10-31 — - clears',
|
help: 'due date of the card — on the board, or highlighted in the agenda: today, tm, fri, +3d, +2w, 10-31, 2026-10-31 — - clears',
|
||||||
complete: () => ['today', 'tm', 'fri', '+3d'],
|
complete: () => ['today', 'tm', 'fri', '+3d'],
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
if (workspace.view !== 'board') return workspace.say('no board open — :board', 'error')
|
const agenda = workspace.overlay === 'agenda'
|
||||||
|
if (workspace.view !== 'board' && !agenda) return workspace.say('no board open — :board', 'error')
|
||||||
|
const set = (due_at: number | null) =>
|
||||||
|
agenda ? workspace.dueAgendaCard(due_at) : workspace.patchCard({ due_at })
|
||||||
const target = arg.trim()
|
const target = arg.trim()
|
||||||
if (target === '-' || target === '') {
|
if (target === '-' || target === '') return workspace.guard(() => set(null))
|
||||||
return workspace.guard(() => workspace.patchCard({ due_at: null }))
|
|
||||||
}
|
|
||||||
const at = parseDue(target)
|
const at = parseDue(target)
|
||||||
if (at === null) return workspace.say(`not a date: ${target} — try fri, +3d or 2026-09-30`, 'error')
|
if (at === null) return workspace.say(`not a date: ${target} — try fri, +3d or 2026-09-30`, 'error')
|
||||||
await workspace.guard(() => workspace.patchCard({ due_at: at }))
|
await workspace.guard(() => set(at))
|
||||||
workspace.say(`due ${formatDay(at)}`)
|
workspace.say(`due ${formatDay(at)}`)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1160,6 +1160,35 @@ class Workspace {
|
|||||||
this.say(`done: ${card.title}`)
|
this.say(`done: ${card.title}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** `:due` with the agenda up: the highlighted card's date, on its own board. */
|
||||||
|
async dueAgendaCard(due_at: number | null) {
|
||||||
|
const card = this.agendaCards[this.agendaCursor]
|
||||||
|
if (!card) return this.say('no card highlighted', 'error')
|
||||||
|
await api.updateCard(card.id, { due_at })
|
||||||
|
await this.loadAgenda()
|
||||||
|
// It may have changed group; the cursor goes with it.
|
||||||
|
const at = this.agendaCards.findIndex((c) => c.id === card.id)
|
||||||
|
if (at >= 0) this.agendaCursor = at
|
||||||
|
if (this.view === 'board' && this.spaceId === card.space_id) await this.reloadBoard()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `H` / `L` in the agenda: the card to the list left or right of the one it
|
||||||
|
* is in, without going to the board — `L` on the last list but one is done.
|
||||||
|
*/
|
||||||
|
async sendAgendaCard(card: AgendaCard, delta: number) {
|
||||||
|
const lists = (await api.board(card.space_id)).lists
|
||||||
|
const from = lists.findIndex((list) => list.id === card.list_id)
|
||||||
|
const target = lists[from + delta]
|
||||||
|
if (from < 0 || !target) return this.say(`${card.title} is already in the ${delta < 0 ? 'first' : 'last'} list`, 'error')
|
||||||
|
await api.moveCard(card.id, target.id, target.cards.length)
|
||||||
|
await this.loadAgenda()
|
||||||
|
const at = this.agendaCards.findIndex((c) => c.id === card.id)
|
||||||
|
if (at >= 0) this.agendaCursor = at
|
||||||
|
if (this.view === 'board' && this.spaceId === card.space_id) await this.reloadBoard()
|
||||||
|
this.say(from + delta === lists.length - 1 ? `done: ${card.title}` : `"${card.title}" → ${target.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
async loadOverview() {
|
async loadOverview() {
|
||||||
this.overview = await api.overview()
|
this.overview = await api.overview()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user