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.
No files matched your search
@@ -136,8 +136,9 @@ it; in the document pane it is that document. In the browse pane:
|
|||||||
| `R` | rename what is highlighted — a document, a folder or a file — starting from its name (`:name!` renames the space) |
|
| `R` | rename what is highlighted — a document, a folder or a file — starting from its name (`:name!` renames the space) |
|
||||||
| `m` / `p` | pick nodes up — `m` again puts one back, `M` takes the whole folder — then drop them all into the folder you walk to |
|
| `m` / `p` | pick nodes up — `m` again puts one back, `M` takes the whole folder — then drop them all into the folder you walk to |
|
||||||
| `Ctrl`/`Shift`-click | pick a row up / every row from the cursor to it; drag picked-up rows onto a folder, a crumb or a space |
|
| `Ctrl`/`Shift`-click | pick a row up / every row from the cursor to it; drag picked-up rows onto a folder, a crumb or a space |
|
||||||
| `b` | the board of this space |
|
| `S` | the special workspace: the board, the agenda and the calendar side by side; `Alt-h`/`j`/`k`/`l` move between them, `f` gives one the screen |
|
||||||
| `t` | the agenda: what is late, due this week, or in hand, on every board |
|
| `b` | the board of this space, in the special workspace; a digit there shows another space's |
|
||||||
|
| `t` | the agenda: what is late, due this week, or in hand, on every board, in the special workspace |
|
||||||
| `T` | today's diary entry, started if there is none |
|
| `T` | today's diary entry, started if there is none |
|
||||||
| `C` | the diary by month (`:cal`) — see [The diary](#the-diary) |
|
| `C` | the diary by month (`:cal`) — see [The diary](#the-diary) |
|
||||||
| `[d` / `]d` | the diary entry before / after the one open, by date |
|
| `[d` / `]d` | the diary entry before / after the one open, by date |
|
||||||
@@ -240,8 +241,9 @@ start as `sftp_host_key` in the data directory.
|
|||||||
|
|
||||||
## Boards
|
## Boards
|
||||||
|
|
||||||
`b`, the board door on the rail, or `:board` swaps the tree for the board
|
`b`, the board door on the rail, or `:board` swaps the tree for the special
|
||||||
of the space being browsed, and swaps it back. Every space has one except the
|
workspace — the board of the space being browsed, with the agenda and the
|
||||||
|
calendar beside it — and swaps it back. Every space has one except the
|
||||||
diary — a journal has no backlog — and `:set board` / `:set noboard` changes
|
diary — a journal has no backlog — and `:set board` / `:set noboard` changes
|
||||||
that for the space you are in (the dimmed board door on the rail does the
|
that for the space you are in (the dimmed board door on the rail does the
|
||||||
same). A new board starts with `backlog`, `doing` and `done`, and the columns mean
|
same). A new board starts with `backlog`, `doing` and `done`, and the columns mean
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
@@ -1,11 +1,33 @@
|
|||||||
# Boards and the agenda
|
# Boards and the agenda
|
||||||
|
|
||||||
|
## The special workspace
|
||||||
|
|
||||||
|
The board, the agenda and the [calendar](Diary.md#the-calendar) share one
|
||||||
|
workspace of their own, as the desktop's special workspace holds what is
|
||||||
|
looked at in passing. `S` (or `:special`) puts it up and away again. It is
|
||||||
|
three tiles: the board on the left, the agenda over the calendar on the right.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
| key | what it does |
|
||||||
|
| --- | --- |
|
||||||
|
| `S` | put the special workspace up, or away |
|
||||||
|
| `b`, `t`, `C` | straight to the board, the agenda or the calendar; again on the one with focus puts the workspace away |
|
||||||
|
| `Alt-h` / `Alt-j` / `Alt-k` / `Alt-l` | focus the tile that way |
|
||||||
|
| `f` | the focused tile fills the screen; again or `Esc` to leave |
|
||||||
|
| `1` … `9` | switch space, and the board with it |
|
||||||
|
| `q`, `Esc` | back to the tree |
|
||||||
|
|
||||||
|
The keys go to the tile with focus, which wears the light border; the other
|
||||||
|
tiles keep their cursors, dimmed. Everything in it is read again each time it
|
||||||
|
comes up.
|
||||||
|
|
||||||
## The board
|
## The board
|
||||||
|
|
||||||
Every space except the diary has a board. `b`, the board button on the rail, or
|
Every space except the diary has a board. `b`, the board button on the rail, or
|
||||||
`:board` swaps the tree for it, and `q` swaps back.
|
`:board` shows it in the special workspace, and `q` goes back to the tree. The
|
||||||
|
head of the board names every space that has one, with the digit that goes
|
||||||

|
there.
|
||||||
|
|
||||||
A new board has three lists: `backlog`, `doing` and `done`. The lists mean
|
A new board has three lists: `backlog`, `doing` and `done`. The lists mean
|
||||||
something by where they stand, not by their names:
|
something by where they stand, not by their names:
|
||||||
@@ -33,11 +55,11 @@ a week fold away until `z` shows them.
|
|||||||
| `z` | show done cards older than a week |
|
| `z` | show done cards older than a week |
|
||||||
| `F` | a letter over every card; type it to jump there |
|
| `F` | a letter over every card; type it to jump there |
|
||||||
| `x`, `dd` | delete the card |
|
| `x`, `dd` | delete the card |
|
||||||
| `q`, `Esc` | back to the tree |
|
| `q`, `Esc` | put the special workspace away |
|
||||||
|
|
||||||
### Capturing a card
|
### Capturing a card
|
||||||
|
|
||||||
`c` anywhere outside the board opens `:card ` on the command line. The card goes
|
`c` anywhere outside the special workspace opens `:card ` on the command line. The card goes
|
||||||
into the inbox of the space you are in, or of the first space with a board, and
|
into the inbox of the space you are in, or of the first space with a board, and
|
||||||
you stay where you were. From outside the browser, see [Capture](Capture.md).
|
you stay where you were. From outside the browser, see [Capture](Capture.md).
|
||||||
|
|
||||||
@@ -86,7 +108,8 @@ does not make a second copy. The board and the agenda show the rule as
|
|||||||
|
|
||||||
## The agenda
|
## The agenda
|
||||||
|
|
||||||
`t` (or `:agenda`) gathers what needs you from every board at once.
|
`t` (or `:agenda`) gathers what needs you from every board at once, in the
|
||||||
|
special workspace beside the board.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -99,13 +122,13 @@ does not make a second copy. The board and the agenda show the rule as
|
|||||||
| key | what it does |
|
| key | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `j` / `k` | down / up, on into the diary entries |
|
| `j` / `k` | down / up, on into the diary entries |
|
||||||
| `Enter` | to the card on its own board, or open the entry |
|
| `Enter` | to the card on its own board, in the tile beside, or open the entry |
|
||||||
| `Space` | finish the card where it stands |
|
| `Space` | finish the card where it stands |
|
||||||
| `D` | due date |
|
| `D` | due date |
|
||||||
| `H` / `L` | to the list left / right on its board |
|
| `H` / `L` | to the list left / right on its board |
|
||||||
| `:` | the command line with the agenda kept up, so `:repeat` acts on the card |
|
| `:` | the command line with the agenda kept up, so `:repeat` acts on the card |
|
||||||
| `T` | today's diary entry |
|
| `T` | today's diary entry |
|
||||||
| `t`, `q`, `Esc` | close |
|
| `t`, `q`, `Esc` | put the special workspace away |
|
||||||
|
|
||||||
The agenda opens by itself once a day when something is late or due. "Today"
|
The agenda opens by itself once a day when something is late or due. "Today"
|
||||||
is the day in `WORKSPACE_TIMEZONE`, the same as for the mails, so a browser in
|
is the day in `WORKSPACE_TIMEZONE`, the same as for the mails, so a browser in
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ arguments and `↑` / `↓` recall earlier lines.
|
|||||||
| `m / p` | pick nodes up, then drop them all into the folder you walk to |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `[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 |
|
| `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 |
|
| `c` | capture a card into this space's inbox, without leaving here |
|
||||||
| `Esc · q` | close the document, and its tile when there are several |
|
| `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 |
|
| `/` | full-text search, inside the current space and its text files — Tab or Ctrl-N/P walk the results, Enter opens one |
|
||||||
@@ -82,20 +84,20 @@ The first list is the inbox, the last list is done.
|
|||||||
| `F` | a letter over every card in view — type it to put the cursor there |
|
| `F` | a letter over every card in view — type it to put the cursor there |
|
||||||
| `r` | read the board again, for changes made elsewhere |
|
| `r` | read the board again, for changes made elsewhere |
|
||||||
| `x · dd` | delete the card (asks y/N) |
|
| `x · dd` | delete the card (asks y/N) |
|
||||||
| `q · Esc` | back to the tree |
|
| `q · Esc` | put the special workspace away |
|
||||||
|
|
||||||
### In the agenda
|
### In the agenda
|
||||||
|
|
||||||
| key | what it does |
|
| key | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `j / k` | down / up the cards, then what was written on this day in earlier years |
|
| `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 |
|
| `Space` | done, where it stands |
|
||||||
| `D` | due date: fri, tm, +3d … — :due on the line |
|
| `D` | due date: fri, tm, +3d … — :due on the line |
|
||||||
| `:` | the line, with the agenda kept up — :repeat weekly acts on the card |
|
| `:` | the line, with the agenda kept up — :repeat weekly acts on the card |
|
||||||
| `H / L` | to the list left / right on its board |
|
| `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` | put the special workspace away |
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
@@ -121,7 +123,8 @@ do: `:w!`, `:space! <name>`, `:import!`.
|
|||||||
|
|
||||||
| command | what it does |
|
| command | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `:board` | show this space's board, or go back to the tree |
|
| `:board` | this space's board, in the special workspace — again puts it away |
|
||||||
|
| `:special` | 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 |
|
||||||
| `:card, :c <title>` | add a card — on the board to the list under the cursor, elsewhere to this space's inbox |
|
| `:card, :c <title>` | add a card — on the board to the list under the cursor, elsewhere to this space's inbox |
|
||||||
| `:card!` | add a card linked to the open document — Enter on it opens the document |
|
| `:card!` | add a card linked to the open document — Enter on it opens the document |
|
||||||
| `:due <when\|->` | due date of the card — on the board, or highlighted in the agenda: today, tm, fri, +3d, +2w, 10-31, 2026-10-31 — - clears |
|
| `:due <when\|->` | due date of the card — on the board, or highlighted in the agenda: today, tm, fri, +3d, +2w, 10-31, 2026-10-31 — - clears |
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ an entry written after midnight on a trip still lands on the right day.
|
|||||||
|
|
||||||
## The calendar
|
## The calendar
|
||||||
|
|
||||||
`C` (or `:cal`) shows the diary a month at a time.
|
`C` (or `:cal`) shows the diary a month at a time, in the
|
||||||
|
[special workspace](Boards-and-Agenda.md#the-special-workspace) under the
|
||||||
|
agenda.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -24,7 +26,7 @@ an entry written after midnight on a trip still lands on the right day.
|
|||||||
| `H` / `L` | a month back / forward |
|
| `H` / `L` | a month back / forward |
|
||||||
| `.` | today |
|
| `.` | today |
|
||||||
| `Enter` | open the day's entry, or start one dated that day |
|
| `Enter` | open the day's entry, or start one dated that day |
|
||||||
| `C`, `Esc` | close |
|
| `C`, `Esc` | put the special workspace away |
|
||||||
|
|
||||||
A dot marks a day with an entry. A number says how many when there are more
|
A dot marks a day with an entry. A number says how many when there are more
|
||||||
than one.
|
than one.
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ The rail of buttons on the right does the same as the keys, for a mouse.
|
|||||||
`h` comes back up.
|
`h` comes back up.
|
||||||
6. Press `c`, type `try the board`, and press `Enter`. That is a card in the
|
6. Press `c`, type `try the board`, and press `Enter`. That is a card in the
|
||||||
inbox of this space's board. Press `b` to see the board, and `b` again to go
|
inbox of this space's board. Press `b` to see the board, and `b` again to go
|
||||||
back.
|
back. It shares a special workspace with the agenda and the calendar: `S`
|
||||||
|
puts it up, and `Alt-h`/`Alt-l` move between them.
|
||||||
7. Press `T`. That is today's diary entry, started for you.
|
7. Press `T`. That is today's diary entry, started for you.
|
||||||
8. Press `?` for the list of every key and command. `h` / `l` walk its columns, `/` narrows it down, and `Esc` closes it.
|
8. Press `?` for the list of every key and command. `h` / `l` walk its columns, `/` narrows it down, and `Esc` closes it.
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ The keys are the desktop's. On a phone, every one of these has a button; see
|
|||||||
## Morning: what needs you
|
## Morning: what needs you
|
||||||
|
|
||||||
The first time you open the workspace each day, the **agenda** opens by itself
|
The first time you open the workspace each day, the **agenda** opens by itself
|
||||||
when anything is late or due. Any other time, press `t`.
|
when anything is late or due. Any other time, press `t`. It opens in the
|
||||||
|
**special workspace**, beside the board and the calendar; `S` puts that up and
|
||||||
|
away from anywhere, and `Alt-h`/`Alt-l` move between its tiles.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -31,9 +33,9 @@ Walk it with `j` and `k`. On a card:
|
|||||||
- `Space` finishes it, where it stands;
|
- `Space` finishes it, where it stands;
|
||||||
- `D` gives it a due date: `fri`, `tm`, `+3d`, `10-31`;
|
- `D` gives it a due date: `fri`, `tm`, `+3d`, `10-31`;
|
||||||
- `H` and `L` move it to the list left or right on its board;
|
- `H` and `L` move it to the list left or right on its board;
|
||||||
- `Enter` goes to the card on its own board.
|
- `Enter` goes to the card on its own board, in the tile beside.
|
||||||
|
|
||||||
`Esc` or `t` closes the agenda. The bar keeps showing the `late` and `today`
|
`Esc` or `t` puts the special workspace away. The bar keeps showing the `late` and `today`
|
||||||
counts while either is above zero. More in [Boards and the agenda](Boards-and-Agenda.md).
|
counts while either is above zero. More in [Boards and the agenda](Boards-and-Agenda.md).
|
||||||
|
|
||||||
## At work: spaces, documents and tiles
|
## At work: spaces, documents and tiles
|
||||||
@@ -84,7 +86,8 @@ In a meeting someone asks you to renew a certificate. Press `c`, type
|
|||||||
`Renew the gateway certificate`, and `Enter`. The card is in this space's
|
`Renew the gateway certificate`, and `Enter`. The card is in this space's
|
||||||
inbox, and you have not left the document you were reading.
|
inbox, and you have not left the document you were reading.
|
||||||
|
|
||||||
Press `b` to see the **board**.
|
Press `b` to see the **board**, in the special workspace. A digit there
|
||||||
|
shows the board of another space.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
import Shared from './lib/Shared.svelte'
|
import Shared from './lib/Shared.svelte'
|
||||||
import Sidebar from './lib/Sidebar.svelte'
|
import Sidebar from './lib/Sidebar.svelte'
|
||||||
import StatusBar from './lib/StatusBar.svelte'
|
import StatusBar from './lib/StatusBar.svelte'
|
||||||
import { workspace } from './lib/store.svelte'
|
import { workspace, type SpecialTile } from './lib/store.svelte'
|
||||||
import { dwindle, formatDay, neighbour, type Direction } from './lib/util'
|
import { dwindle, formatDay, neighbour, type Direction } from './lib/util'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -220,8 +220,8 @@
|
|||||||
[['j', 'k'], 'browse, l opens, L opens beside'],
|
[['j', 'k'], 'browse, l opens, L opens beside'],
|
||||||
[['1…9'], 'switch space, Tab cycles'],
|
[['1…9'], 'switch space, Tab cycles'],
|
||||||
[['w'], 'overview of every space'],
|
[['w'], 'overview of every space'],
|
||||||
[['b'], "this space's board"],
|
[['S'], 'board, agenda and calendar'],
|
||||||
[['t'], 'agenda — late, due, in hand'],
|
[['b', 't', 'C'], 'straight to one of them'],
|
||||||
[['c'], 'capture a card to the inbox'],
|
[['c'], 'capture a card to the inbox'],
|
||||||
[['⌃ P'], 'find in every space'],
|
[['⌃ P'], 'find in every space'],
|
||||||
[['/'], 'search this space'],
|
[['/'], 'search this space'],
|
||||||
@@ -313,7 +313,7 @@
|
|||||||
case 'Enter':
|
case 'Enter':
|
||||||
if (card) void workspace.guard(() => workspace.goToCard(card))
|
if (card) void workspace.guard(() => workspace.goToCard(card))
|
||||||
else if (past) {
|
else if (past) {
|
||||||
workspace.overlay = 'none'
|
workspace.closeSpecial()
|
||||||
void workspace.guard(() => workspace.openPath(past.path))
|
void workspace.guard(() => workspace.openPath(past.path))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -337,12 +337,12 @@
|
|||||||
if (card) void workspace.guard(() => workspace.sendAgendaCard(card, event.key === 'H' ? -1 : 1))
|
if (card) void workspace.guard(() => workspace.sendAgendaCard(card, event.key === 'H' ? -1 : 1))
|
||||||
return true
|
return true
|
||||||
case 'T':
|
case 'T':
|
||||||
workspace.overlay = 'none'
|
workspace.closeSpecial()
|
||||||
void runCommand('today')
|
void runCommand('today')
|
||||||
return true
|
return true
|
||||||
case 't':
|
case 't':
|
||||||
case 'q':
|
case 'q':
|
||||||
workspace.overlay = 'none'
|
workspace.closeSpecial()
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -371,7 +371,7 @@
|
|||||||
return true
|
return true
|
||||||
case 'C':
|
case 'C':
|
||||||
case 'q':
|
case 'q':
|
||||||
workspace.overlay = 'none'
|
workspace.closeSpecial()
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -584,7 +584,22 @@
|
|||||||
* too; left of the leftmost tile is the list. With Shift the focused tile
|
* too; left of the leftmost tile is the list. With Shift the focused tile
|
||||||
* goes that way instead, swapping places with the one it lands on.
|
* goes that way instead, swapping places with the one it lands on.
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
* The special workspace is three tiles: the board on the left, the agenda
|
||||||
|
* over the calendar on the right. Alt-h/j/k/l moves between them.
|
||||||
|
*/
|
||||||
|
const specialNeighbours: Record<SpecialTile, Partial<Record<Direction, SpecialTile>>> = {
|
||||||
|
board: { l: 'agenda' },
|
||||||
|
agenda: { h: 'board', j: 'calendar' },
|
||||||
|
calendar: { h: 'board', k: 'agenda' },
|
||||||
|
}
|
||||||
|
|
||||||
function tileKey(direction: Direction, shift: boolean) {
|
function tileKey(direction: Direction, shift: boolean) {
|
||||||
|
if (workspace.view === 'special') {
|
||||||
|
const next = specialNeighbours[workspace.specialFocus][direction]
|
||||||
|
if (next && !shift) workspace.specialFocus = next
|
||||||
|
return
|
||||||
|
}
|
||||||
if (workspace.pane === 'list' || !workspace.open) {
|
if (workspace.pane === 'list' || !workspace.open) {
|
||||||
if (direction === 'l' && !shift) focusPane('document')
|
if (direction === 'l' && !shift) focusPane('document')
|
||||||
return
|
return
|
||||||
@@ -681,7 +696,7 @@
|
|||||||
return true
|
return true
|
||||||
case 'q':
|
case 'q':
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
workspace.closeBoard()
|
workspace.closeSpecial()
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -734,7 +749,10 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (workspace.overlay === 'agenda' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
// The special workspace's agenda and calendar read their own keys while
|
||||||
|
// they have focus; what they leave falls through to the rest below.
|
||||||
|
const special = workspace.overlay === 'none' && workspace.view === 'special' && listening && !event.ctrlKey && !event.metaKey && !event.altKey
|
||||||
|
if (special && workspace.specialFocus === 'agenda') {
|
||||||
if (agendaKey(event)) {
|
if (agendaKey(event)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
@@ -764,7 +782,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (workspace.overlay === 'calendar' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
if (special && workspace.specialFocus === 'calendar') {
|
||||||
if (calendarKey(event)) {
|
if (calendarKey(event)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return
|
return
|
||||||
@@ -842,7 +860,7 @@
|
|||||||
}
|
}
|
||||||
// `gt` / `gT` walk the tiles, as they walk vim's tab pages — before `t`
|
// `gt` / `gT` walk the tiles, as they walk vim's tab pages — before `t`
|
||||||
// and `T` on their own are taken for the agenda and today's entry.
|
// and `T` on their own are taken for the agenda and today's entry.
|
||||||
if (previous === 'g' && (key === 't' || key === 'T') && workspace.view !== 'board') {
|
if (previous === 'g' && (key === 't' || key === 'T') && workspace.view !== 'special') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
void cycleTile(key === 't' ? 1 : -1)
|
void cycleTile(key === 't' ? 1 : -1)
|
||||||
return
|
return
|
||||||
@@ -869,8 +887,31 @@
|
|||||||
void runCommand('cal')
|
void runCommand('cal')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// `S`, as the desktop's special workspace is bound: the board, the
|
||||||
|
// agenda and the calendar, put up and away again.
|
||||||
|
if (key === 'S') {
|
||||||
|
event.preventDefault()
|
||||||
|
void runCommand('special')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (workspace.view === 'board') {
|
if (workspace.view === 'special') {
|
||||||
|
if (workspace.specialFocus !== 'board') {
|
||||||
|
// The agenda and the calendar have had their keys; nothing reaches
|
||||||
|
// the board's from them, since it is not the tile with focus.
|
||||||
|
if (key === 'b') void runCommand('board')
|
||||||
|
else if (key === 'Escape') workspace.closeSpecial()
|
||||||
|
else if (key === ':') cmdline = ':'
|
||||||
|
else if (key === '?') void runCommand('help')
|
||||||
|
else return
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (key === 'b') {
|
||||||
|
event.preventDefault()
|
||||||
|
workspace.closeSpecial()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (previous === 'g' && key === 'g') {
|
if (previous === 'g' && key === 'g') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
workspace.cardCursor = 0
|
workspace.cardCursor = 0
|
||||||
@@ -1142,10 +1183,26 @@
|
|||||||
<!-- Keyed on the space, so a switch slides the new one in, as the desktop
|
<!-- Keyed on the space, so a switch slides the new one in, as the desktop
|
||||||
does between workspaces: from below when moving right, above when left. -->
|
does between workspaces: from below when moving right, above when left. -->
|
||||||
{#key workspace.spaceId}
|
{#key workspace.spaceId}
|
||||||
{#if workspace.view === 'board'}
|
{#if workspace.view === 'special'}
|
||||||
<!-- The board takes the whole width: its columns are the two panes. -->
|
<!-- The special workspace: the board, and the agenda over the
|
||||||
<div class="panes board switch" style="--dir: {workspace.switchDirection}">
|
calendar beside it. `f` gives the focused one the whole porthole. -->
|
||||||
<div class="window focused" bind:this={boardEl}><Board /></div>
|
<div class="panes special switch" class:full={workspace.fullscreen} data-focus={workspace.specialFocus} style="--dir: {workspace.switchDirection}">
|
||||||
|
{#each ['board', 'agenda', 'calendar'] as const as tile (tile)}
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
class="window {tile}"
|
||||||
|
class:focused={workspace.specialFocus === tile}
|
||||||
|
onpointerdown={() => (workspace.specialFocus = tile)}
|
||||||
|
>
|
||||||
|
{#if tile === 'board'}
|
||||||
|
<div class="fill" bind:this={boardEl}><Board /></div>
|
||||||
|
{:else if tile === 'agenda'}
|
||||||
|
<Agenda />
|
||||||
|
{:else}
|
||||||
|
<Calendar />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
@@ -1218,7 +1275,7 @@
|
|||||||
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions, a11y_no_noninteractive_tabindex -->
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions, a11y_no_noninteractive_tabindex -->
|
||||||
<div class="overlay" tabindex="-1" bind:this={overlayEl} onclick={(e) => e.target === e.currentTarget && (workspace.overlay = 'none')}
|
<div class="overlay" tabindex="-1" bind:this={overlayEl} onclick={(e) => e.target === e.currentTarget && (workspace.overlay = 'none')}
|
||||||
>
|
>
|
||||||
{#if workspace.overlay === 'help'}<Help />{:else if workspace.overlay === 'overview'}<Overview />{:else if workspace.overlay === 'agenda'}<Agenda />{:else if workspace.overlay === 'actions'}<Actions />{:else if workspace.overlay === 'verify'}<Verify />{:else if workspace.overlay === 'outline'}<Headings />{:else if workspace.overlay === 'trash'}<Trash />{:else if workspace.overlay === 'history'}<History />{:else if workspace.overlay === 'backlinks'}<Backlinks />{:else if workspace.overlay === 'calendar'}<Calendar />{:else}<MediaBrowser />{/if}
|
{#if workspace.overlay === 'help'}<Help />{:else if workspace.overlay === 'overview'}<Overview />{:else if workspace.overlay === 'actions'}<Actions />{:else if workspace.overlay === 'verify'}<Verify />{:else if workspace.overlay === 'outline'}<Headings />{:else if workspace.overlay === 'trash'}<Trash />{:else if workspace.overlay === 'history'}<History />{:else if workspace.overlay === 'backlinks'}<Backlinks />{:else}<MediaBrowser />{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -1237,12 +1294,49 @@
|
|||||||
<style>
|
<style>
|
||||||
.boot { display: grid; place-items: center; height: 100%; }
|
.boot { display: grid; place-items: center; height: 100%; }
|
||||||
|
|
||||||
.panes.board { grid-template-columns: 1fr; }
|
/*
|
||||||
|
* The special workspace: the board takes what the right column leaves,
|
||||||
|
* the agenda takes what the calendar leaves under it.
|
||||||
|
*/
|
||||||
|
.panes.special {
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(44ch, 30vw);
|
||||||
|
grid-template-rows: minmax(0, 1fr) auto;
|
||||||
|
grid-template-areas: 'board agenda' 'board calendar';
|
||||||
|
}
|
||||||
|
.special > .board { grid-area: board; }
|
||||||
|
.special > .agenda { grid-area: agenda; }
|
||||||
|
.special > .calendar { grid-area: calendar; }
|
||||||
|
.special .fill { height: 100%; }
|
||||||
|
/* One cursor is lit at a time: the other tiles keep theirs, dimmed. */
|
||||||
|
.special > .window:not(.focused) :global(:is(.card.active, .row.at, .day.at)) {
|
||||||
|
background: var(--surface-alt);
|
||||||
|
color: var(--light);
|
||||||
|
}
|
||||||
|
.special > .window:not(.focused) :global(.row.at :is(.where, .due)) { color: var(--muted); }
|
||||||
|
.special > .window:not(.focused) :global(.card.active :is(.meta, .doc)) { color: var(--muted); }
|
||||||
|
.special > .window:not(.focused) :global(:is(.card.active .today, .today .row.at .due)) { color: var(--light); }
|
||||||
|
.special > .window:not(.focused) :global(:is(.card.active .overdue, .overdue .row.at .due)) { color: var(--alarm); }
|
||||||
|
.special > .window:not(.focused) :global(.card.active .tick) { border-color: var(--outline); }
|
||||||
|
.special > .window:not(.focused) :global(.day.at :is(.dot)) { background: var(--light); }
|
||||||
|
.special > .window:not(.focused) :global(.day.at .many) { color: var(--light); }
|
||||||
|
.panes.special.full { grid-template-columns: 1fr; grid-template-rows: 1fr; grid-template-areas: none; }
|
||||||
|
.panes.special.full > .window:not(.focused) { display: none; }
|
||||||
|
.panes.special.full > .window { grid-area: auto; }
|
||||||
|
|
||||||
|
/* The agenda and the calendar are sheets of their own as overlays; as tiles
|
||||||
|
the window is their sheet. */
|
||||||
|
.special > .window :global(.sheet) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: none;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
.window.list { --pane-bg: color-mix(in oklab, var(--surface) 60%, var(--base)); }
|
.window.list { --pane-bg: color-mix(in oklab, var(--surface) 60%, var(--base)); }
|
||||||
|
|
||||||
/* Only one pane on screen: nothing to tell apart, so no active border. */
|
/* Only one pane on screen: nothing to tell apart, so no active border. */
|
||||||
.panes:not(.reading) .window.list,
|
.panes:not(.reading) .window.list {
|
||||||
.panes.board .window {
|
|
||||||
background:
|
background:
|
||||||
linear-gradient(var(--pane-bg), var(--pane-bg)) padding-box,
|
linear-gradient(var(--pane-bg), var(--pane-bg)) padding-box,
|
||||||
linear-gradient(var(--overlay), var(--overlay)) border-box;
|
linear-gradient(var(--overlay), var(--overlay)) border-box;
|
||||||
@@ -1311,5 +1405,9 @@
|
|||||||
.tiles > .window:not(.current) { display: none; }
|
.tiles > .window:not(.current) { display: none; }
|
||||||
.tiles > .window.current { inset: calc(var(--gap) / 2); width: auto; height: auto; }
|
.tiles > .window.current { inset: calc(var(--gap) / 2); width: auto; height: auto; }
|
||||||
.overlay { padding: 12px; }
|
.overlay { padding: 12px; }
|
||||||
|
/* A phone shows the special workspace's focused tile alone. */
|
||||||
|
.panes.special { grid-template-columns: 1fr; grid-template-rows: 1fr; grid-template-areas: none; }
|
||||||
|
.panes.special > .window { grid-area: auto; }
|
||||||
|
.panes.special > .window:not(.focused) { display: none; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
/** An entry from an earlier year, in place in the diary. */
|
/** An entry from an earlier year, in place in the diary. */
|
||||||
function open(entry: DiaryEntry) {
|
function open(entry: DiaryEntry) {
|
||||||
workspace.overlay = 'none'
|
workspace.closeSpecial()
|
||||||
void workspace.guard(() => workspace.openPath(entry.path))
|
void workspace.guard(() => workspace.openPath(entry.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<div class="sheet" bind:this={sheet}>
|
<div class="sheet" bind:this={sheet}>
|
||||||
<header>
|
<header>
|
||||||
<span class="label rule"><span class="marker"></span><span class="title">agenda</span> {total} open</span>
|
<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>
|
</header>
|
||||||
|
|
||||||
{#if workspace.agenda.diary_today === false}
|
{#if workspace.agenda.diary_today === false}
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
<span class="divider path"></span>
|
<span class="divider path"></span>
|
||||||
|
|
||||||
<nav class="crumbs" aria-label="path">
|
<nav class="crumbs" aria-label="path">
|
||||||
{#if workspace.view === 'board'}
|
{#if workspace.view === 'special'}
|
||||||
<span class="here">board</span>
|
<span class="here">special:{workspace.specialFocus}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="crumb"
|
class="crumb"
|
||||||
|
|||||||
@@ -43,6 +43,11 @@
|
|||||||
return index === 0 ? 'inbox' : index === lastIndex ? 'done' : ''
|
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) {
|
function pick(list: number, card: number) {
|
||||||
workspace.listCursor = list
|
workspace.listCursor = list
|
||||||
workspace.cardCursor = card
|
workspace.cardCursor = card
|
||||||
@@ -54,7 +59,18 @@
|
|||||||
<span class="marker"></span>
|
<span class="marker"></span>
|
||||||
<span class="name">{workspace.space?.slug ?? ''}</span>
|
<span class="name">{workspace.space?.slug ?? ''}</span>
|
||||||
<span>board</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>
|
||||||
|
|
||||||
<div class="columns" bind:this={columns}>
|
<div class="columns" bind:this={columns}>
|
||||||
@@ -116,7 +132,11 @@
|
|||||||
</section>
|
</section>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="empty muted">
|
<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>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +151,7 @@
|
|||||||
<span><span class="kbd">D</span> due</span>
|
<span><span class="kbd">D</span> due</span>
|
||||||
<span><span class="kbd">R</span> rename</span>
|
<span><span class="kbd">R</span> rename</span>
|
||||||
<span><span class="kbd">dd</span> delete</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>
|
<span class="rule-hint">first list is the inbox · last list is done</span>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,6 +171,21 @@
|
|||||||
}
|
}
|
||||||
.head .marker { background: var(--light); }
|
.head .marker { background: var(--light); }
|
||||||
.head .name { color: 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 {
|
.columns {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<span class="label rule"><span class="marker"></span><span class="title">{title}</span> {workspace.calendarEntries.length} entries</span>
|
<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="previous month">‹</button>
|
||||||
<button onclick={() => workspace.guard(() => workspace.moveCalendar(0, 1))} aria-label="next 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>
|
</header>
|
||||||
|
|
||||||
<ol class="grid">
|
<ol class="grid">
|
||||||
|
|||||||
@@ -72,12 +72,14 @@
|
|||||||
['m / p', 'pick nodes up, then drop them all into the folder you walk to'],
|
['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'],
|
['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'],
|
['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"],
|
['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'],
|
['[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'],
|
['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"],
|
['c', "capture a card into this space's inbox, without leaving here"],
|
||||||
['Esc · q', 'close the document, and its tile when there are several'],
|
['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'],
|
['/', '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'],
|
['F', 'a letter over every card in view — type it to put the cursor there'],
|
||||||
['r', 'read the board again, for changes made elsewhere'],
|
['r', 'read the board again, for changes made elsewhere'],
|
||||||
['x · dd', 'delete the card (asks y/N)'],
|
['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][] = [
|
const agendaKeys: [string, string][] = [
|
||||||
['j / k', 'down / up the cards, then what was written on this day in earlier years'],
|
['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'],
|
['Space', 'done, where it stands'],
|
||||||
['D', 'due date: fri, tm, +3d … — :due on the line'],
|
['D', 'due date: fri, tm, +3d … — :due on the line'],
|
||||||
[':', 'the line, with the agenda kept up — :repeat weekly acts on the card'],
|
[':', 'the line, with the agenda kept up — :repeat weekly acts on the card'],
|
||||||
['H / L', 'to the list left / right on its board'],
|
['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', 'put the special workspace away'],
|
||||||
]
|
]
|
||||||
|
|
||||||
const titles: Record<Group, string> = {
|
const titles: Record<Group, string> = {
|
||||||
|
|||||||
@@ -30,15 +30,24 @@
|
|||||||
const cmd: Door = { icon: 'prompt', label: 'Command line', keys: [':'], run: () => hooks.openCommandLine(':') }
|
const cmd: Door = { icon: 'prompt', label: 'Command line', keys: [':'], run: () => hooks.openCommandLine(':') }
|
||||||
const help: Door = { icon: 'help', label: 'Help', keys: ['?'], run: () => runCommand('help') }
|
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
|
// The card verbs are doors too, so a phone — which has no H, L or
|
||||||
// Space — can still move work along.
|
// Space — can still move work along.
|
||||||
const card = !!workspace.card
|
const card = !!workspace.card
|
||||||
return [
|
return [
|
||||||
|
tiles,
|
||||||
[
|
[
|
||||||
{ icon: 'doc-plus', label: 'New card on this list', keys: ['o'], run: () => hooks.openCommandLine(':card ') },
|
{ 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: '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 },
|
{ 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: '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: '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 ') },
|
{ icon: 'capture', label: 'Capture a card to the inbox', keys: ['c'], run: () => hooks.openCommandLine(':card ') },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -429,8 +429,14 @@ export const commands: CommandSpec[] = [
|
|||||||
{
|
{
|
||||||
name: 'board',
|
name: 'board',
|
||||||
group: 'board',
|
group: 'board',
|
||||||
help: "show this space's board, or go back to the tree",
|
help: "this space's board, in the special workspace — again puts it away",
|
||||||
run: () => workspace.guard(() => workspace.toggleBoardView()),
|
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',
|
name: 'card',
|
||||||
@@ -447,7 +453,7 @@ export const commands: CommandSpec[] = [
|
|||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
// On the board the tree is not on screen, so only the open document
|
// On the board the tree is not on screen, so only the open document
|
||||||
// can be meant; elsewhere it is whatever has focus.
|
// 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') {
|
if (!node || node.kind !== 'document') {
|
||||||
return workspace.say('no document to link — open one first', 'error')
|
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',
|
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) => {
|
||||||
const agenda = workspace.overlay === 'agenda'
|
const agenda = workspace.view === 'special' && workspace.specialFocus === 'agenda'
|
||||||
if (workspace.view !== 'board' && !agenda) return workspace.say('no board open — :board', 'error')
|
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
||||||
const set = (due_at: number | null) =>
|
const set = (due_at: number | null) =>
|
||||||
agenda ? workspace.patchAgendaCard({ due_at }) : workspace.patchCard({ due_at })
|
agenda ? workspace.patchAgendaCard({ due_at }) : workspace.patchCard({ due_at })
|
||||||
const target = arg.trim()
|
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',
|
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', '-'],
|
complete: () => ['daily', 'weekly', 'monthly', 'yearly', '+2w', '-'],
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
const agenda = workspace.overlay === 'agenda'
|
const agenda = workspace.view === 'special' && workspace.specialFocus === 'agenda'
|
||||||
if (workspace.view !== 'board' && !agenda) return workspace.say('no board open — :board', 'error')
|
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
||||||
const rule = arg.trim()
|
const rule = arg.trim()
|
||||||
const repeat = rule === '-' || rule === '' ? null : rule
|
const repeat = rule === '-' || rule === '' ? null : rule
|
||||||
await workspace.guard(async () => {
|
await workspace.guard(async () => {
|
||||||
@@ -498,7 +504,7 @@ export const commands: CommandSpec[] = [
|
|||||||
group: 'board',
|
group: 'board',
|
||||||
help: 'move the selected card into the done list, or back out to the inbox',
|
help: 'move the selected card into the done list, or back out to the inbox',
|
||||||
run: () => {
|
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())
|
return workspace.guard(() => workspace.toggleDone())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -508,14 +514,14 @@ export const commands: CommandSpec[] = [
|
|||||||
group: 'board',
|
group: 'board',
|
||||||
help: 'add a column to the board',
|
help: 'add a column to the board',
|
||||||
run: async (arg) => {
|
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')
|
if (!arg.trim()) return workspace.say('usage: :list <name>', 'error')
|
||||||
await workspace.guard(() => workspace.createList(arg.trim()))
|
await workspace.guard(() => workspace.createList(arg.trim()))
|
||||||
},
|
},
|
||||||
bang: {
|
bang: {
|
||||||
help: 'delete the column under the cursor and every card on it',
|
help: 'delete the column under the cursor and every card on it',
|
||||||
run: async () => {
|
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
|
const list = workspace.list
|
||||||
if (!list) return workspace.say('no list selected', 'error')
|
if (!list) return workspace.say('no list selected', 'error')
|
||||||
const cards = list.cards.length
|
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',
|
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: () =>
|
run: () =>
|
||||||
workspace.guard(async () => {
|
workspace.guard(async () => {
|
||||||
if (workspace.overlay === 'calendar') return void (workspace.overlay = 'none')
|
if (workspace.view === 'special') return workspace.toggleSpecial('calendar')
|
||||||
await workspace.openCalendar()
|
await workspace.openCalendar()
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -604,9 +610,8 @@ export const commands: CommandSpec[] = [
|
|||||||
help: 'what is late, due this week, or in hand — across every board',
|
help: 'what is late, due this week, or in hand — across every board',
|
||||||
run: () =>
|
run: () =>
|
||||||
workspace.guard(async () => {
|
workspace.guard(async () => {
|
||||||
await workspace.loadAgenda()
|
if (workspace.view !== 'special') workspace.agendaCursor = 0
|
||||||
workspace.agendaCursor = 0
|
await workspace.toggleSpecial('agenda')
|
||||||
workspace.overlay = workspace.overlay === 'agenda' ? 'none' : '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',
|
help: 'delete a node, and everything under it, into the trash (asks first) — u brings it back',
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
// On the board the thing under the cursor is a card, not a node.
|
// 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
|
const card = workspace.card
|
||||||
if (!card) return workspace.say('no card selected', 'error')
|
if (!card) return workspace.say('no card selected', 'error')
|
||||||
if (!(await workspace.ask(`delete the card "${card.title}"?`))) return
|
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',
|
help: 'rename what is under the cursor — document, folder, or card on a board; bare, while writing, into the title',
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
if (!arg.trim() && workspace.editing && workspace.pane === 'document') return hooks.focusTitle()
|
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')
|
if (!arg.trim()) return workspace.say('usage: :name <text>', 'error')
|
||||||
return workspace.guard(() => workspace.patchCard({ title: arg.trim() }))
|
return workspace.guard(() => workspace.patchCard({ title: arg.trim() }))
|
||||||
}
|
}
|
||||||
@@ -814,7 +819,7 @@ export const commands: CommandSpec[] = [
|
|||||||
bang: {
|
bang: {
|
||||||
help: 'rename the space you are in — on the board, the list under the cursor',
|
help: 'rename the space you are in — on the board, the list under the cursor',
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
if (workspace.view === 'board') {
|
if (workspace.view === 'special') {
|
||||||
if (!arg.trim()) return workspace.say('usage: :name! <list name>', 'error')
|
if (!arg.trim()) return workspace.say('usage: :name! <list name>', 'error')
|
||||||
return workspace.guard(() => workspace.renameList(arg.trim()))
|
return workspace.guard(() => workspace.renameList(arg.trim()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,14 @@ import {
|
|||||||
import { formatBytes, formatDay, formatStamp, formatWeekday, isTouchDevice, parseDay, sealedNote, type Heading } from './util'
|
import { formatBytes, formatDay, formatStamp, formatWeekday, isTouchDevice, parseDay, sealedNote, type Heading } from './util'
|
||||||
|
|
||||||
export type Pane = 'list' | 'document'
|
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
|
export type Flash = { text: string; kind: 'info' | 'error' } | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -290,7 +296,9 @@ class Workspace {
|
|||||||
pane = $state<Pane>('list')
|
pane = $state<Pane>('list')
|
||||||
flash = $state<Flash>(null)
|
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. */
|
/** `gO`: the headings of what is on screen, to jump between from the keyboard. */
|
||||||
outline = $state.raw<Heading[]>([])
|
outline = $state.raw<Heading[]>([])
|
||||||
outlineCursor = $state(0)
|
outlineCursor = $state(0)
|
||||||
@@ -813,14 +821,14 @@ class Workspace {
|
|||||||
this.cursor = Math.min(spot.cursor, Math.max(this.nodes.length - 1, 0))
|
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
|
// The board belongs to the space, so switching space with the special
|
||||||
// shows the new space's board — or drops back to the tree for a space that
|
// workspace up shows the new space's board — or says it has none, rather
|
||||||
// has none, rather than leaving the previous board on screen.
|
// than leaving the previous board on screen.
|
||||||
if (this.view === 'board') {
|
if (this.view === 'special') {
|
||||||
this.listCursor = 0
|
this.listCursor = 0
|
||||||
this.cardCursor = 0
|
this.cardCursor = 0
|
||||||
if (this.space?.has_board) await this.reloadBoard(id, undefined)
|
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) {
|
async search(query: string, global = this.globalSearch) {
|
||||||
// The results are rows in the list, which the board would hide.
|
// 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.query = query
|
||||||
this.globalSearch = global && query !== ''
|
this.globalSearch = global && query !== ''
|
||||||
this.cursor = 0
|
this.cursor = 0
|
||||||
@@ -898,8 +906,8 @@ class Workspace {
|
|||||||
const node = await api.getNode(id)
|
const node = await api.getNode(id)
|
||||||
// What is asked for from the board — `:12`, `T`, a search — is shown, not
|
// 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.
|
// opened behind it; closing a document goes back to the board.
|
||||||
if (this.view === 'board' && !underBoard) {
|
if (this.view === 'special' && !underBoard) {
|
||||||
if (isContainer(node)) this.closeBoard()
|
if (isContainer(node)) this.closeSpecial()
|
||||||
else {
|
else {
|
||||||
this.view = 'tree'
|
this.view = 'tree'
|
||||||
this.returnTo = 'board'
|
this.returnTo = 'board'
|
||||||
@@ -1314,7 +1322,7 @@ class Workspace {
|
|||||||
if (late + due === 0) return
|
if (late + due === 0) return
|
||||||
localStorage.setItem('diary:agenda-seen', today)
|
localStorage.setItem('diary:agenda-seen', today)
|
||||||
this.agendaCursor = 0
|
this.agendaCursor = 0
|
||||||
this.overlay = 'agenda'
|
await this.openSpecial('agenda')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `C`: the calendar, on the day of the entry being read or else today. */
|
/** `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 open = this.open
|
||||||
const reading = open && this.spaces.find((space) => space.id === open.path[0]?.id)?.role === 'diary'
|
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))
|
this.calendarDay = formatDay(reading ? open.created_at : Math.floor(Date.now() / 1000))
|
||||||
await this.loadCalendar()
|
await this.openSpecial('calendar')
|
||||||
this.overlay = 'calendar'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCalendar() {
|
async loadCalendar() {
|
||||||
@@ -1344,7 +1351,7 @@ class Workspace {
|
|||||||
async openCalendarDay() {
|
async openCalendarDay() {
|
||||||
const day = this.calendarDay
|
const day = this.calendarDay
|
||||||
const entry = this.calendarEntries.find((e) => e.day === day)
|
const entry = this.calendarEntries.find((e) => e.day === day)
|
||||||
this.overlay = 'none'
|
this.closeSpecial()
|
||||||
if (entry) return this.openPath(entry.path)
|
if (entry) return this.openPath(entry.path)
|
||||||
const home = this.spaces.find((space) => space.role === 'diary')
|
const home = this.spaces.find((space) => space.role === 'diary')
|
||||||
if (!home) return this.say('there is no diary to write in', 'error')
|
if (!home) return this.say('there is no diary to write in', 'error')
|
||||||
@@ -1363,12 +1370,12 @@ class Workspace {
|
|||||||
this.say(entry.day)
|
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) {
|
async goToCard(card: AgendaCard) {
|
||||||
this.overlay = 'none'
|
|
||||||
if (this.spaceId !== card.space_id) await this.enterSpace(card.space_id)
|
if (this.spaceId !== card.space_id) await this.enterSpace(card.space_id)
|
||||||
await this.reloadBoard(card.space_id, card.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. */
|
/** Space in the agenda: into that board's done list, from wherever it is. */
|
||||||
@@ -1377,7 +1384,7 @@ class Workspace {
|
|||||||
if (!done) return
|
if (!done) return
|
||||||
const moved = await api.moveCard(card.id, done.id, done.cards.length)
|
const moved = await api.moveCard(card.id, done.id, done.cards.length)
|
||||||
await this.loadAgenda()
|
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))
|
this.say(doneSaid(moved))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1390,7 +1397,7 @@ class Workspace {
|
|||||||
// It may have changed group; the cursor goes with it.
|
// It may have changed group; the cursor goes with it.
|
||||||
const at = this.agendaCards.findIndex((c) => c.id === card.id)
|
const at = this.agendaCards.findIndex((c) => c.id === card.id)
|
||||||
if (at >= 0) this.agendaCursor = at
|
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
|
return updated
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1407,7 +1414,7 @@ class Workspace {
|
|||||||
await this.loadAgenda()
|
await this.loadAgenda()
|
||||||
const at = this.agendaCards.findIndex((c) => c.id === card.id)
|
const at = this.agendaCards.findIndex((c) => c.id === card.id)
|
||||||
if (at >= 0) this.agendaCursor = at
|
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}`)
|
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.
|
* 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
|
const space = this.space
|
||||||
if (!space) return
|
if (space?.has_board) await this.reloadBoard(space.id)
|
||||||
if (!space.has_board) {
|
else this.rawBoard = null
|
||||||
return this.say(`${space.slug} has no board — :set board turns one on`, 'error')
|
if (!this.calendarDay) this.calendarDay = formatDay(Math.floor(Date.now() / 1000))
|
||||||
}
|
await Promise.all([this.loadAgenda(), this.loadCalendar()])
|
||||||
await this.reloadBoard(space.id)
|
this.view = 'special'
|
||||||
this.view = 'board'
|
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.view = 'tree'
|
||||||
this.returnTo = null
|
this.returnTo = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleBoardView() {
|
/**
|
||||||
if (this.view === 'board') this.closeBoard()
|
* `b`, `t`, `C` and `S`: the special workspace on that tile. A key whose
|
||||||
else await this.openBoard()
|
* 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) {
|
async reloadBoard(spaceId = this.spaceId, keep = this.card?.id) {
|
||||||
@@ -1582,7 +1605,7 @@ class Workspace {
|
|||||||
* it goes on the list under the cursor instead.
|
* it goes on the list under the cursor instead.
|
||||||
*/
|
*/
|
||||||
async createCard(title: string, nodeId: number | null = null) {
|
async createCard(title: string, nodeId: number | null = null) {
|
||||||
if (this.view === 'board') {
|
if (this.view === 'special') {
|
||||||
const list = this.list
|
const list = this.list
|
||||||
if (!list) return this.say('no list to put it on', 'error')
|
if (!list) return this.say('no list to put it on', 'error')
|
||||||
const card = await api.createCard(list.id, { title, node_id: nodeId })
|
const card = await api.createCard(list.id, { title, node_id: nodeId })
|
||||||
@@ -1704,7 +1727,7 @@ class Workspace {
|
|||||||
this.closeTile()
|
this.closeTile()
|
||||||
this.pane = 'list'
|
this.pane = 'list'
|
||||||
await this.reloadBoard()
|
await this.reloadBoard()
|
||||||
this.view = 'board'
|
this.view = 'special'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Turn a space's board on or off. The diary ships with it off. */
|
/** 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 })
|
await api.updateNode(space.id, { has_board: on })
|
||||||
this.spaces = await api.listSpaces()
|
this.spaces = await api.listSpaces()
|
||||||
if (on) await this.openBoard()
|
if (on) await this.openBoard()
|
||||||
else {
|
else this.rawBoard = null
|
||||||
this.closeBoard()
|
|
||||||
this.rawBoard = null
|
|
||||||
}
|
|
||||||
this.say(on ? `${space.slug} has a board` : `${space.slug} has no board any more`)
|
this.say(on ? `${space.slug} has a board` : `${space.slug} has no board any more`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||