mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-04 17:07:00 +01:00
special: the board, the agenda and the calendar in a workspace of their own
The board swapped out the tree, and the agenda and the calendar were overlays over it, each with a key to open and a key to close. They are now three tiles of one special workspace, as the desktop keeps its scratchpad: the board on the left, the agenda over the calendar on the right. S puts it up and away from anywhere. b, t and C go straight to their tile, and put the workspace away when that tile already has focus. Alt-h/j/k/l move the focus between the tiles, f gives the focused one the porthole, and the tiles without focus keep their cursors, dimmed. A digit switches space as everywhere, and the board follows; the board's head names every space that has one. Enter on an agenda card goes to it on the board beside. A space without a board says so in the board's tile instead of refusing. The rail offers the three tiles as doors, so a phone, which shows one at a time, can walk between them. The help, the README, the wiki and the screenshots say how it works now.
This commit is contained in:
1 parent
304c67151c
commit
73ac961751
31 files changed
+321
-117
No files matched your search
@@ -25,7 +25,7 @@
|
||||
|
||||
/** An entry from an earlier year, in place in the diary. */
|
||||
function open(entry: DiaryEntry) {
|
||||
workspace.overlay = 'none'
|
||||
workspace.closeSpecial()
|
||||
void workspace.guard(() => workspace.openPath(entry.path))
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="sheet" bind:this={sheet}>
|
||||
<header>
|
||||
<span class="label rule"><span class="marker"></span><span class="title">agenda</span> {total} open</span>
|
||||
<button onclick={() => (workspace.overlay = 'none')}>esc</button>
|
||||
<button onclick={() => workspace.closeSpecial()}>esc</button>
|
||||
</header>
|
||||
|
||||
{#if workspace.agenda.diary_today === false}
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
<span class="divider path"></span>
|
||||
|
||||
<nav class="crumbs" aria-label="path">
|
||||
{#if workspace.view === 'board'}
|
||||
<span class="here">board</span>
|
||||
{#if workspace.view === 'special'}
|
||||
<span class="here">special:{workspace.specialFocus}</span>
|
||||
{:else}
|
||||
<button
|
||||
class="crumb"
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
return index === 0 ? 'inbox' : index === lastIndex ? 'done' : ''
|
||||
}
|
||||
|
||||
/** The spaces that have a board, with the digit that switches to each. */
|
||||
const boards = $derived(
|
||||
workspace.spaces.flatMap((space, index) => (space.has_board && index < 10 ? [{ space, digit: (index + 1) % 10 }] : [])),
|
||||
)
|
||||
|
||||
function pick(list: number, card: number) {
|
||||
workspace.listCursor = list
|
||||
workspace.cardCursor = card
|
||||
@@ -54,7 +59,18 @@
|
||||
<span class="marker"></span>
|
||||
<span class="name">{workspace.space?.slug ?? ''}</span>
|
||||
<span>board</span>
|
||||
<span class="count">{total} {total === 1 ? 'card' : 'cards'}</span>
|
||||
{#if workspace.board}<span class="count">{total} {total === 1 ? 'card' : 'cards'}</span>{/if}
|
||||
<!-- Every board is a digit away: the digit switches space, and the board
|
||||
follows the space. -->
|
||||
{#if boards.length > 1}
|
||||
<span class="boards">
|
||||
{#each boards as { space, digit } (space.id)}
|
||||
<button class="other" class:here={space.id === workspace.spaceId} onclick={() => workspace.guard(() => workspace.switchSpace(digit === 0 ? 9 : digit - 1))}>
|
||||
<span class="n">{digit}</span>{space.slug}
|
||||
</button>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="columns" bind:this={columns}>
|
||||
@@ -116,7 +132,11 @@
|
||||
</section>
|
||||
{:else}
|
||||
<div class="empty muted">
|
||||
no lists yet — :list <name> makes a column. then o adds a card to it.
|
||||
{#if !workspace.space?.has_board}
|
||||
{workspace.space?.slug ?? 'this space'} has no board — :set board turns one on{#if boards.length}, or a digit goes to one that has{/if}.
|
||||
{:else}
|
||||
no lists yet — :list <name> makes a column. then o adds a card to it.
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -131,7 +151,7 @@
|
||||
<span><span class="kbd">D</span> due</span>
|
||||
<span><span class="kbd">R</span> rename</span>
|
||||
<span><span class="kbd">dd</span> delete</span>
|
||||
<span><span class="kbd">q</span> tree</span>
|
||||
<span><span class="kbd">1</span>…<span class="kbd">9</span> board</span>
|
||||
<span class="rule-hint">first list is the inbox · last list is done</span>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -151,6 +171,21 @@
|
||||
}
|
||||
.head .marker { background: var(--light); }
|
||||
.head .name { color: var(--light); }
|
||||
.boards { display: flex; gap: 4px; margin-left: 8px; }
|
||||
.other {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 2px 8px;
|
||||
border: 0;
|
||||
border-radius: var(--radius-s);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: none;
|
||||
color: var(--muted);
|
||||
}
|
||||
.other .n { color: var(--outline); }
|
||||
.other.here { background: var(--surface-alt); color: var(--light); }
|
||||
.other.here .n { color: var(--light-dim); }
|
||||
|
||||
.columns {
|
||||
flex: 1;
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<span class="label rule"><span class="marker"></span><span class="title">{title}</span> {workspace.calendarEntries.length} entries</span>
|
||||
<button onclick={() => workspace.guard(() => workspace.moveCalendar(0, -1))} aria-label="previous month">‹</button>
|
||||
<button onclick={() => workspace.guard(() => workspace.moveCalendar(0, 1))} aria-label="next month">›</button>
|
||||
<button onclick={() => (workspace.overlay = 'none')}>esc</button>
|
||||
<button onclick={() => workspace.closeSpecial()}>esc</button>
|
||||
</header>
|
||||
|
||||
<ol class="grid">
|
||||
|
||||
@@ -72,12 +72,14 @@
|
||||
['m / p', 'pick nodes up, then drop them all into the folder you walk to'],
|
||||
['M', 'pick up the whole folder — again puts it all back'],
|
||||
['Ctrl / Shift-click', 'pick a row up · every row up to it — drag them onto a folder'],
|
||||
['t', 'agenda — late, due this week, in hand, on every board'],
|
||||
['S', 'the special workspace: the board, the agenda and the calendar side by side — again puts it away'],
|
||||
['Alt-h/j/k/l · f', 'in the special workspace: to the board, the agenda or the calendar — f gives one the screen'],
|
||||
['t', 'agenda — late, due this week, in hand, on every board — in the special workspace'],
|
||||
['T', "today's diary entry, started if there is none — from templates/day when the diary has one"],
|
||||
['C', 'the diary by month: h/l a day, j/k a week, H/L a month, . today, Enter opens or starts the day'],
|
||||
['C', 'the diary by month, in the special workspace: h/l a day, j/k a week, H/L a month, . today, Enter opens or starts the day'],
|
||||
['[d / ]d', 'the diary entry before / after the one open, by date'],
|
||||
['Ctrl-P', 'find anything, in every space — Enter goes to the result lit'],
|
||||
['b', "open this space's board"],
|
||||
['b', "this space's board, in the special workspace — 1 … 9 there shows another space's"],
|
||||
['c', "capture a card into this space's inbox, without leaving here"],
|
||||
['Esc · q', 'close the document, and its tile when there are several'],
|
||||
['/', 'full-text search, inside the current space and its text files — Tab or Ctrl-N/P walk the results, Enter opens one'],
|
||||
@@ -112,18 +114,18 @@
|
||||
['F', 'a letter over every card in view — type it to put the cursor there'],
|
||||
['r', 'read the board again, for changes made elsewhere'],
|
||||
['x · dd', 'delete the card (asks y/N)'],
|
||||
['q · Esc', 'back to the tree'],
|
||||
['q · Esc', 'put the special workspace away'],
|
||||
]
|
||||
|
||||
const agendaKeys: [string, string][] = [
|
||||
['j / k', 'down / up the cards, then what was written on this day in earlier years'],
|
||||
['Enter', 'to the card, on its own board — or to the entry'],
|
||||
['Enter', 'to the card, on its own board beside — or to the entry'],
|
||||
['Space', 'done, where it stands'],
|
||||
['D', 'due date: fri, tm, +3d … — :due on the line'],
|
||||
[':', 'the line, with the agenda kept up — :repeat weekly acts on the card'],
|
||||
['H / L', 'to the list left / right on its board'],
|
||||
['T', "today's diary entry"],
|
||||
['t · q · Esc', 'close the agenda'],
|
||||
['t · q · Esc', 'put the special workspace away'],
|
||||
]
|
||||
|
||||
const titles: Record<Group, string> = {
|
||||
|
||||
+12
-2
@@ -30,15 +30,24 @@
|
||||
const cmd: Door = { icon: 'prompt', label: 'Command line', keys: [':'], run: () => hooks.openCommandLine(':') }
|
||||
const help: Door = { icon: 'help', label: 'Help', keys: ['?'], run: () => runCommand('help') }
|
||||
|
||||
if (workspace.view === 'board') {
|
||||
if (workspace.view === 'special') {
|
||||
// The tiles of the special workspace are doors, so a phone — which
|
||||
// shows one at a time — can walk between them.
|
||||
const tiles: Door[] = [
|
||||
{ icon: 'board', label: 'Board', keys: ['b'], run: () => (workspace.specialFocus = 'board'), on: workspace.specialFocus === 'board' },
|
||||
{ icon: 'agenda', label: 'Agenda — late, due, in hand', keys: ['t'], run: () => (workspace.specialFocus = 'agenda'), on: workspace.specialFocus === 'agenda' },
|
||||
{ icon: 'calendar', label: 'Calendar', keys: ['C'], run: () => (workspace.specialFocus = 'calendar'), on: workspace.specialFocus === 'calendar' },
|
||||
{ icon: 'list', label: 'Back to the tree', keys: ['S', 'q'], run: () => workspace.closeSpecial() },
|
||||
]
|
||||
if (workspace.specialFocus !== 'board') return [tiles, [cmd, help]]
|
||||
// The card verbs are doors too, so a phone — which has no H, L or
|
||||
// Space — can still move work along.
|
||||
const card = !!workspace.card
|
||||
return [
|
||||
tiles,
|
||||
[
|
||||
{ icon: 'doc-plus', label: 'New card on this list', keys: ['o'], run: () => hooks.openCommandLine(':card ') },
|
||||
{ icon: 'column-plus', label: 'New list', keys: [':list'], run: () => hooks.openCommandLine(':list ') },
|
||||
{ icon: 'list', label: 'Back to the tree', keys: ['b', 'q'], run: () => runCommand('board'), on: true },
|
||||
],
|
||||
[
|
||||
{ icon: 'left', label: 'Send the card left', keys: ['H'], run: () => workspace.guard(() => workspace.sendCard(-1)), dim: !card },
|
||||
@@ -79,6 +88,7 @@
|
||||
],
|
||||
[
|
||||
{ icon: 'agenda', label: 'Agenda — late, due, in hand', keys: ['t'], run: () => runCommand('agenda') },
|
||||
{ icon: 'calendar', label: 'Calendar — the diary by month', keys: ['C'], run: () => runCommand('cal') },
|
||||
{ icon: 'today', label: "Today's diary entry", keys: ['T'], run: () => runCommand('today') },
|
||||
{ icon: 'capture', label: 'Capture a card to the inbox', keys: ['c'], run: () => hooks.openCommandLine(':card ') },
|
||||
],
|
||||
|
||||
+22
-17
@@ -429,8 +429,14 @@ export const commands: CommandSpec[] = [
|
||||
{
|
||||
name: 'board',
|
||||
group: 'board',
|
||||
help: "show this space's board, or go back to the tree",
|
||||
run: () => workspace.guard(() => workspace.toggleBoardView()),
|
||||
help: "this space's board, in the special workspace — again puts it away",
|
||||
run: () => workspace.guard(() => workspace.toggleSpecial('board')),
|
||||
},
|
||||
{
|
||||
name: 'special',
|
||||
group: 'board',
|
||||
help: 'the special workspace: the board, the agenda and the calendar side by side — S toggles it, Alt-h/j/k/l moves between them, 1 … 9 picks the board',
|
||||
run: () => workspace.guard(() => workspace.toggleSpecial()),
|
||||
},
|
||||
{
|
||||
name: 'card',
|
||||
@@ -447,7 +453,7 @@ export const commands: CommandSpec[] = [
|
||||
run: async (arg) => {
|
||||
// On the board the tree is not on screen, so only the open document
|
||||
// can be meant; elsewhere it is whatever has focus.
|
||||
const node = workspace.view === 'board' ? workspace.open : workspace.target
|
||||
const node = workspace.view === 'special' ? workspace.open : workspace.target
|
||||
if (!node || node.kind !== 'document') {
|
||||
return workspace.say('no document to link — open one first', 'error')
|
||||
}
|
||||
@@ -463,8 +469,8 @@ export const commands: CommandSpec[] = [
|
||||
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'],
|
||||
run: async (arg) => {
|
||||
const agenda = workspace.overlay === 'agenda'
|
||||
if (workspace.view !== 'board' && !agenda) return workspace.say('no board open — :board', 'error')
|
||||
const agenda = workspace.view === 'special' && workspace.specialFocus === 'agenda'
|
||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
||||
const set = (due_at: number | null) =>
|
||||
agenda ? workspace.patchAgendaCard({ due_at }) : workspace.patchCard({ due_at })
|
||||
const target = arg.trim()
|
||||
@@ -482,8 +488,8 @@ export const commands: CommandSpec[] = [
|
||||
help: 'once the card is done, a copy comes back in the inbox: daily, weekly, monthly, yearly, +3d, +2w, +6m — - stops it',
|
||||
complete: () => ['daily', 'weekly', 'monthly', 'yearly', '+2w', '-'],
|
||||
run: async (arg) => {
|
||||
const agenda = workspace.overlay === 'agenda'
|
||||
if (workspace.view !== 'board' && !agenda) return workspace.say('no board open — :board', 'error')
|
||||
const agenda = workspace.view === 'special' && workspace.specialFocus === 'agenda'
|
||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
||||
const rule = arg.trim()
|
||||
const repeat = rule === '-' || rule === '' ? null : rule
|
||||
await workspace.guard(async () => {
|
||||
@@ -498,7 +504,7 @@ export const commands: CommandSpec[] = [
|
||||
group: 'board',
|
||||
help: 'move the selected card into the done list, or back out to the inbox',
|
||||
run: () => {
|
||||
if (workspace.view !== 'board') return workspace.say('no board open — :board', 'error')
|
||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
||||
return workspace.guard(() => workspace.toggleDone())
|
||||
},
|
||||
},
|
||||
@@ -508,14 +514,14 @@ export const commands: CommandSpec[] = [
|
||||
group: 'board',
|
||||
help: 'add a column to the board',
|
||||
run: async (arg) => {
|
||||
if (workspace.view !== 'board') return workspace.say('no board open — :board', 'error')
|
||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
||||
if (!arg.trim()) return workspace.say('usage: :list <name>', 'error')
|
||||
await workspace.guard(() => workspace.createList(arg.trim()))
|
||||
},
|
||||
bang: {
|
||||
help: 'delete the column under the cursor and every card on it',
|
||||
run: async () => {
|
||||
if (workspace.view !== 'board') return workspace.say('no board open — :board', 'error')
|
||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
||||
const list = workspace.list
|
||||
if (!list) return workspace.say('no list selected', 'error')
|
||||
const cards = list.cards.length
|
||||
@@ -593,7 +599,7 @@ export const commands: CommandSpec[] = [
|
||||
help: 'the diary by month — h/l a day, j/k a week, H/L a month, Enter opens the day or starts it',
|
||||
run: () =>
|
||||
workspace.guard(async () => {
|
||||
if (workspace.overlay === 'calendar') return void (workspace.overlay = 'none')
|
||||
if (workspace.view === 'special') return workspace.toggleSpecial('calendar')
|
||||
await workspace.openCalendar()
|
||||
}),
|
||||
},
|
||||
@@ -604,9 +610,8 @@ export const commands: CommandSpec[] = [
|
||||
help: 'what is late, due this week, or in hand — across every board',
|
||||
run: () =>
|
||||
workspace.guard(async () => {
|
||||
await workspace.loadAgenda()
|
||||
workspace.agendaCursor = 0
|
||||
workspace.overlay = workspace.overlay === 'agenda' ? 'none' : 'agenda'
|
||||
if (workspace.view !== 'special') workspace.agendaCursor = 0
|
||||
await workspace.toggleSpecial('agenda')
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -765,7 +770,7 @@ export const commands: CommandSpec[] = [
|
||||
help: 'delete a node, and everything under it, into the trash (asks first) — u brings it back',
|
||||
run: async (arg) => {
|
||||
// On the board the thing under the cursor is a card, not a node.
|
||||
if (workspace.view === 'board') {
|
||||
if (workspace.view === 'special') {
|
||||
const card = workspace.card
|
||||
if (!card) return workspace.say('no card selected', 'error')
|
||||
if (!(await workspace.ask(`delete the card "${card.title}"?`))) return
|
||||
@@ -793,7 +798,7 @@ export const commands: CommandSpec[] = [
|
||||
help: 'rename what is under the cursor — document, folder, or card on a board; bare, while writing, into the title',
|
||||
run: async (arg) => {
|
||||
if (!arg.trim() && workspace.editing && workspace.pane === 'document') return hooks.focusTitle()
|
||||
if (workspace.view === 'board') {
|
||||
if (workspace.view === 'special') {
|
||||
if (!arg.trim()) return workspace.say('usage: :name <text>', 'error')
|
||||
return workspace.guard(() => workspace.patchCard({ title: arg.trim() }))
|
||||
}
|
||||
@@ -814,7 +819,7 @@ export const commands: CommandSpec[] = [
|
||||
bang: {
|
||||
help: 'rename the space you are in — on the board, the list under the cursor',
|
||||
run: async (arg) => {
|
||||
if (workspace.view === 'board') {
|
||||
if (workspace.view === 'special') {
|
||||
if (!arg.trim()) return workspace.say('usage: :name! <list name>', 'error')
|
||||
return workspace.guard(() => workspace.renameList(arg.trim()))
|
||||
}
|
||||
|
||||
+58
-38
@@ -23,8 +23,14 @@ import {
|
||||
import { formatBytes, formatDay, formatStamp, formatWeekday, isTouchDevice, parseDay, sealedNote, type Heading } from './util'
|
||||
|
||||
export type Pane = 'list' | 'document'
|
||||
/** The tree of documents, or the board of the space being browsed. */
|
||||
export type View = 'tree' | 'board'
|
||||
/**
|
||||
* The tree of documents, or the special workspace: the board of the space
|
||||
* being browsed, the agenda and the calendar, tiled side by side and a key
|
||||
* away from anywhere, like the desktop's scratchpad.
|
||||
*/
|
||||
export type View = 'tree' | 'special'
|
||||
/** The tile of the special workspace that has focus. */
|
||||
export type SpecialTile = 'board' | 'agenda' | 'calendar'
|
||||
export type Flash = { text: string; kind: 'info' | 'error' } | null
|
||||
|
||||
/**
|
||||
@@ -290,7 +296,9 @@ class Workspace {
|
||||
pane = $state<Pane>('list')
|
||||
flash = $state<Flash>(null)
|
||||
|
||||
overlay = $state<'none' | 'help' | 'media' | 'overview' | 'agenda' | 'actions' | 'verify' | 'outline' | 'trash' | 'history' | 'backlinks' | 'calendar'>('none')
|
||||
overlay = $state<'none' | 'help' | 'media' | 'overview' | 'actions' | 'verify' | 'outline' | 'trash' | 'history' | 'backlinks'>('none')
|
||||
/** Which of the special workspace's tiles the keys go to. */
|
||||
specialFocus = $state<SpecialTile>('board')
|
||||
/** `gO`: the headings of what is on screen, to jump between from the keyboard. */
|
||||
outline = $state.raw<Heading[]>([])
|
||||
outlineCursor = $state(0)
|
||||
@@ -813,14 +821,14 @@ class Workspace {
|
||||
this.cursor = Math.min(spot.cursor, Math.max(this.nodes.length - 1, 0))
|
||||
}
|
||||
}
|
||||
// The board belongs to the space, so switching space while looking at one
|
||||
// shows the new space's board — or drops back to the tree for a space that
|
||||
// has none, rather than leaving the previous board on screen.
|
||||
if (this.view === 'board') {
|
||||
// The board belongs to the space, so switching space with the special
|
||||
// workspace up shows the new space's board — or says it has none, rather
|
||||
// than leaving the previous board on screen.
|
||||
if (this.view === 'special') {
|
||||
this.listCursor = 0
|
||||
this.cardCursor = 0
|
||||
if (this.space?.has_board) await this.reloadBoard(id, undefined)
|
||||
else this.closeBoard()
|
||||
else this.rawBoard = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,7 +870,7 @@ class Workspace {
|
||||
|
||||
async search(query: string, global = this.globalSearch) {
|
||||
// The results are rows in the list, which the board would hide.
|
||||
if (query && this.view === 'board') this.closeBoard()
|
||||
if (query && this.view === 'special') this.closeSpecial()
|
||||
this.query = query
|
||||
this.globalSearch = global && query !== ''
|
||||
this.cursor = 0
|
||||
@@ -898,8 +906,8 @@ class Workspace {
|
||||
const node = await api.getNode(id)
|
||||
// What is asked for from the board — `:12`, `T`, a search — is shown, not
|
||||
// opened behind it; closing a document goes back to the board.
|
||||
if (this.view === 'board' && !underBoard) {
|
||||
if (isContainer(node)) this.closeBoard()
|
||||
if (this.view === 'special' && !underBoard) {
|
||||
if (isContainer(node)) this.closeSpecial()
|
||||
else {
|
||||
this.view = 'tree'
|
||||
this.returnTo = 'board'
|
||||
@@ -1314,7 +1322,7 @@ class Workspace {
|
||||
if (late + due === 0) return
|
||||
localStorage.setItem('diary:agenda-seen', today)
|
||||
this.agendaCursor = 0
|
||||
this.overlay = 'agenda'
|
||||
await this.openSpecial('agenda')
|
||||
}
|
||||
|
||||
/** `C`: the calendar, on the day of the entry being read or else today. */
|
||||
@@ -1322,8 +1330,7 @@ class Workspace {
|
||||
const open = this.open
|
||||
const reading = open && this.spaces.find((space) => space.id === open.path[0]?.id)?.role === 'diary'
|
||||
this.calendarDay = formatDay(reading ? open.created_at : Math.floor(Date.now() / 1000))
|
||||
await this.loadCalendar()
|
||||
this.overlay = 'calendar'
|
||||
await this.openSpecial('calendar')
|
||||
}
|
||||
|
||||
async loadCalendar() {
|
||||
@@ -1344,7 +1351,7 @@ class Workspace {
|
||||
async openCalendarDay() {
|
||||
const day = this.calendarDay
|
||||
const entry = this.calendarEntries.find((e) => e.day === day)
|
||||
this.overlay = 'none'
|
||||
this.closeSpecial()
|
||||
if (entry) return this.openPath(entry.path)
|
||||
const home = this.spaces.find((space) => space.role === 'diary')
|
||||
if (!home) return this.say('there is no diary to write in', 'error')
|
||||
@@ -1363,12 +1370,12 @@ class Workspace {
|
||||
this.say(entry.day)
|
||||
}
|
||||
|
||||
/** Enter in the agenda: to the card, on its own board. */
|
||||
/** Enter in the agenda: to the card, on its own board, beside the agenda. */
|
||||
async goToCard(card: AgendaCard) {
|
||||
this.overlay = 'none'
|
||||
if (this.spaceId !== card.space_id) await this.enterSpace(card.space_id)
|
||||
await this.reloadBoard(card.space_id, card.id)
|
||||
this.view = 'board'
|
||||
this.view = 'special'
|
||||
this.specialFocus = 'board'
|
||||
}
|
||||
|
||||
/** Space in the agenda: into that board's done list, from wherever it is. */
|
||||
@@ -1377,7 +1384,7 @@ class Workspace {
|
||||
if (!done) return
|
||||
const moved = await api.moveCard(card.id, done.id, done.cards.length)
|
||||
await this.loadAgenda()
|
||||
if (this.view === 'board' && this.spaceId === card.space_id) await this.reloadBoard()
|
||||
if (this.view === 'special' && this.spaceId === card.space_id) await this.reloadBoard()
|
||||
this.say(doneSaid(moved))
|
||||
}
|
||||
|
||||
@@ -1390,7 +1397,7 @@ class Workspace {
|
||||
// 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()
|
||||
if (this.view === 'special' && this.spaceId === card.space_id) await this.reloadBoard()
|
||||
return updated
|
||||
}
|
||||
|
||||
@@ -1407,7 +1414,7 @@ class Workspace {
|
||||
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()
|
||||
if (this.view === 'special' && this.spaceId === card.space_id) await this.reloadBoard()
|
||||
this.say(from + delta === lists.length - 1 ? doneSaid(moved) : `"${card.title}" → ${target.name}`)
|
||||
}
|
||||
|
||||
@@ -1475,24 +1482,40 @@ class Workspace {
|
||||
* where a card is would be worse than a round trip.
|
||||
*/
|
||||
|
||||
async openBoard() {
|
||||
/**
|
||||
* The special workspace, with `focus` the tile the keys go to. Everything
|
||||
* in it is read again on the way in: it is looked at in passing, and what
|
||||
* it says should be what is true now.
|
||||
*/
|
||||
async openSpecial(focus: SpecialTile = this.specialFocus) {
|
||||
const space = this.space
|
||||
if (!space) return
|
||||
if (!space.has_board) {
|
||||
return this.say(`${space.slug} has no board — :set board turns one on`, 'error')
|
||||
}
|
||||
await this.reloadBoard(space.id)
|
||||
this.view = 'board'
|
||||
if (space?.has_board) await this.reloadBoard(space.id)
|
||||
else this.rawBoard = null
|
||||
if (!this.calendarDay) this.calendarDay = formatDay(Math.floor(Date.now() / 1000))
|
||||
await Promise.all([this.loadAgenda(), this.loadCalendar()])
|
||||
this.view = 'special'
|
||||
this.specialFocus = focus
|
||||
if (focus === 'board' && space && !space.has_board) this.say(`${space.slug} has no board — :set board turns one on`, 'error')
|
||||
}
|
||||
|
||||
closeBoard() {
|
||||
closeSpecial() {
|
||||
this.view = 'tree'
|
||||
this.returnTo = null
|
||||
}
|
||||
|
||||
async toggleBoardView() {
|
||||
if (this.view === 'board') this.closeBoard()
|
||||
else await this.openBoard()
|
||||
/**
|
||||
* `b`, `t`, `C` and `S`: the special workspace on that tile. A key whose
|
||||
* tile already has focus puts the workspace away again, as the desktop
|
||||
* toggles its scratchpad; any other moves the focus there.
|
||||
*/
|
||||
async toggleSpecial(focus?: SpecialTile) {
|
||||
if (this.view !== 'special') return this.openSpecial(focus)
|
||||
if (focus === undefined || focus === this.specialFocus) return this.closeSpecial()
|
||||
this.specialFocus = focus
|
||||
}
|
||||
|
||||
async openBoard() {
|
||||
await this.openSpecial('board')
|
||||
}
|
||||
|
||||
async reloadBoard(spaceId = this.spaceId, keep = this.card?.id) {
|
||||
@@ -1582,7 +1605,7 @@ class Workspace {
|
||||
* it goes on the list under the cursor instead.
|
||||
*/
|
||||
async createCard(title: string, nodeId: number | null = null) {
|
||||
if (this.view === 'board') {
|
||||
if (this.view === 'special') {
|
||||
const list = this.list
|
||||
if (!list) return this.say('no list to put it on', 'error')
|
||||
const card = await api.createCard(list.id, { title, node_id: nodeId })
|
||||
@@ -1704,7 +1727,7 @@ class Workspace {
|
||||
this.closeTile()
|
||||
this.pane = 'list'
|
||||
await this.reloadBoard()
|
||||
this.view = 'board'
|
||||
this.view = 'special'
|
||||
}
|
||||
|
||||
/** Turn a space's board on or off. The diary ships with it off. */
|
||||
@@ -1714,10 +1737,7 @@ class Workspace {
|
||||
await api.updateNode(space.id, { has_board: on })
|
||||
this.spaces = await api.listSpaces()
|
||||
if (on) await this.openBoard()
|
||||
else {
|
||||
this.closeBoard()
|
||||
this.rawBoard = null
|
||||
}
|
||||
else this.rawBoard = null
|
||||
this.say(on ? `${space.slug} has a board` : `${space.slug} has no board any more`)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user