mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-03 16:41:27 +01:00
tiles: the board, the agenda, the calendar and a card's back are tiles
The special workspace was a second layout beside the tiles, with its own focus, its own Tab, Esc and q, and a returnTo flag to find the way back. The views are now tile kinds instead: opened beside what is on screen, walked with Alt-h/j/k/l and gt/gT, closed with q. - Tile gets a kind: node, board, agenda, calendar or card. view, specialFocus, returnTo and the special context are gone. - A trail of focused tiles: closing one goes back to the one before, so q on a card's notes or a calendar day returns to the view. - A view is never replaced by what is opened from it; that opens beside. - S tiles the board, agenda and calendar ahead of the documents, or puts every view away. :plan, with :special kept as an alias. - The card sheet is a card tile beside the board, following its cursor; leaving it while writing the description saves what is written. - Spaces remember their views; with a view focused, a space switch takes the views along instead of leaving copies behind. - Card commands check what has the focus rather than the special view.
This commit is contained in:
1 parent
26b3c686d2
commit
5684614d0a
18 files changed
+522
-512
No files matched your search
@@ -141,9 +141,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 |
|
||||||
| `S` | the special workspace: the board, the agenda and the calendar side by side; `Tab` or `Alt-h`/`j`/`k`/`l` move between them, `f` gives one the screen, `Esc` leaves |
|
| `S` | the board, the agenda and the calendar tiled ahead of what is open, or every view away again; `Alt-h`/`j`/`k`/`l` move between the tiles, `f` gives one the screen |
|
||||||
| `b` | the board of this space, in the special workspace; a digit there shows another space's |
|
| `b` | the board of this space, in a tile; 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` | the agenda: what is late, due this week, or in hand, on every board, in a tile |
|
||||||
| `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 |
|
||||||
@@ -247,9 +247,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 special
|
`b`, the board door on the rail, or `:board` shows the board of the space
|
||||||
workspace — the board of the space being browsed, with the agenda and the
|
being browsed in a tile beside what is open, and `q` or `Esc` closes it; `S`
|
||||||
calendar beside it — and `Esc` swaps it back. Every space has one except the
|
puts it up with the agenda and the calendar. 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
|
||||||
@@ -271,8 +271,8 @@ you were. On the board, `o` or `c` puts a card on the column under the cursor.
|
|||||||
| `j` / `k` | down / up the cards of that list |
|
| `j` / `k` | down / up the cards of that list |
|
||||||
| `J` / `K` | move the card down / up its list |
|
| `J` / `K` | move the card down / up its list |
|
||||||
| `H` / `L` | move the card to the list on the left / right |
|
| `H` / `L` | move the card to the list on the left / right |
|
||||||
| `Enter` | turn the card over: labels, a markdown description, a checklist |
|
| `Enter` | turn the card over, in a tile beside: labels, a markdown description, a checklist |
|
||||||
| `gf` | the card's notes, a document made on first use; `q` comes back |
|
| `gf` | the card's notes, a document made on first use, beside; `q` comes back |
|
||||||
| `Space` | into the done column, or back out to the inbox |
|
| `Space` | into the done column, or back out to the inbox |
|
||||||
| `o`, `c` | a new card on this list |
|
| `o`, `c` | a new card on this list |
|
||||||
| `D` | due date — `fri`, `tm`, `+3d`, `+2w`, `10-31`, `2026-10-31` |
|
| `D` | due date — `fri`, `tm`, `+3d`, `+2w`, `10-31`, `2026-10-31` |
|
||||||
@@ -280,7 +280,7 @@ you were. On the board, `o` or `c` puts a card on the column under the cursor.
|
|||||||
| `z` | show done cards older than a week |
|
| `z` | show done cards older than a week |
|
||||||
| `F` | a letter over every card in view; typing it puts the cursor on that card |
|
| `F` | a letter over every card in view; typing it puts the cursor on that card |
|
||||||
| `x`, `dd` | delete the card |
|
| `x`, `dd` | delete the card |
|
||||||
| `q`, `Esc` | back to the tree |
|
| `q`, `Esc` | close the board's tile |
|
||||||
|
|
||||||
On the back of a card, `j`/`k` walk the checklist, `Space` ticks a step, `o`
|
On the back of a card, `j`/`k` walk the checklist, `Space` ticks a step, `o`
|
||||||
adds one and `dd` drops it; `e` writes the description in the vim editor, `:w`
|
adds one and `dd` drops it; `e` writes the description in the vim editor, `:w`
|
||||||
|
|||||||
@@ -1,38 +1,44 @@
|
|||||||
# Boards and the agenda
|
# Boards and the agenda
|
||||||
|
|
||||||
## The special workspace
|
## Views
|
||||||
|
|
||||||
The board, the agenda and the [calendar](Diary.md#the-calendar) share one
|
The board, the agenda, the [calendar](Diary.md#the-calendar) and the back of
|
||||||
workspace of their own, as the desktop's special workspace holds what is
|
a card are views: tiles like the documents' tiles, opened beside what is on
|
||||||
looked at in passing. `S` (or `:special`, or the `special` pill in the bar)
|
screen and walked the same way. `S` (or `:plan`) puts the board, the agenda
|
||||||
puts it up and away again. It is three tiles: the board on the left, the
|
and the calendar up together, ahead of the documents with the board focused —
|
||||||
agenda over the calendar on the right.
|
the board on the left, the agenda over the calendar on the right when nothing
|
||||||
|
else is open — and puts every view away again.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
| key | what it does |
|
| key | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `S` | put the special workspace up, or away |
|
| `S` | the board, the agenda and the calendar up together, or every view away |
|
||||||
| `b`, `t`, `C` | to the board, the agenda or the calendar, from anywhere |
|
| `b`, `t`, `C` | to the board, the agenda or the calendar, opening its tile when it is not up |
|
||||||
| `Tab` / `Shift-Tab` | the next / previous tile |
|
| `gt` / `gT` | the next / previous tile |
|
||||||
| `Alt-h` / `Alt-j` / `Alt-k` / `Alt-l` | the tile that way |
|
| `Alt-h` / `Alt-j` / `Alt-k` / `Alt-l` | the tile that way; left of the leftmost is the list |
|
||||||
| `l` past the last list, `h` in the agenda, `j` past its end | on into the tile beside |
|
| `l` past the last list, `h` in the agenda, `j` past its end | on into the tile beside |
|
||||||
| `f` | the focused tile fills the screen; again or `Esc` to leave |
|
| `f` | the focused tile fills the screen; again or `Esc` to leave |
|
||||||
| `1` … `9` | switch space, and the board with it |
|
| `1` … `9` | switch space; with a view focused, the views come along |
|
||||||
| `q`, `Esc` | leave, back to where you were |
|
| `q`, `Esc` | close the view's tile, back to the tile focused before it |
|
||||||
|
|
||||||
The keys go to the tile with focus, which wears the light border and its key
|
The keys go to the tile with focus, which wears the light border and its key
|
||||||
lit in its head; the other tiles keep their cursors, dimmed. `b`, `t` and `C`
|
lit in its head; the other views keep their cursors, dimmed. A view is never
|
||||||
only ever go to their tile, and `Esc` only ever leaves — or, with a tile
|
replaced by what is opened from it — a card's notes, a day of the calendar, an
|
||||||
filling the screen, leaves that first. A document opened from the workspace —
|
entry of the agenda, a row of the list: that opens in a tile beside it, and
|
||||||
a card's notes, a day of the calendar, an entry of the agenda, `T` — comes
|
`q` on it comes back to the view. Each view is read again whenever it is gone
|
||||||
back to the tile it was opened from on `q`. Everything in it is read again
|
to.
|
||||||
each time it comes up.
|
|
||||||
|
Each space keeps its own tiles, views among them. Switching space with a view
|
||||||
|
focused takes the board, the agenda and the calendar along, the way the
|
||||||
|
special workspace used to stay up: a digit on the board shows that space's
|
||||||
|
board, with the agenda still beside it. Switching with a document or the list
|
||||||
|
focused leaves them in the space, and they are there again on coming back.
|
||||||
|
|
||||||
## 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` shows it in the special workspace, and `Esc` goes back to the tree.
|
`:board` shows it in a tile beside what is open, and `q` or `Esc` closes it.
|
||||||
It is the board of the space being browsed, or, in a space without one such as
|
It is the board of the space being browsed, or, in a space without one such as
|
||||||
the diary, the last board shown. The head of the board names every space that
|
the diary, the last board shown. The head of the board names every space that
|
||||||
has one, with the digit that goes there.
|
has one, with the digit that goes there.
|
||||||
@@ -64,13 +70,14 @@ 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` | leave the special workspace |
|
| `q`, `Esc` | close the board's tile |
|
||||||
|
|
||||||
### The back of a card
|
### The back of a card
|
||||||
|
|
||||||
`Enter` on a card turns it over: a sheet floats over the board with its
|
`Enter` on a card turns it over: a tile beside the board with its labels, its
|
||||||
labels, its description and its checklist. The card stays the one under the
|
description and its checklist. It is the card under the board's cursor, so
|
||||||
board's cursor, so `D`, `R`, `H` and `L` act on it as they do on the board.
|
walking the board leafs through the backs, and `D`, `R`, `H` and `L` act on it
|
||||||
|
as they do on the board.
|
||||||
|
|
||||||
| key | what it does |
|
| key | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -83,11 +90,12 @@ board's cursor, so `D`, `R`, `H` and `L` act on it as they do on the board.
|
|||||||
| `e`, `i`, `Enter` | write the description |
|
| `e`, `i`, `Enter` | write the description |
|
||||||
| `a` | put a label on, or take it off (`:label <name>`) |
|
| `a` | put a label on, or take it off (`:label <name>`) |
|
||||||
| `gf` | the card's notes, below |
|
| `gf` | the card's notes, below |
|
||||||
| `q`, `Esc` | back to the board |
|
| `q`, `Esc` | close the back, back to the board |
|
||||||
|
|
||||||
The description is markdown, written in the same editor as a document, vim
|
The description is markdown, written in the same editor as a document, vim
|
||||||
and all: `:w` writes it to the card, `:q` or `Esc` in normal mode leaves, and
|
and all: `:w` writes it to the card, `:q` or `Esc` in normal mode leaves, and
|
||||||
`:q!` throws away what was not written. A fenced block in a language is
|
`:q!` throws away what was not written. Leaving the tile while writing keeps
|
||||||
|
what is written. A fenced block in a language is
|
||||||
coloured the way nvim colours it, written and read.
|
coloured the way nvim colours it, written and read.
|
||||||
|
|
||||||
Labels are lower case, and each name keeps its colour on every card and every
|
Labels are lower case, and each name keeps its colour on every card and every
|
||||||
@@ -101,15 +109,15 @@ step open again.
|
|||||||
|
|
||||||
### Capturing a card
|
### Capturing a card
|
||||||
|
|
||||||
`c` anywhere outside the special workspace opens `:card ` on the command line. The card goes
|
`c` in the tree 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).
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
`gf` on a card, or on its back, opens its notes: a document named after the card, made in the
|
`gf` on a card, or on its back, opens its notes: a document named after the card, made in the
|
||||||
root of the space the first time. Closing it goes back to the board, on the
|
root of the space the first time, in a tile beside the board. Closing it goes
|
||||||
same card. `:card!` while reading a document makes a card that points at it.
|
back to the board, on the same card. `:card!` while reading a document makes a card that points at it.
|
||||||
|
|
||||||
The link is loose on purpose: deleting the document leaves the card, because the
|
The link is loose on purpose: deleting the document leaves the card, because the
|
||||||
work is still open when its notes are gone.
|
work is still open when its notes are gone.
|
||||||
@@ -150,8 +158,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, in the
|
`t` (or `:agenda`) gathers what needs you from every board at once, in a tile
|
||||||
special workspace beside the board.
|
of its own.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -163,15 +171,15 @@ special workspace beside the board.
|
|||||||
|
|
||||||
| key | what it does |
|
| key | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `j` / `k` | down / up, on into the diary entries, then into the calendar |
|
| `j` / `k` | down / up, on into the diary entries, then into the tile below |
|
||||||
| `h` | to the board |
|
| `h` | to the tile on the left |
|
||||||
| `Enter` | to the card on its own board, in the tile beside, 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 |
|
||||||
| `q`, `Esc` | leave the special workspace |
|
| `q`, `Esc` | close the agenda's tile |
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
+13
-16
@@ -22,7 +22,7 @@ arguments and `↑` / `↓` recall earlier lines.
|
|||||||
| `Alt-h/j/k/l` | focus the tile that way, from the editor too — left of the leftmost is the list |
|
| `Alt-h/j/k/l` | focus the tile that way, from the editor too — left of the leftmost is the list |
|
||||||
| `Alt-Shift-h/j/k/l` | move the focused tile that way |
|
| `Alt-Shift-h/j/k/l` | move the focused tile that way |
|
||||||
| `L · Shift-Enter` | open the row in a new tile beside the others |
|
| `L · Shift-Enter` | open the row in a new tile beside the others |
|
||||||
| `gt / gT` | next / previous tile — in the editor too |
|
| `gt / gT` | next / previous tile — a view too |
|
||||||
| `Alt-q` | close the focused tile — :close; :only closes the others |
|
| `Alt-q` | close the focused tile — :close; :only closes the others |
|
||||||
| `j / k · ↓ ↑` | move down / up the list — scroll the document when it has focus |
|
| `j / k · ↓ ↑` | move down / up the list — scroll the document when it has focus |
|
||||||
| `gg / G` | first / last row, or top / bottom of the document — a first g, d, ] or [ waits a second in the status line |
|
| `gg / G` | first / last row, or top / bottom of the document — a first g, d, ] or [ waits a second in the status line |
|
||||||
@@ -43,14 +43,13 @@ 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 |
|
||||||
| `S` | the special workspace: the board, the agenda and the calendar side by side — S again, q or Esc leave it |
|
| `S` | the board, the agenda and the calendar, tiled ahead of what is open with the board focused — S again puts every view away |
|
||||||
| `Tab · Alt-h/j/k/l` | in the special workspace: the next tile, or the one that way — l past the last list and j past the agenda go on too |
|
| `t` | agenda — late, due this week, in hand, on every board — in a tile of its own |
|
||||||
| `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, in the special workspace: 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 a tile: h/l a day, j/k a week, H/L a month, . today, Enter opens or starts the day beside it |
|
||||||
| `[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 opens the result lit beside them, n goes on |
|
| `Ctrl-P` | find anything, in every space — Enter opens the result lit beside them, n goes on |
|
||||||
| `b` | the board, in the special workspace: this space's, or the last one shown — 1 … 9 there shows another's |
|
| `b` | the board, in a tile: this space's, or the last one shown — 1 … 9 there shows another'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 |
|
||||||
@@ -64,6 +63,7 @@ arguments and `↑` / `↓` recall earlier lines.
|
|||||||
| `:` | command line — Tab / Shift-Tab cycle the matches, then the argument; ↑ ↓ recall what ran before |
|
| `:` | command line — Tab / Shift-Tab cycle the matches, then the argument; ↑ ↓ recall what ran before |
|
||||||
| `Ctrl-S` | save, from anywhere |
|
| `Ctrl-S` | save, from anywhere |
|
||||||
| `?` | this screen, from the editor too — h/l a column, / filters it down |
|
| `?` | this screen, from the editor too — h/l a column, / filters it down |
|
||||||
|
| `q · Esc` | in a view — the board, the agenda, the calendar, a card's back — close its tile; the focus goes back to the tile before |
|
||||||
|
|
||||||
### On a board
|
### On a board
|
||||||
|
|
||||||
@@ -71,13 +71,13 @@ The first list is the inbox, the last list is done.
|
|||||||
|
|
||||||
| key | what it does |
|
| key | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `h / l` | to the list left / right — right of the last is the agenda |
|
| `h / l` | to the list left / right — right of the last, to the tile beside |
|
||||||
| `j / k` | down / up the cards |
|
| `j / k` | down / up the cards |
|
||||||
| `gg / G` | first / last card of the list |
|
| `gg / G` | first / last card of the list |
|
||||||
| `J / K` | move the card down / up its list |
|
| `J / K` | move the card down / up its list |
|
||||||
| `H / L` | move the card to the list left / right |
|
| `H / L` | move the card to the list left / right |
|
||||||
| `Space` | into the done list, or back out to the inbox |
|
| `Space` | into the done list, or back out to the inbox |
|
||||||
| `Enter` | turn the card over: its labels, description and checklist |
|
| `Enter` | turn the card over, in a tile beside: its labels, description and checklist |
|
||||||
| `gf` | the card's notes — a document, made on first use; q comes back |
|
| `gf` | the card's notes — a document, made on first use; q comes back |
|
||||||
| `o · c` | new card on this list |
|
| `o · c` | new card on this list |
|
||||||
| `D` | due date: fri, tm, +3d, 10-31 … |
|
| `D` | due date: fri, tm, +3d, 10-31 … |
|
||||||
@@ -86,7 +86,6 @@ 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` | leave the special workspace |
|
|
||||||
|
|
||||||
### On the back of a card
|
### On the back of a card
|
||||||
|
|
||||||
@@ -104,21 +103,19 @@ The first list is the inbox, the last list is done.
|
|||||||
| `R · D` | rename the card, its due date |
|
| `R · D` | rename the card, its due date |
|
||||||
| `H / L` | send the card to the list left / right |
|
| `H / L` | send the card to the list left / right |
|
||||||
| `gf` | the card's notes — a document, made on first use; q comes back |
|
| `gf` | the card's notes — a document, made on first use; q comes back |
|
||||||
| `q · Esc` | back to the board |
|
|
||||||
|
|
||||||
### 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, then into the calendar |
|
| `j / k` | down / up the cards, then what was written on this day in earlier years, then on to the tile below |
|
||||||
| `h` | to the board |
|
| `h` | to the tile on the left |
|
||||||
| `Enter` | to the card, on its own board beside — 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 |
|
||||||
| `q · Esc` | leave the special workspace — a document opened from it comes back here on q |
|
|
||||||
|
|
||||||
### In the scratchpad
|
### In the scratchpad
|
||||||
|
|
||||||
@@ -156,11 +153,11 @@ do: `:w!`, `:space! <name>`, `:import!`.
|
|||||||
|
|
||||||
| command | what it does |
|
| command | what it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `:board` | this space's board, in the special workspace |
|
| `:board` | this space's board, in a tile — the last one shown when this space has none |
|
||||||
| `:special` | the special workspace: the board, the agenda and the calendar side by side — S puts it up and away, Tab walks its tiles, 1 … 9 picks the board |
|
| `:plan, :special` | the board, the agenda and the calendar, tiled ahead of what is open — S puts them up, and every view away again; 1 … 9 on the board 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 — gf on it opens the document |
|
| `:card!` | add a card linked to the open document — gf 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, its back, or highlighted in the agenda: today, tm, fri, +3d, +2w, 10-31, 2026-10-31 — - clears |
|
||||||
| `:repeat <rule\|->` | once the card is done, a copy comes back in the inbox: daily, weekly, monthly, yearly, +3d, +2w, +6m — - stops it |
|
| `:repeat <rule\|->` | once the card is done, a copy comes back in the inbox: daily, weekly, monthly, yearly, +3d, +2w, +6m — - stops it |
|
||||||
| `:done` | move the selected card into the done list, or back out to the inbox |
|
| `:done` | move the selected card into the done list, or back out to the inbox |
|
||||||
| `:task, :step <text>` | add a step to the open card's checklist, below the cursor |
|
| `:task, :step <text>` | add a step to the open card's checklist, below the cursor |
|
||||||
|
|||||||
+5
-5
@@ -13,9 +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, in the
|
`C` (or `:cal`) shows the diary a month at a time, in a
|
||||||
[special workspace](Boards-and-Agenda.md#the-special-workspace) under the
|
[tile of its own](Boards-and-Agenda.md#views) — under the agenda when `S` puts
|
||||||
agenda.
|
them up together.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ agenda.
|
|||||||
| `j` / `k` | a week forward / back |
|
| `j` / `k` | a week forward / back |
|
||||||
| `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 beside the calendar, or start one dated that day |
|
||||||
| `q`, `Esc` | leave the special workspace; an entry opened here comes back on `q` |
|
| `q`, `Esc` | close the calendar's tile; `q` on an entry opened from it comes back here |
|
||||||
|
|
||||||
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.
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ The rail of buttons on the right does the same as the keys, for a mouse.
|
|||||||
add a name and press `Enter`. `l` or `Enter` on the folder goes into it, and
|
add a name and press `Enter`. `l` or `Enter` on the folder goes into it, and
|
||||||
`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 `Esc` to go
|
inbox of this space's board. Press `b` to see the board in a tile, and
|
||||||
back. It shares a special workspace with the agenda and the calendar: `S`
|
`Esc` to close it. `S` puts it up with the agenda and the calendar, and puts
|
||||||
puts it up and away, and `Tab` walks between them.
|
them away again; `Alt-h`/`j`/`k`/`l` walk between the tiles.
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
+10
-9
@@ -11,9 +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`. It opens in the
|
when anything is late or due. Any other time, press `t`. It opens in a tile
|
||||||
**special workspace**, beside the board and the calendar; `S` puts that up and
|
beside what is open; `S` puts it up with the board and the calendar, and puts
|
||||||
away from anywhere, and `Tab` walks its tiles.
|
them away again, and `Alt-h`/`j`/`k`/`l` walk the tiles.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ Walk it with `j` and `k`. On a card:
|
|||||||
- `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, in the tile beside.
|
- `Enter` goes to the card on its own board, in the tile beside.
|
||||||
|
|
||||||
`Esc` leaves the special workspace. The bar keeps showing the `late` and `today`
|
`Esc` closes the agenda's tile. 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
|
||||||
@@ -86,7 +86,7 @@ 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**, in the special workspace. A digit there
|
Press `b` to see the **board**, in a tile beside the document. A digit there
|
||||||
shows the board of another space.
|
shows the board of another space.
|
||||||
|
|
||||||

|

|
||||||
@@ -94,10 +94,11 @@ shows the board of another space.
|
|||||||
The first list is the inbox and the last is done; the ones between are work in
|
The first list is the inbox and the last is done; the ones between are work in
|
||||||
hand. `h`/`l` move between lists and `j`/`k` between cards. `H`/`L` move a
|
hand. `h`/`l` move between lists and `j`/`k` between cards. `H`/`L` move a
|
||||||
card across, `Space` finishes it, and `D` sets its due date. `Enter` turns a
|
card across, `Space` finishes it, and `D` sets its due date. `Enter` turns a
|
||||||
card over to its labels, description and checklist, and `gf` opens its notes:
|
card over to its labels, description and checklist, in a tile beside, and
|
||||||
a document made for the card the first time you ask. `:repeat weekly`
|
`gf` opens its notes: a document made for the card the first time you ask,
|
||||||
makes a card come back in the inbox each time it is finished. `q` goes back to
|
also beside, and `q` on it comes back to the board. `:repeat weekly` makes a
|
||||||
the tree.
|
card come back in the inbox each time it is finished. `q` on the board closes
|
||||||
|
its tile.
|
||||||
|
|
||||||
### Finding things
|
### Finding things
|
||||||
|
|
||||||
|
|||||||
+32
-75
@@ -20,7 +20,7 @@
|
|||||||
import Overview from './lib/Overview.svelte'
|
import Overview from './lib/Overview.svelte'
|
||||||
import MediaBrowser from './lib/MediaBrowser.svelte'
|
import MediaBrowser from './lib/MediaBrowser.svelte'
|
||||||
import Trash from './lib/Trash.svelte'
|
import Trash from './lib/Trash.svelte'
|
||||||
import CardSheet from './lib/CardSheet.svelte'
|
import CardTile from './lib/CardTile.svelte'
|
||||||
import Scratch from './lib/Scratch.svelte'
|
import Scratch from './lib/Scratch.svelte'
|
||||||
import Actions from './lib/Actions.svelte'
|
import Actions from './lib/Actions.svelte'
|
||||||
import Prompt from './lib/Prompt.svelte'
|
import Prompt from './lib/Prompt.svelte'
|
||||||
@@ -62,7 +62,6 @@
|
|||||||
verify: Verify,
|
verify: Verify,
|
||||||
info: Info,
|
info: Info,
|
||||||
trash: Trash,
|
trash: Trash,
|
||||||
card: CardSheet,
|
|
||||||
media: MediaBrowser,
|
media: MediaBrowser,
|
||||||
}
|
}
|
||||||
let boardEl = $state<HTMLDivElement>()
|
let boardEl = $state<HTMLDivElement>()
|
||||||
@@ -244,8 +243,8 @@
|
|||||||
[[...keysOf('tree', 'down'), ...keysOf('tree', 'up')], 'browse, l opens, L opens beside'],
|
[[...keysOf('tree', 'down'), ...keysOf('tree', 'up')], 'browse, l opens, L opens beside'],
|
||||||
[keysOf('global', 'space'), 'switch space, Tab cycles'],
|
[keysOf('global', 'space'), 'switch space, Tab cycles'],
|
||||||
[keysOf('global', 'overview'), 'overview of every space'],
|
[keysOf('global', 'overview'), 'overview of every space'],
|
||||||
[keysOf('global', 'special'), 'board, agenda and calendar'],
|
[keysOf('global', 'plan'), 'board, agenda and calendar'],
|
||||||
[[...keysOf('tree', 'board'), ...keysOf('global', 'agenda'), ...keysOf('global', 'calendar')], 'straight to one of them'],
|
[[...keysOf('global', 'board'), ...keysOf('global', 'agenda'), ...keysOf('global', 'calendar')], 'one of them, in a tile'],
|
||||||
[keysOf('tree', 'capture'), 'capture a card to the inbox'],
|
[keysOf('tree', 'capture'), 'capture a card to the inbox'],
|
||||||
[keysOf('global', 'find'), 'find in every space'],
|
[keysOf('global', 'find'), 'find in every space'],
|
||||||
[keysOf('tree', 'search'), 'search this space'],
|
[keysOf('tree', 'search'), 'search this space'],
|
||||||
@@ -287,39 +286,17 @@
|
|||||||
<!-- 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 === 'special'}
|
|
||||||
<!-- The special workspace: the board, and the agenda over the
|
|
||||||
calendar beside it. `f` gives the focused one the whole porthole. -->
|
|
||||||
<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>
|
|
||||||
{:else}
|
|
||||||
<div
|
<div
|
||||||
class="panes switch"
|
class="panes switch"
|
||||||
class:reading={workspace.tiles.some((tile) => tile.open)}
|
class:reading={workspace.tiles.some((tile) => !tile.empty)}
|
||||||
class:full={workspace.fullscreen}
|
class:full={workspace.fullscreen}
|
||||||
class:focus-doc={!!workspace.open && workspace.pane === 'document'}
|
class:focus-doc={workspace.focus !== 'list'}
|
||||||
style="--dir: {workspace.switchDirection}"
|
style="--dir: {workspace.switchDirection}"
|
||||||
>
|
>
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="window list"
|
class="window list"
|
||||||
class:focused={!workspace.open || workspace.pane === 'list'}
|
class:focused={workspace.focus === 'list'}
|
||||||
onpointerdown={() => (workspace.pane = 'list')}
|
onpointerdown={() => (workspace.pane = 'list')}
|
||||||
><Sidebar /></div>
|
><Sidebar /></div>
|
||||||
<div class="tiles" class:only={workspace.tiles.length === 1}>
|
<div class="tiles" class:only={workspace.tiles.length === 1}>
|
||||||
@@ -329,13 +306,23 @@
|
|||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="window entry"
|
class="window entry"
|
||||||
|
class:view={tile.kind !== 'node'}
|
||||||
|
data-kind={tile.kind}
|
||||||
class:current={index === workspace.focused}
|
class:current={index === workspace.focused}
|
||||||
class:focused={!!tile.open && index === workspace.focused && workspace.pane === 'document'}
|
class:focused={!tile.empty && index === workspace.focused && workspace.pane === 'document'}
|
||||||
style="--x: {rect.x}; --y: {rect.y}; --w: {rect.w}; --h: {rect.h}"
|
style="--x: {rect.x}; --y: {rect.y}; --w: {rect.w}; --h: {rect.h}"
|
||||||
onpointerdown={() => tile.open && workspace.focusTile(index)}
|
onpointerdown={() => !tile.empty && workspace.focusTile(index)}
|
||||||
onfocusin={() => tile.open && index !== workspace.focused && workspace.focusTile(index)}
|
onfocusin={() => !tile.empty && index !== workspace.focused && workspace.focusTile(index)}
|
||||||
>
|
>
|
||||||
{#if tile.open?.kind === 'file'}
|
{#if tile.kind === 'board'}
|
||||||
|
<div class="fill" bind:this={boardEl}><Board /></div>
|
||||||
|
{:else if tile.kind === 'agenda'}
|
||||||
|
<Agenda />
|
||||||
|
{:else if tile.kind === 'calendar'}
|
||||||
|
<Calendar />
|
||||||
|
{:else if tile.kind === 'card'}
|
||||||
|
<CardTile />
|
||||||
|
{:else if tile.open?.kind === 'file'}
|
||||||
<FilePane {tile} />
|
<FilePane {tile} />
|
||||||
{:else if tile.open}
|
{:else if tile.open}
|
||||||
<DocumentPane {tile} />
|
<DocumentPane {tile} />
|
||||||
@@ -358,7 +345,6 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
{/key}
|
{/key}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -402,48 +388,23 @@
|
|||||||
<style>
|
<style>
|
||||||
.boot { display: grid; place-items: center; height: 100%; }
|
.boot { display: grid; place-items: center; height: 100%; }
|
||||||
|
|
||||||
/*
|
.fill { height: 100%; }
|
||||||
* The special workspace: the board takes what the right column leaves,
|
/* The focused view's key is lit, as its border is. */
|
||||||
* the agenda takes what the calendar leaves under it.
|
.tiles > .window.focused :global(.tile-key) { background: var(--light); border-color: var(--light); color: var(--ink); }
|
||||||
*/
|
|
||||||
.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%; }
|
|
||||||
/* The focused tile's key is lit, as its border is. */
|
|
||||||
.special > .window.focused :global(.tile-key) { background: var(--light); border-color: var(--light); color: var(--ink); }
|
|
||||||
|
|
||||||
/* One cursor is lit at a time: the other tiles keep theirs, dimmed. */
|
/* One cursor is lit at a time: the views without the focus keep theirs, dimmed. */
|
||||||
.special > .window:not(.focused) :global(:is(.card.active, .row.at, .day.at)) {
|
.view:not(.focused) :global(:is(.card.active, .row.at, .day.at, li.at)) {
|
||||||
background: var(--surface-alt);
|
background: var(--surface-alt);
|
||||||
color: var(--light);
|
color: var(--light);
|
||||||
}
|
}
|
||||||
.special > .window:not(.focused) :global(.row.at :is(.where, .due)) { color: var(--muted); }
|
.view:not(.focused) :global(.row.at :is(.where, .due)) { color: var(--muted); }
|
||||||
.special > .window:not(.focused) :global(.card.active :is(.meta, .doc)) { color: var(--muted); }
|
.view: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); }
|
.view: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); }
|
.view: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); }
|
.view:not(.focused) :global(:is(.card.active, li.at) .tick) { border-color: var(--outline); }
|
||||||
.special > .window:not(.focused) :global(.day.at :is(.dot)) { background: var(--light); }
|
.view:not(.focused) :global(.day.at :is(.dot)) { background: var(--light); }
|
||||||
.special > .window:not(.focused) :global(.day.at .many) { color: var(--light); }
|
.view: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. */
|
||||||
@@ -516,9 +477,5 @@
|
|||||||
.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>
|
||||||
@@ -112,23 +112,20 @@
|
|||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Keys contexts={['agenda', 'special']} />
|
<Keys contexts={['agenda', 'views']} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
li:has(.finish) { display: flex; align-items: center; }
|
li:has(.finish) { display: flex; align-items: center; }
|
||||||
.finish { flex: none; display: grid; place-items: center; width: 44px; height: 40px; padding: 0; border: 0; background: none; color: var(--muted); }
|
.finish { flex: none; display: grid; place-items: center; width: 44px; height: 40px; padding: 0; border: 0; background: none; color: var(--muted); }
|
||||||
|
|
||||||
|
/* A tile's window is its frame, so it only fills it. */
|
||||||
.sheet {
|
.sheet {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
width: min(100%, 84ch);
|
height: 100%;
|
||||||
max-height: 100%;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: color-mix(in oklab, var(--hull) 97%, transparent);
|
|
||||||
border: 1px solid var(--surface-alt);
|
|
||||||
border-radius: var(--frame-radius);
|
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-31
@@ -35,43 +35,30 @@
|
|||||||
<button class="pill add" title="new space (:space! <name>)" onclick={() => hooks.openCommandLine(':space! ')}>
|
<button class="pill add" title="new space (:space! <name>)" onclick={() => hooks.openCommandLine(':space! ')}>
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
<!-- The special workspace, beside the others as the desktop bar shows its own. -->
|
|
||||||
<button
|
|
||||||
class="pill special"
|
|
||||||
aria-current={workspace.view === 'special'}
|
|
||||||
title="the special workspace: board, agenda, calendar (S)"
|
|
||||||
onclick={() => workspace.guard(() => workspace.toggleSpecial())}
|
|
||||||
>
|
|
||||||
<span class="slug">special</span>
|
|
||||||
</button>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<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 === 'special'}
|
<button
|
||||||
<span class="here">special:{workspace.specialFocus}</span>
|
class="crumb"
|
||||||
{:else}
|
class:here={crumbs.length === 0}
|
||||||
|
onclick={() => workspace.spaceId !== null && workspace.guard(() => workspace.goTo(workspace.spaceId!))}
|
||||||
|
{@attach into(workspace.spaceId, 'into ~')}
|
||||||
|
>
|
||||||
|
~
|
||||||
|
</button>
|
||||||
|
{#each crumbs as crumb, index (crumb.id)}
|
||||||
|
<span class="sep">/</span>
|
||||||
<button
|
<button
|
||||||
class="crumb"
|
class="crumb"
|
||||||
class:here={crumbs.length === 0}
|
class:here={index === crumbs.length - 1}
|
||||||
onclick={() => workspace.spaceId !== null && workspace.guard(() => workspace.goTo(workspace.spaceId!))}
|
onclick={() => workspace.guard(() => workspace.goTo(crumb.id))}
|
||||||
{@attach into(workspace.spaceId, 'into ~')}
|
{@attach into(crumb.id, `into ${crumb.slug}`)}
|
||||||
>
|
>
|
||||||
~
|
{crumb.slug}
|
||||||
</button>
|
</button>
|
||||||
{#each crumbs as crumb, index (crumb.id)}
|
{/each}
|
||||||
<span class="sep">/</span>
|
|
||||||
<button
|
|
||||||
class="crumb"
|
|
||||||
class:here={index === crumbs.length - 1}
|
|
||||||
onclick={() => workspace.guard(() => workspace.goTo(crumb.id))}
|
|
||||||
{@attach into(crumb.id, `into ${crumb.slug}`)}
|
|
||||||
>
|
|
||||||
{crumb.slug}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
@@ -123,7 +110,6 @@
|
|||||||
.pill:hover .n { color: var(--light-dim); }
|
.pill:hover .n { color: var(--light-dim); }
|
||||||
.pill[aria-current='true'] .n { color: var(--ink); opacity: 0.55; }
|
.pill[aria-current='true'] .n { color: var(--ink); opacity: 0.55; }
|
||||||
.pill.add { color: var(--muted); }
|
.pill.add { color: var(--muted); }
|
||||||
.pill.special { margin-left: 6px; }
|
|
||||||
.pill.add:hover { color: var(--light); }
|
.pill.add:hover { color: var(--light); }
|
||||||
|
|
||||||
/* A crumb is a link along the path; a hairline grows under it on hover. */
|
/* A crumb is a link along the path; a hairline grows under it on hover. */
|
||||||
@@ -152,9 +138,8 @@
|
|||||||
}
|
}
|
||||||
.crumb:hover { background: transparent; color: var(--light); }
|
.crumb:hover { background: transparent; color: var(--light); }
|
||||||
.crumb:hover::after { transform: scaleX(1); }
|
.crumb:hover::after { transform: scaleX(1); }
|
||||||
.crumb.here, .here { color: var(--light); }
|
.crumb.here { color: var(--light); }
|
||||||
.sep { color: var(--overlay); }
|
.sep { color: var(--overlay); }
|
||||||
span.here { letter-spacing: 0.06em; text-transform: none; }
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* On a phone the other spaces are only their number, like the desktop's
|
* On a phone the other spaces are only their number, like the desktop's
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
class:done={card.done_at !== null}
|
class:done={card.done_at !== null}
|
||||||
data-at="{listIndex}:{cardIndex}"
|
data-at="{listIndex}:{cardIndex}"
|
||||||
onclick={() => pick(listIndex, cardIndex)}
|
onclick={() => pick(listIndex, cardIndex)}
|
||||||
ondblclick={() => workspace.openCardSheet()}
|
ondblclick={() => workspace.guard(() => workspace.turnCard())}
|
||||||
title="Enter turns the card over"
|
title="Enter turns the card over"
|
||||||
>
|
>
|
||||||
{#if card.labels.length}
|
{#if card.labels.length}
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
|
|
||||||
<!-- The board's keys are not the list's, so they are spelled out where they apply. -->
|
<!-- The board's keys are not the list's, so they are spelled out where they apply. -->
|
||||||
<div class="keys">
|
<div class="keys">
|
||||||
<Keys contexts={['board', 'special']}><span class="rule-hint">first list is the inbox · last list is done</span></Keys>
|
<Keys contexts={['board', 'views']}><span class="rule-hint">first list is the inbox · last list is done</span></Keys>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -79,20 +79,17 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Keys contexts={['calendar', 'special']} />
|
<Keys contexts={['calendar', 'views']} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* A tile's window is its frame, so it only fills it. */
|
||||||
.sheet {
|
.sheet {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
width: min(100%, 60ch);
|
height: 100%;
|
||||||
max-height: 100%;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: color-mix(in oklab, var(--hull) 97%, transparent);
|
|
||||||
border: 1px solid var(--surface-alt);
|
|
||||||
border-radius: var(--frame-radius);
|
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/*
|
/*
|
||||||
* Enter on a card turns it over: a window floated over the board, as the
|
* Enter on a card turns it over: a tile beside the board with what the
|
||||||
* desktop floats one, with what the card holds on its back — labels, the
|
* card holds on its back — labels, the description, the checklist. It is
|
||||||
* description, the checklist. The keys live in actions.ts with the others;
|
* the card under the board's cursor, so walking the board leafs through
|
||||||
* this draws and lets a pointer do the same.
|
* the backs. The keys live in actions.ts with the others; this draws and
|
||||||
|
* lets a pointer do the same.
|
||||||
*/
|
*/
|
||||||
import Sheet from './Sheet.svelte'
|
import Keys from './Keys.svelte'
|
||||||
import { followLink } from './commands'
|
import { followLink } from './commands'
|
||||||
import { renderMarkdown } from './markdown'
|
import { renderMarkdown } from './markdown'
|
||||||
import { workspace } from './store.svelte'
|
import { workspace } from './store.svelte'
|
||||||
@@ -18,11 +19,21 @@
|
|||||||
const ticked = $derived(card?.checklist.filter((task) => task.done).length ?? 0)
|
const ticked = $derived(card?.checklist.filter((task) => task.done).length ?? 0)
|
||||||
const html = $derived(card?.body.trim() ? renderMarkdown(card.body, { wikilinks: card.wikilinks }) : '')
|
const html = $derived(card?.body.trim() ? renderMarkdown(card.body, { wikilinks: card.wikilinks }) : '')
|
||||||
|
|
||||||
// A card deleted or moved out of sight under the sheet takes the sheet with it.
|
let root = $state<HTMLDivElement>()
|
||||||
|
|
||||||
|
// Another card's back starts at its first step.
|
||||||
|
let shown: number | undefined
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!card) workspace.closeSheet()
|
if (card?.id === shown) return
|
||||||
|
shown = card?.id
|
||||||
|
workspace.taskCursor = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Keep the step under the cursor on screen.
|
||||||
|
$effect(() => {
|
||||||
|
const index = workspace.taskCursor
|
||||||
|
root?.querySelector<HTMLElement>(`[data-index="${index}"]`)?.scrollIntoView({ block: 'nearest' })
|
||||||
|
})
|
||||||
|
|
||||||
/** A link in the description leaves the card for what it leads to, as one in a document does. */
|
/** A link in the description leaves the card for what it leads to, as one in a document does. */
|
||||||
function follow(event: MouseEvent) {
|
function follow(event: MouseEvent) {
|
||||||
@@ -30,7 +41,6 @@
|
|||||||
const href = (event.target as HTMLElement | null)?.closest('a')?.getAttribute('href') ?? ''
|
const href = (event.target as HTMLElement | null)?.closest('a')?.getAttribute('href') ?? ''
|
||||||
if (!href.startsWith('/n/')) return
|
if (!href.startsWith('/n/')) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
workspace.closeCardSheet()
|
|
||||||
followLink(href)
|
followLink(href)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +54,16 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if card}
|
<div class="back" bind:this={root}>
|
||||||
<Sheet title="card" detail="{workspace.boardSpace?.slug ?? ''} / {list?.name ?? ''}{role ? ` · ${role}` : ''}" width={76} cursor={workspace.taskCursor} keys={['card']}>
|
<header>
|
||||||
|
<span class="label rule">
|
||||||
|
<span class="marker"></span><span class="title">card</span>
|
||||||
|
{#if card}{workspace.boardSpace?.slug ?? ''} / {list?.name ?? ''}{role ? ` · ${role}` : ''}{/if}
|
||||||
|
</span>
|
||||||
|
<span class="kbd tile-key" title="the key that turns a card over, on the board">↵</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{#if card}
|
||||||
<div class="scroll">
|
<div class="scroll">
|
||||||
<h2 class:done={card.done_at !== null}>{card.title}</h2>
|
<h2 class:done={card.done_at !== null}>{card.title}</h2>
|
||||||
|
|
||||||
@@ -113,12 +130,28 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</Sheet>
|
{:else}
|
||||||
{/if}
|
<p class="none muted">no card under the board's cursor — o on the board adds one</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Keys contexts={['card', 'views']} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.back {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.scroll { display: flex; flex-direction: column; gap: 18px; min-height: 0; overflow-y: auto; }
|
header { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
||||||
|
header .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
header .marker { background: var(--light); }
|
||||||
|
header .title { color: var(--light); }
|
||||||
|
.scroll { flex: 1; display: flex; flex-direction: column; gap: 18px; min-height: 0; overflow-y: auto; }
|
||||||
|
|
||||||
h2 { margin: 0; font-family: var(--font-sans); font-size: 20px; font-weight: 600; color: var(--light); }
|
h2 { margin: 0; font-family: var(--font-sans); font-size: 20px; font-weight: 600; color: var(--light); }
|
||||||
h2.done { text-decoration: line-through; color: var(--muted); }
|
h2.done { text-decoration: line-through; color: var(--muted); }
|
||||||
@@ -170,4 +203,7 @@
|
|||||||
li.at .tick { border-color: var(--ink); }
|
li.at .tick { border-color: var(--ink); }
|
||||||
li.at.ticked .tick { background: var(--ink); }
|
li.at.ticked .tick { background: var(--ink); }
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.back { padding: 14px; gap: 14px; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
+15
-16
@@ -31,21 +31,22 @@
|
|||||||
const cmd: Door = { icon: 'prompt', label: 'Command line', keys: keysOf('global', 'line'), run: () => hooks.openCommandLine(':') }
|
const cmd: Door = { icon: 'prompt', label: 'Command line', keys: keysOf('global', 'line'), run: () => hooks.openCommandLine(':') }
|
||||||
const help: Door = { icon: 'help', label: 'Help', keys: keysOf('global', 'help'), run: () => runCommand('help') }
|
const help: Door = { icon: 'help', label: 'Help', keys: keysOf('global', 'help'), run: () => runCommand('help') }
|
||||||
|
|
||||||
if (workspace.view === 'special') {
|
const focus = workspace.focus
|
||||||
// The tiles of the special workspace are doors, so a phone — which
|
const board: Door = workspace.space?.has_board || focus === 'board'
|
||||||
// shows one at a time — can walk between them.
|
? { icon: 'board', label: 'Board', keys: keysOf('global', 'board'), run: () => runCommand('board'), on: focus === 'board' }
|
||||||
const tiles: Door[] = [
|
: { icon: 'board', label: 'Give this space a board', keys: [':set board'], run: () => runCommand('set board'), dim: true }
|
||||||
{ icon: 'board', label: 'Board', keys: keysOf('special', 'board'), run: () => (workspace.specialFocus = 'board'), on: workspace.specialFocus === 'board' },
|
const agenda: Door = { icon: 'agenda', label: 'Agenda — late, due, in hand', keys: keysOf('global', 'agenda'), run: () => runCommand('agenda'), on: focus === 'agenda' }
|
||||||
{ icon: 'agenda', label: 'Agenda — late, due, in hand', keys: keysOf('global', 'agenda'), run: () => (workspace.specialFocus = 'agenda'), on: workspace.specialFocus === 'agenda' },
|
const calendar: Door = { icon: 'calendar', label: 'Calendar — the diary by month', keys: keysOf('global', 'calendar'), run: () => runCommand('cal'), on: focus === 'calendar' }
|
||||||
{ icon: 'calendar', label: 'Calendar', keys: keysOf('global', 'calendar'), run: () => (workspace.specialFocus = 'calendar'), on: workspace.specialFocus === 'calendar' },
|
// A phone shows the focused tile alone, so a view needs a door out of it.
|
||||||
{ icon: 'list', label: 'Back to the tree', keys: [...keysOf('special', 'leave'), ...keysOf('global', 'special')], run: () => workspace.closeSpecial() },
|
const close: Door[] =
|
||||||
]
|
focus !== 'list' && focus !== 'node' ? [{ icon: 'list', label: 'Close this view', keys: keysOf('views', 'close'), run: () => runCommand('close') }] : []
|
||||||
if (workspace.specialFocus !== 'board') return [tiles, [cmd, help]]
|
|
||||||
|
if (focus === 'board' || focus === 'card') {
|
||||||
// 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,
|
[board, agenda, calendar, ...close],
|
||||||
[
|
[
|
||||||
{ icon: 'doc-plus', label: 'New card on this list', keys: keysOf('board', 'new'), run: () => hooks.openCommandLine(':card ') },
|
{ icon: 'doc-plus', label: 'New card on this list', keys: keysOf('board', 'new'), 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 ') },
|
||||||
@@ -78,9 +79,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const board: Door = workspace.space?.has_board
|
|
||||||
? { icon: 'board', label: 'Board', keys: keysOf('tree', 'board'), run: () => runCommand('board') }
|
|
||||||
: { icon: 'board', label: 'Give this space a board', keys: [':set board'], run: () => runCommand('set board'), dim: true }
|
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
{ icon: 'doc-plus', label: 'New document', keys: keysOf('tree', 'new'), run: () => runCommand('new') },
|
{ icon: 'doc-plus', label: 'New document', keys: keysOf('tree', 'new'), run: () => runCommand('new') },
|
||||||
@@ -88,10 +86,11 @@
|
|||||||
...(workspace.space ? [board] : []),
|
...(workspace.space ? [board] : []),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ icon: 'agenda', label: 'Agenda — late, due, in hand', keys: keysOf('global', 'agenda'), run: () => runCommand('agenda') },
|
agenda,
|
||||||
{ icon: 'calendar', label: 'Calendar — the diary by month', keys: keysOf('global', 'calendar'), run: () => runCommand('cal') },
|
calendar,
|
||||||
{ icon: 'today', label: "Today's diary entry", keys: keysOf('global', 'today'), run: () => runCommand('today') },
|
{ icon: 'today', label: "Today's diary entry", keys: keysOf('global', 'today'), run: () => runCommand('today') },
|
||||||
{ icon: 'capture', label: 'Capture a card to the inbox', keys: keysOf('tree', 'capture'), run: () => hooks.openCommandLine(':card ') },
|
{ icon: 'capture', label: 'Capture a card to the inbox', keys: keysOf('tree', 'capture'), run: () => hooks.openCommandLine(':card ') },
|
||||||
|
...close,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ icon: 'upload', label: 'Upload files into this folder', keys: [':upload'], run: () => hooks.pickTreeFiles(false) },
|
{ icon: 'upload', label: 'Upload files into this folder', keys: [':upload'], run: () => hooks.pickTreeFiles(false) },
|
||||||
|
|||||||
+26
-52
@@ -14,7 +14,7 @@ import { focusHelpFilter, scrollHelp } from './Help.svelte'
|
|||||||
import { copyMedia, insertMedia, removeMedia } from './MediaBrowser.svelte'
|
import { copyMedia, insertMedia, removeMedia } from './MediaBrowser.svelte'
|
||||||
import { purgeTrashed, restoreTrashed } from './Trash.svelte'
|
import { purgeTrashed, restoreTrashed } from './Trash.svelte'
|
||||||
import { goHeading, moveHistory, openBacklink, renewLink, restoreShown, toggleSharing, turnInfo } from './Info.svelte'
|
import { goHeading, moveHistory, openBacklink, renewLink, restoreShown, toggleSharing, turnInfo } from './Info.svelte'
|
||||||
import { workspace, type SpecialTile } from './store.svelte'
|
import { workspace } from './store.svelte'
|
||||||
import { dwindle, formatDay, neighbour, type Direction } from './util'
|
import { dwindle, formatDay, neighbour, type Direction } from './util'
|
||||||
|
|
||||||
export type Run = (key: string, event: KeyboardEvent) => boolean | void
|
export type Run = (key: string, event: KeyboardEvent) => boolean | void
|
||||||
@@ -75,34 +75,19 @@ function openInTab() {
|
|||||||
|
|
||||||
/** Focus moves between the panes, out of the editor too. */
|
/** Focus moves between the panes, out of the editor too. */
|
||||||
function focusPane(pane: 'list' | 'document') {
|
function focusPane(pane: 'list' | 'document') {
|
||||||
if (pane === 'document' && !workspace.open) return
|
if (pane === 'document' && workspace.tile.empty) return
|
||||||
workspace.pane = pane
|
workspace.pane = pane
|
||||||
if (pane === 'document' && workspace.editing) hooks.focusEditor()
|
if (pane === 'document' && workspace.editing) hooks.focusEditor()
|
||||||
else hooks.focusList()
|
else hooks.focusList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 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' },
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Alt + h/j/k/l: the focus goes to the tile that way, out of the editor
|
* Alt + h/j/k/l: the focus goes to the tile that way, out of the editor
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
function tileKey(direction: Direction, shift: boolean) {
|
function tileKey(direction: Direction, shift: boolean) {
|
||||||
if (workspace.view === 'special') {
|
if (workspace.focus === 'list') {
|
||||||
const next = specialNeighbours[workspace.specialFocus][direction]
|
|
||||||
if (next && !shift) workspace.specialFocus = next
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (workspace.pane === 'list' || !workspace.open) {
|
|
||||||
if (direction === 'l' && !shift) focusPane('document')
|
if (direction === 'l' && !shift) focusPane('document')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -131,10 +116,9 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
sheet: {
|
sheet: {
|
||||||
// A description or a note being written has had its Esc; the sheet
|
// A note being written has had its Esc; the sheet stays up under it.
|
||||||
// stays up under it.
|
|
||||||
close: () => {
|
close: () => {
|
||||||
if (workspace.describing || workspace.scratchEditing) return
|
if (workspace.scratchEditing) return
|
||||||
workspace.closeSheet()
|
workspace.closeSheet()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -233,16 +217,16 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
restore: () => void restoreShown(),
|
restore: () => void restoreShown(),
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Its sharing: the same s and y as on the document itself. */
|
/** Its sharing: s shares or stops, y makes a new link. */
|
||||||
share: {
|
share: {
|
||||||
toggle: () => void toggleSharing(workspace.open),
|
toggle: () => void toggleSharing(workspace.open),
|
||||||
link: () => void renewLink(workspace.open),
|
link: () => void renewLink(workspace.open),
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The card sheet reads the list keys over its checklist, and the board's
|
* A card's back reads the list keys over its checklist, and the board's
|
||||||
* card keys for the card itself — `D`, `H`/`L`, `R` act on it as they do
|
* card keys for the card itself — `D`, `H`/`L`, `R` act on it as they do
|
||||||
* on the board under it. While the description is being written the keys
|
* on the board beside it. While the description is being written the keys
|
||||||
* are the editor's.
|
* are the editor's.
|
||||||
*/
|
*/
|
||||||
card: {
|
card: {
|
||||||
@@ -265,12 +249,8 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
due: () => line(':due '),
|
due: () => line(':due '),
|
||||||
sendLeft: () => guard(() => workspace.sendCard(-1)),
|
sendLeft: () => guard(() => workspace.sendCard(-1)),
|
||||||
sendRight: () => guard(() => workspace.sendCard(1)),
|
sendRight: () => guard(() => workspace.sendCard(1)),
|
||||||
notes: () => {
|
notes: () => guard(() => workspace.openCard()),
|
||||||
workspace.closeSheet()
|
|
||||||
guard(() => workspace.openCard())
|
|
||||||
},
|
|
||||||
line: () => line(':'),
|
line: () => line(':'),
|
||||||
close: () => workspace.closeCardSheet(),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -346,9 +326,6 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
if (workspace.open && workspace.pane === 'document') focusPane('list')
|
if (workspace.open && workspace.pane === 'document') focusPane('list')
|
||||||
else guard(() => workspace.up())
|
else guard(() => workspace.up())
|
||||||
},
|
},
|
||||||
// `gt` / `gT` walk the tiles, as they walk vim's tab pages.
|
|
||||||
nextTile: () => void cycleTile(1),
|
|
||||||
previousTile: () => void cycleTile(-1),
|
|
||||||
sideways: (key) => {
|
sideways: (key) => {
|
||||||
const amount = ({ zh: -1, zl: 1, zH: 'halfleft', zL: 'halfright' } as const)[key as 'zh' | 'zl' | 'zH' | 'zL']
|
const amount = ({ zh: -1, zl: 1, zH: 'halfleft', zL: 'halfright' } as const)[key as 'zh' | 'zl' | 'zH' | 'zL']
|
||||||
if (!reading() || !hooks.scrollWide(amount)) workspace.say('nothing wider than the column in view', 'error')
|
if (!reading() || !hooks.scrollWide(amount)) workspace.say('nothing wider than the column in view', 'error')
|
||||||
@@ -385,9 +362,6 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
// names are typed — this only saves typing `:mkdir `.
|
// names are typed — this only saves typing `:mkdir `.
|
||||||
mkdir: () => line(':mkdir '),
|
mkdir: () => line(':mkdir '),
|
||||||
edit: () => void runCommand('edit'),
|
edit: () => void runCommand('edit'),
|
||||||
// The board is a view of the space, not a row in the list, so it needs
|
|
||||||
// a key as plain as the one that opens a document.
|
|
||||||
board: () => void runCommand('board'),
|
|
||||||
// Renaming starts from the name it has, so a typo is a fix, not a retype.
|
// Renaming starts from the name it has, so a typo is a fix, not a retype.
|
||||||
rename: () => {
|
rename: () => {
|
||||||
const target = workspace.target
|
const target = workspace.target
|
||||||
@@ -419,9 +393,9 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
*/
|
*/
|
||||||
board: {
|
board: {
|
||||||
left: () => workspace.moveList(-1),
|
left: () => workspace.moveList(-1),
|
||||||
// Right of the last list is the agenda, the tile beside the board.
|
// Right of the last list is the tile beside the board.
|
||||||
right: () => {
|
right: () => {
|
||||||
if (workspace.listCursor >= (workspace.board?.lists.length ?? 0) - 1) workspace.specialFocus = 'agenda'
|
if (workspace.listCursor >= (workspace.board?.lists.length ?? 0) - 1) tileKey('l', false)
|
||||||
else workspace.moveList(1)
|
else workspace.moveList(1)
|
||||||
},
|
},
|
||||||
down: () => workspace.moveCard(1),
|
down: () => workspace.moveCard(1),
|
||||||
@@ -433,7 +407,7 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
sendLeft: () => guard(() => workspace.sendCard(-1)),
|
sendLeft: () => guard(() => workspace.sendCard(-1)),
|
||||||
sendRight: () => guard(() => workspace.sendCard(1)),
|
sendRight: () => guard(() => workspace.sendCard(1)),
|
||||||
done: () => guard(() => workspace.toggleDone()),
|
done: () => guard(() => workspace.toggleDone()),
|
||||||
turn: () => workspace.openCardSheet(),
|
turn: () => guard(() => workspace.turnCard()),
|
||||||
// The card's notes, without turning it over first: `gf` as on its back.
|
// The card's notes, without turning it over first: `gf` as on its back.
|
||||||
notes: () => guard(() => workspace.openCard()),
|
notes: () => guard(() => workspace.openCard()),
|
||||||
new: () => line(':card '),
|
new: () => line(':card '),
|
||||||
@@ -449,27 +423,26 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
},
|
},
|
||||||
reload: () => guard(() => workspace.reloadBoard()),
|
reload: () => guard(() => workspace.reloadBoard()),
|
||||||
delete: () => void runCommand('delete'),
|
delete: () => void runCommand('delete'),
|
||||||
leave: () => workspace.closeSpecial(),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The agenda: j/k down the list, Enter to the card, Space finishes it.
|
* The agenda: j/k down the list, Enter to the card, Space finishes it.
|
||||||
* Past the cards the cursor walks on into what was written on this day,
|
* Past the cards the cursor walks on into what was written on this day,
|
||||||
* and past that into the calendar under it; `h` is the board to its left.
|
* and past that into the tile under it; `h` is the tile to its left.
|
||||||
*/
|
*/
|
||||||
agenda: {
|
agenda: {
|
||||||
down: () => {
|
down: () => {
|
||||||
const last = Math.max(workspace.agendaCards.length + workspace.agenda.on_this_day.length - 1, 0)
|
const last = Math.max(workspace.agendaCards.length + workspace.agenda.on_this_day.length - 1, 0)
|
||||||
if (workspace.agendaCursor >= last) workspace.specialFocus = 'calendar'
|
if (workspace.agendaCursor >= last) tileKey('j', false)
|
||||||
else workspace.agendaCursor += 1
|
else workspace.agendaCursor += 1
|
||||||
},
|
},
|
||||||
up: () => void (workspace.agendaCursor = Math.max(workspace.agendaCursor - 1, 0)),
|
up: () => void (workspace.agendaCursor = Math.max(workspace.agendaCursor - 1, 0)),
|
||||||
left: () => void (workspace.specialFocus = 'board'),
|
left: () => tileKey('h', false),
|
||||||
open: () => {
|
open: () => {
|
||||||
const card = workspace.agendaCards[workspace.agendaCursor]
|
const card = workspace.agendaCards[workspace.agendaCursor]
|
||||||
const past = workspace.agenda.on_this_day[workspace.agendaCursor - workspace.agendaCards.length]
|
const past = workspace.agenda.on_this_day[workspace.agendaCursor - workspace.agendaCards.length]
|
||||||
if (card) guard(() => workspace.goToCard(card))
|
if (card) guard(() => workspace.goToCard(card))
|
||||||
// Opened from the special workspace, so `q` on it comes back here.
|
// Beside the agenda, so `q` on it comes back here.
|
||||||
else if (past) guard(() => workspace.openPath(past.path))
|
else if (past) guard(() => workspace.openPath(past.path))
|
||||||
},
|
},
|
||||||
finish: () => {
|
finish: () => {
|
||||||
@@ -483,7 +456,6 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
sendLeft: () => sendAgendaCard(-1),
|
sendLeft: () => sendAgendaCard(-1),
|
||||||
sendRight: () => sendAgendaCard(1),
|
sendRight: () => sendAgendaCard(1),
|
||||||
today: () => void runCommand('today'),
|
today: () => void runCommand('today'),
|
||||||
leave: () => workspace.closeSpecial(),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** The calendar: h/l a day, j/k a week, H/L a month, Enter to the day. */
|
/** The calendar: h/l a day, j/k a week, H/L a month, Enter to the day. */
|
||||||
@@ -496,15 +468,11 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
guard(() => workspace.loadCalendar())
|
guard(() => workspace.loadCalendar())
|
||||||
},
|
},
|
||||||
open: () => void workspace.guard(() => workspace.openCalendarDay()).then(() => hooks.focusEditor()),
|
open: () => void workspace.guard(() => workspace.openCalendarDay()).then(() => hooks.focusEditor()),
|
||||||
leave: () => workspace.closeSpecial(),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
special: {
|
/** What every view shares: `q` closes its tile, back to the one before. */
|
||||||
// Tab walks the tiles here, as it walks the spaces everywhere else; the
|
views: {
|
||||||
// digits still switch space, and the board with it.
|
close: () => void runCommand('close'),
|
||||||
cycle: (key) => workspace.cycleSpecial(shifted(key) ? -1 : 1),
|
|
||||||
board: () => void runCommand('board'),
|
|
||||||
leave: () => workspace.closeSpecial(),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
global: {
|
global: {
|
||||||
@@ -524,6 +492,12 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
cycle: (key) => guard(() => workspace.cycleSpace(shifted(key) ? -1 : 1)),
|
cycle: (key) => guard(() => workspace.cycleSpace(shifted(key) ? -1 : 1)),
|
||||||
overview: () => void openOverview(),
|
overview: () => void openOverview(),
|
||||||
fullscreen: () => void (workspace.fullscreen = !workspace.fullscreen),
|
fullscreen: () => void (workspace.fullscreen = !workspace.fullscreen),
|
||||||
|
// `gt` / `gT` walk the tiles, as they walk vim's tab pages.
|
||||||
|
nextTile: () => void cycleTile(1),
|
||||||
|
previousTile: () => void cycleTile(-1),
|
||||||
|
// The board is a view of the space, not a row in the list, so it needs
|
||||||
|
// a key as plain as the one that opens a document.
|
||||||
|
board: () => void runCommand('board'),
|
||||||
// The agenda and today's entry are a key away from anywhere, the board
|
// The agenda and today's entry are a key away from anywhere, the board
|
||||||
// included: they are the two questions asked most.
|
// included: they are the two questions asked most.
|
||||||
agenda: () => void runCommand('agenda'),
|
agenda: () => void runCommand('agenda'),
|
||||||
@@ -532,7 +506,7 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
|||||||
// `S`, as the desktop's special workspace is bound: the board, the
|
// `S`, as the desktop's special workspace is bound: the board, the
|
||||||
// agenda and the calendar, put up and away again. `b`, `t` and `C` only
|
// agenda and the calendar, put up and away again. `b`, `t` and `C` only
|
||||||
// ever go to their tile.
|
// ever go to their tile.
|
||||||
special: () => void runCommand('special'),
|
plan: () => void runCommand('plan'),
|
||||||
line: () => line(':'),
|
line: () => line(':'),
|
||||||
help: () => void runCommand('help'),
|
help: () => void runCommand('help'),
|
||||||
},
|
},
|
||||||
|
|||||||
+40
-34
@@ -95,6 +95,11 @@ export async function openInfo(section?: InfoSection) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The board's cursor, or the card turned over beside it, has the keys. */
|
||||||
|
const onBoard = () => workspace.focus === 'board' || workspace.focus === 'card'
|
||||||
|
/** A card has the keys: on the board, its back, or highlighted in the agenda. */
|
||||||
|
const cardFocused = () => onBoard() || workspace.focus === 'agenda'
|
||||||
|
|
||||||
const requireOpen = () => {
|
const requireOpen = () => {
|
||||||
if (!workspace.open) {
|
if (!workspace.open) {
|
||||||
workspace.say('no document open', 'error')
|
workspace.say('no document open', 'error')
|
||||||
@@ -151,7 +156,7 @@ export function cycleTile(delta: 1 | -1) {
|
|||||||
/** `:ls`'s line for one tile: number, `%` for the focused one, `[+]` unsaved. */
|
/** `:ls`'s line for one tile: number, `%` for the focused one, `[+]` unsaved. */
|
||||||
const describeTile = (index: number) => {
|
const describeTile = (index: number) => {
|
||||||
const tile = workspace.tiles[index]
|
const tile = workspace.tiles[index]
|
||||||
const name = tile.open ? tile.open.name.trim() || tile.open.slug : '(empty)'
|
const name = tile.kind !== 'node' ? tile.kind : tile.open ? tile.open.name.trim() || tile.open.slug : '(empty)'
|
||||||
return `${index + 1}${index === workspace.focused ? '%' : ''} ${name}${tile.dirty ? ' [+]' : ''}`
|
return `${index + 1}${index === workspace.focused ? '%' : ''} ${name}${tile.dirty ? ' [+]' : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,16 +378,12 @@ export const commands: CommandSpec[] = [
|
|||||||
if (workspace.scratchEditing) return workspace.leaveScratchNote()
|
if (workspace.scratchEditing) return workspace.leaveScratchNote()
|
||||||
if (workspace.describing) return void workspace.stopDescribing()
|
if (workspace.describing) return void workspace.stopDescribing()
|
||||||
// A document `o` made and nothing was written in is not kept.
|
// A document `o` made and nothing was written in is not kept.
|
||||||
if (await workspace.abandonIfBlank()) {
|
if (await workspace.abandonIfBlank()) return hooks.focusList()
|
||||||
if (workspace.returnTo === 'special') await workspace.guard(() => workspace.returnToSpecial())
|
|
||||||
return hooks.focusList()
|
|
||||||
}
|
|
||||||
if (workspace.dirty) {
|
if (workspace.dirty) {
|
||||||
workspace.say('unsaved changes — :w to write, :q! to discard', 'error')
|
workspace.say('unsaved changes — :w to write, :q! to discard', 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (workspace.editing) workspace.editing = false
|
if (workspace.editing) workspace.editing = false
|
||||||
else if (workspace.returnTo === 'special') return void workspace.guard(() => workspace.returnToSpecial())
|
|
||||||
else if (workspace.tiles.length > 1) return focusTile(closeFocused())
|
else if (workspace.tiles.length > 1) return focusTile(closeFocused())
|
||||||
else workspace.closeTile()
|
else workspace.closeTile()
|
||||||
hooks.focusList()
|
hooks.focusList()
|
||||||
@@ -472,14 +473,15 @@ export const commands: CommandSpec[] = [
|
|||||||
{
|
{
|
||||||
name: 'board',
|
name: 'board',
|
||||||
group: 'board',
|
group: 'board',
|
||||||
help: "this space's board, in the special workspace",
|
help: "this space's board, in a tile — the last one shown when this space has none",
|
||||||
run: () => workspace.guard(() => workspace.showSpecial('board')),
|
run: () => workspace.guard(() => workspace.showView('board')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'special',
|
name: 'plan',
|
||||||
|
aliases: ['special'],
|
||||||
group: 'board',
|
group: 'board',
|
||||||
help: 'the special workspace: the board, the agenda and the calendar side by side — S puts it up and away, Tab walks its tiles, 1 … 9 picks the board',
|
help: 'the board, the agenda and the calendar, tiled ahead of what is open — S puts them up, and every view away again; 1 … 9 on the board picks the board',
|
||||||
run: () => workspace.guard(() => workspace.toggleSpecial()),
|
run: () => workspace.guard(() => workspace.togglePlan()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'card',
|
name: 'card',
|
||||||
@@ -494,9 +496,7 @@ export const commands: CommandSpec[] = [
|
|||||||
bang: {
|
bang: {
|
||||||
help: 'add a card linked to the open document — gf on it opens the document',
|
help: 'add a card linked to the open document — gf on it opens the document',
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
// On the board the tree is not on screen, so only the open document
|
const node = workspace.target
|
||||||
// can be meant; elsewhere it is whatever has focus.
|
|
||||||
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')
|
||||||
}
|
}
|
||||||
@@ -509,11 +509,11 @@ export const commands: CommandSpec[] = [
|
|||||||
name: 'due',
|
name: 'due',
|
||||||
args: '<when|->',
|
args: '<when|->',
|
||||||
group: 'board',
|
group: 'board',
|
||||||
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, its back, 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.view === 'special' && workspace.specialFocus === 'agenda'
|
const agenda = workspace.focus === 'agenda'
|
||||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
if (!cardFocused()) return workspace.say('no card in focus — :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()
|
||||||
@@ -531,8 +531,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.view === 'special' && workspace.specialFocus === 'agenda'
|
const agenda = workspace.focus === 'agenda'
|
||||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
if (!cardFocused()) return workspace.say('no card in focus — :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 () => {
|
||||||
@@ -547,7 +547,11 @@ 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 !== 'special') return workspace.say('no board open — :board', 'error')
|
if (workspace.focus === 'agenda') {
|
||||||
|
const card = workspace.agendaCards[workspace.agendaCursor]
|
||||||
|
return card ? workspace.guard(() => workspace.finishCard(card)) : workspace.say('no card highlighted', 'error')
|
||||||
|
}
|
||||||
|
if (!onBoard()) return workspace.say('no card in focus — :board', 'error')
|
||||||
return workspace.guard(() => workspace.toggleDone())
|
return workspace.guard(() => workspace.toggleDone())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -579,14 +583,14 @@ export const commands: CommandSpec[] = [
|
|||||||
group: 'board',
|
group: 'board',
|
||||||
help: "add a step to the open card's checklist, below the cursor",
|
help: "add a step to the open card's checklist, below the cursor",
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
if (workspace.overlay !== 'card') return workspace.say('no card open — Enter on the board opens one', 'error')
|
if (workspace.focus !== 'card') return workspace.say('no card turned over — Enter on the board turns one', 'error')
|
||||||
if (!arg.trim()) return workspace.say('usage: :task <text>', 'error')
|
if (!arg.trim()) return workspace.say('usage: :task <text>', 'error')
|
||||||
await workspace.guard(() => workspace.addTask(arg.trim()))
|
await workspace.guard(() => workspace.addTask(arg.trim()))
|
||||||
},
|
},
|
||||||
bang: {
|
bang: {
|
||||||
help: 'reword the step under the cursor',
|
help: 'reword the step under the cursor',
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
if (workspace.overlay !== 'card') return workspace.say('no card open — Enter on the board opens one', 'error')
|
if (workspace.focus !== 'card') return workspace.say('no card turned over — Enter on the board turns one', 'error')
|
||||||
if (!arg.trim()) return workspace.say('usage: :task! <text>', 'error')
|
if (!arg.trim()) return workspace.say('usage: :task! <text>', 'error')
|
||||||
await workspace.guard(() => workspace.renameTask(arg.trim()))
|
await workspace.guard(() => workspace.renameTask(arg.trim()))
|
||||||
},
|
},
|
||||||
@@ -599,7 +603,7 @@ export const commands: CommandSpec[] = [
|
|||||||
help: 'put a label on the card, or take it off when it has it — each name keeps its colour everywhere',
|
help: 'put a label on the card, or take it off when it has it — each name keeps its colour everywhere',
|
||||||
complete: () => workspace.boardLabels,
|
complete: () => workspace.boardLabels,
|
||||||
run: async (arg) => {
|
run: async (arg) => {
|
||||||
if (workspace.view !== 'special') return workspace.say('no board open — :board', 'error')
|
if (!onBoard()) return workspace.say('no card in focus — :board', 'error')
|
||||||
if (!arg.trim()) return workspace.say(workspace.card?.labels.join(' · ') || 'no labels — :label <name> adds one')
|
if (!arg.trim()) return workspace.say(workspace.card?.labels.join(' · ') || 'no labels — :label <name> adds one')
|
||||||
await workspace.guard(() => workspace.toggleLabel(arg))
|
await workspace.guard(() => workspace.toggleLabel(arg))
|
||||||
},
|
},
|
||||||
@@ -610,14 +614,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 !== 'special') return workspace.say('no board open — :board', 'error')
|
if (!workspace.view('board')) 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 !== 'special') return workspace.say('no board open — :board', 'error')
|
if (!workspace.view('board')) 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
|
||||||
@@ -695,7 +699,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.view === 'special') return void (workspace.specialFocus = 'calendar')
|
if (workspace.view('calendar')) return workspace.showView('calendar')
|
||||||
await workspace.openCalendar()
|
await workspace.openCalendar()
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -706,8 +710,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 () => {
|
||||||
if (workspace.view !== 'special') workspace.agendaCursor = 0
|
if (!workspace.view('agenda')) workspace.agendaCursor = 0
|
||||||
await workspace.showSpecial('agenda')
|
await workspace.showView('agenda')
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -797,14 +801,16 @@ export const commands: CommandSpec[] = [
|
|||||||
args: '<n|name>',
|
args: '<n|name>',
|
||||||
group: 'tiles',
|
group: 'tiles',
|
||||||
help: 'focus a tile by its number in :ls, or by the start of its name',
|
help: 'focus a tile by its number in :ls, or by the start of its name',
|
||||||
complete: () => workspace.tiles.flatMap((tile) => (tile.open ? [tile.open.slug] : [])),
|
complete: () => workspace.tiles.flatMap((tile) => (tile.kind !== 'node' ? [tile.kind] : tile.open ? [tile.open.slug] : [])),
|
||||||
run: (arg) => {
|
run: (arg) => {
|
||||||
const wanted = arg.trim().toLowerCase()
|
const wanted = arg.trim().toLowerCase()
|
||||||
if (!wanted) return workspace.say(describeTile(workspace.focused))
|
if (!wanted) return workspace.say(describeTile(workspace.focused))
|
||||||
const index = /^\d+$/.test(wanted)
|
const index = /^\d+$/.test(wanted)
|
||||||
? Number(wanted) - 1
|
? Number(wanted) - 1
|
||||||
: workspace.tiles.findIndex(
|
: workspace.tiles.findIndex((tile) =>
|
||||||
(tile) => tile.open && (tile.open.slug.startsWith(wanted) || tile.open.name.toLowerCase().startsWith(wanted)),
|
tile.kind !== 'node'
|
||||||
|
? tile.kind.startsWith(wanted)
|
||||||
|
: tile.open && (tile.open.slug.startsWith(wanted) || tile.open.name.toLowerCase().startsWith(wanted)),
|
||||||
)
|
)
|
||||||
if (!workspace.tiles[index]) return workspace.say(`E86: no tile ${arg.trim()} — :ls lists them`, 'error')
|
if (!workspace.tiles[index]) return workspace.say(`E86: no tile ${arg.trim()} — :ls lists them`, 'error')
|
||||||
return focusTile(index)
|
return focusTile(index)
|
||||||
@@ -850,7 +856,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 === 'special') {
|
if (onBoard() && !arg) {
|
||||||
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
|
||||||
@@ -878,7 +884,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 === 'special') {
|
if (onBoard()) {
|
||||||
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() }))
|
||||||
}
|
}
|
||||||
@@ -899,7 +905,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 === 'special') {
|
if (onBoard()) {
|
||||||
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()))
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-22
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Where a key is heard. modes.ts stacks them, the topmost first:
|
* Where a key is heard. modes.ts stacks them, the topmost first:
|
||||||
* always > a sheet > the drawer > workspace > writing > the focused tile's
|
* always > a sheet > the drawer > workspace > writing > what has the focus
|
||||||
* kind (tree, board, agenda, calendar) > special > global.
|
* (the tree, or a view: board, agenda, calendar, card) > views > global.
|
||||||
*/
|
*/
|
||||||
export type Context =
|
export type Context =
|
||||||
| 'always'
|
| 'always'
|
||||||
@@ -33,7 +33,7 @@ export type Context =
|
|||||||
| 'board'
|
| 'board'
|
||||||
| 'agenda'
|
| 'agenda'
|
||||||
| 'calendar'
|
| 'calendar'
|
||||||
| 'special'
|
| 'views'
|
||||||
| 'global'
|
| 'global'
|
||||||
| 'editor'
|
| 'editor'
|
||||||
|
|
||||||
@@ -77,8 +77,8 @@ export const bindings = [
|
|||||||
{ context: 'always', keys: ['Alt-h', 'Alt-j', 'Alt-k', 'Alt-l'], action: 'focus', typing: true, label: 'Alt-h/j/k/l', help: 'focus the tile that way, from the editor too — left of the leftmost is the list' },
|
{ context: 'always', keys: ['Alt-h', 'Alt-j', 'Alt-k', 'Alt-l'], action: 'focus', typing: true, label: 'Alt-h/j/k/l', help: 'focus the tile that way, from the editor too — left of the leftmost is the list' },
|
||||||
{ context: 'always', keys: ['Alt-Shift-h', 'Alt-Shift-j', 'Alt-Shift-k', 'Alt-Shift-l'], action: 'swap', typing: true, label: 'Alt-Shift-h/j/k/l', help: 'move the focused tile that way' },
|
{ context: 'always', keys: ['Alt-Shift-h', 'Alt-Shift-j', 'Alt-Shift-k', 'Alt-Shift-l'], action: 'swap', typing: true, label: 'Alt-Shift-h/j/k/l', help: 'move the focused tile that way' },
|
||||||
{ context: 'tree', keys: ['L'], action: 'beside', label: 'L · Shift-Enter', help: 'open the row in a new tile beside the others' },
|
{ context: 'tree', keys: ['L'], action: 'beside', label: 'L · Shift-Enter', help: 'open the row in a new tile beside the others' },
|
||||||
{ context: 'tree', keys: ['gt'], action: 'nextTile', label: 'gt / gT', help: 'next / previous tile — in the editor too' },
|
{ context: 'global', keys: ['gt'], action: 'nextTile', label: 'gt / gT', help: 'next / previous tile — a view too' },
|
||||||
{ context: 'tree', keys: ['gT'], action: 'previousTile' },
|
{ context: 'global', keys: ['gT'], action: 'previousTile' },
|
||||||
{ context: 'always', keys: ['Alt-q'], action: 'close', typing: true, help: 'close the focused tile — :close; :only closes the others' },
|
{ context: 'always', keys: ['Alt-q'], action: 'close', typing: true, help: 'close the focused tile — :close; :only closes the others' },
|
||||||
{ context: 'tree', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k · ↓ ↑', help: 'move down / up the list — scroll the document when it has focus' },
|
{ context: 'tree', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k · ↓ ↑', help: 'move down / up the list — scroll the document when it has focus' },
|
||||||
{ context: 'tree', keys: ['k', 'ArrowUp'], action: 'up' },
|
{ context: 'tree', keys: ['k', 'ArrowUp'], action: 'up' },
|
||||||
@@ -104,16 +104,14 @@ export const bindings = [
|
|||||||
{ context: 'tree', keys: ['p'], action: 'drop' },
|
{ context: 'tree', keys: ['p'], action: 'drop' },
|
||||||
{ context: 'tree', keys: ['M'], action: 'markAll', help: 'pick up the whole folder — again puts it all back' },
|
{ context: 'tree', keys: ['M'], action: 'markAll', help: 'pick up the whole folder — again puts it all back' },
|
||||||
{ context: 'tree', label: 'Ctrl / Shift-click', help: 'pick a row up · every row up to it — drag them onto a folder' },
|
{ context: 'tree', label: 'Ctrl / Shift-click', help: 'pick a row up · every row up to it — drag them onto a folder' },
|
||||||
{ context: 'global', keys: ['S'], action: 'special', help: 'the special workspace: the board, the agenda and the calendar side by side — S again, q or Esc leave it' },
|
{ context: 'global', keys: ['S'], action: 'plan', help: 'the board, the agenda and the calendar, tiled ahead of what is open with the board focused — S again puts every view away' },
|
||||||
{ context: 'special', keys: ['Tab', 'Shift-Tab'], action: 'cycle', label: 'Tab · Alt-h/j/k/l', hint: 'tile', help: 'in the special workspace: the next tile, or the one that way — l past the last list and j past the agenda go on too' },
|
{ context: 'global', keys: ['t'], action: 'agenda', help: 'agenda — late, due this week, in hand, on every board — in a tile of its own' },
|
||||||
{ context: 'global', keys: ['t'], action: 'agenda', help: 'agenda — late, due this week, in hand, on every board — in the special workspace' },
|
|
||||||
{ context: 'global', keys: ['T'], action: 'today', help: "today's diary entry, started if there is none — from templates/day when the diary has one" },
|
{ context: 'global', keys: ['T'], action: 'today', help: "today's diary entry, started if there is none — from templates/day when the diary has one" },
|
||||||
{ context: 'global', keys: ['C'], action: 'calendar', help: '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' },
|
{ context: 'global', keys: ['C'], action: 'calendar', help: 'the diary by month, in a tile: h/l a day, j/k a week, H/L a month, . today, Enter opens or starts the day beside it' },
|
||||||
{ context: 'tree', keys: ['[d'], action: 'previousEntry', label: '[d / ]d', help: 'the diary entry before / after the one open, by date' },
|
{ context: 'tree', keys: ['[d'], action: 'previousEntry', label: '[d / ]d', help: 'the diary entry before / after the one open, by date' },
|
||||||
{ context: 'tree', keys: [']d'], action: 'nextEntry' },
|
{ context: 'tree', keys: [']d'], action: 'nextEntry' },
|
||||||
{ context: 'global', keys: ['Ctrl-p'], action: 'find', label: 'Ctrl-P', help: 'find anything, in every space — Enter opens the result lit beside them, n goes on' },
|
{ context: 'global', keys: ['Ctrl-p'], action: 'find', label: 'Ctrl-P', help: 'find anything, in every space — Enter opens the result lit beside them, n goes on' },
|
||||||
{ context: 'tree', keys: ['b'], action: 'board', help: "the board, in the special workspace: this space's, or the last one shown — 1 … 9 there shows another's" },
|
{ context: 'global', keys: ['b'], action: 'board', help: "the board, in a tile: this space's, or the last one shown — 1 … 9 there shows another's" },
|
||||||
{ context: 'special', keys: ['b'], action: 'board' },
|
|
||||||
{ context: 'tree', keys: ['c'], action: 'capture', help: "capture a card into this space's inbox, without leaving here" },
|
{ context: 'tree', keys: ['c'], action: 'capture', help: "capture a card into this space's inbox, without leaving here" },
|
||||||
{ context: 'tree', keys: ['q', 'Escape'], action: 'close', label: 'Esc · q', help: 'close the document, and its tile when there are several' },
|
{ context: 'tree', keys: ['q', 'Escape'], action: 'close', label: 'Esc · q', help: 'close the document, and its tile when there are several' },
|
||||||
{ context: 'tree', keys: ['/'], action: 'search', help: 'full-text search, inside the current space and its text files — Tab or Ctrl-N/P walk the results, Enter opens one' },
|
{ context: 'tree', keys: ['/'], action: 'search', help: 'full-text search, inside the current space and its text files — Tab or Ctrl-N/P walk the results, Enter opens one' },
|
||||||
@@ -136,11 +134,11 @@ export const bindings = [
|
|||||||
// hands the focus back to it instead of walking the list unseen.
|
// hands the focus back to it instead of walking the list unseen.
|
||||||
{ context: 'writing', keys: ['*'], action: 'refocus' },
|
{ context: 'writing', keys: ['*'], action: 'refocus' },
|
||||||
|
|
||||||
// --- the special workspace -------------------------------------------------
|
// --- what the views share: the board, the agenda, the calendar, a card's back
|
||||||
{ context: 'special', keys: ['Escape'], action: 'leave', caps: ['esc'], hint: 'leave' },
|
{ context: 'views', keys: ['q', 'Escape'], action: 'close', label: 'q · Esc', caps: ['q'], hint: 'close', help: "in a view — the board, the agenda, the calendar, a card's back — close its tile; the focus goes back to the tile before" },
|
||||||
|
|
||||||
// --- on a board ---------------------------------------------------------------
|
// --- on a board ---------------------------------------------------------------
|
||||||
{ context: 'board', keys: ['h', 'ArrowLeft'], action: 'left', label: 'h / l', caps: ['h', 'j', 'k', 'l'], hint: 'move', help: 'to the list left / right — right of the last is the agenda' },
|
{ context: 'board', keys: ['h', 'ArrowLeft'], action: 'left', label: 'h / l', caps: ['h', 'j', 'k', 'l'], hint: 'move', help: 'to the list left / right — right of the last, to the tile beside' },
|
||||||
{ context: 'board', keys: ['l', 'ArrowRight'], action: 'right' },
|
{ context: 'board', keys: ['l', 'ArrowRight'], action: 'right' },
|
||||||
{ context: 'board', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', help: 'down / up the cards' },
|
{ context: 'board', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', help: 'down / up the cards' },
|
||||||
{ context: 'board', keys: ['k', 'ArrowUp'], action: 'up' },
|
{ context: 'board', keys: ['k', 'ArrowUp'], action: 'up' },
|
||||||
@@ -151,7 +149,7 @@ export const bindings = [
|
|||||||
{ context: 'board', keys: ['H'], action: 'sendLeft', label: 'H / L', caps: ['H', 'L'], hint: 'send', help: 'move the card to the list left / right' },
|
{ context: 'board', keys: ['H'], action: 'sendLeft', label: 'H / L', caps: ['H', 'L'], hint: 'send', help: 'move the card to the list left / right' },
|
||||||
{ context: 'board', keys: ['L'], action: 'sendRight' },
|
{ context: 'board', keys: ['L'], action: 'sendRight' },
|
||||||
{ context: 'board', keys: ['Space'], action: 'done', hint: 'done', help: 'into the done list, or back out to the inbox' },
|
{ context: 'board', keys: ['Space'], action: 'done', hint: 'done', help: 'into the done list, or back out to the inbox' },
|
||||||
{ context: 'board', keys: ['Enter'], action: 'turn', hint: 'turn over', help: 'turn the card over: its labels, description and checklist' },
|
{ context: 'board', keys: ['Enter'], action: 'turn', hint: 'turn over', help: 'turn the card over, in a tile beside: its labels, description and checklist' },
|
||||||
{ context: 'board', keys: ['gf'], action: 'notes', help: "the card's notes — a document, made on first use; q comes back" },
|
{ context: 'board', keys: ['gf'], action: 'notes', help: "the card's notes — a document, made on first use; q comes back" },
|
||||||
{ context: 'board', keys: ['o', 'c'], action: 'new', label: 'o · c', caps: ['o'], hint: 'new', help: 'new card on this list' },
|
{ context: 'board', keys: ['o', 'c'], action: 'new', label: 'o · c', caps: ['o'], hint: 'new', help: 'new card on this list' },
|
||||||
{ context: 'board', keys: ['D'], action: 'due', hint: 'due', help: 'due date: fri, tm, +3d, 10-31 …' },
|
{ context: 'board', keys: ['D'], action: 'due', hint: 'due', help: 'due date: fri, tm, +3d, 10-31 …' },
|
||||||
@@ -161,7 +159,6 @@ export const bindings = [
|
|||||||
{ context: 'board', keys: ['r'], action: 'reload', help: 'read the board again, for changes made elsewhere' },
|
{ context: 'board', keys: ['r'], action: 'reload', help: 'read the board again, for changes made elsewhere' },
|
||||||
{ context: 'board', keys: ['x', 'dd'], action: 'delete', caps: ['dd'], hint: 'delete', help: 'delete the card (asks y/N)' },
|
{ context: 'board', keys: ['x', 'dd'], action: 'delete', caps: ['dd'], hint: 'delete', help: 'delete the card (asks y/N)' },
|
||||||
{ context: 'board', caps: ['1…9'], hint: 'board' },
|
{ context: 'board', caps: ['1…9'], hint: 'board' },
|
||||||
{ context: 'board', keys: ['q'], action: 'leave', label: 'q · Esc', help: 'leave the special workspace' },
|
|
||||||
|
|
||||||
// --- the back of a card ---------------------------------------------------
|
// --- the back of a card ---------------------------------------------------
|
||||||
{ context: 'card', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', caps: ['j', 'k'], hint: 'step', help: 'down / up the checklist' },
|
{ context: 'card', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', caps: ['j', 'k'], hint: 'step', help: 'down / up the checklist' },
|
||||||
@@ -182,7 +179,6 @@ export const bindings = [
|
|||||||
{ context: 'card', keys: ['L'], action: 'sendRight' },
|
{ context: 'card', keys: ['L'], action: 'sendRight' },
|
||||||
{ context: 'card', keys: ['gf'], action: 'notes', hint: 'notes', help: "the card's notes — a document, made on first use; q comes back" },
|
{ context: 'card', keys: ['gf'], action: 'notes', hint: 'notes', help: "the card's notes — a document, made on first use; q comes back" },
|
||||||
{ context: 'card', keys: [':'], action: 'line' },
|
{ context: 'card', keys: [':'], action: 'line' },
|
||||||
{ context: 'card', keys: ['q', 'Escape'], action: 'close', label: 'q · Esc', caps: ['q'], hint: 'close', help: 'back to the board' },
|
|
||||||
|
|
||||||
// --- the scratchpad, while it is down --------------------------------------------
|
// --- the scratchpad, while it is down --------------------------------------------
|
||||||
{ context: 'drawer', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', caps: ['j', 'k'], hint: 'note', help: 'down / up the notes, the one touched last on top' },
|
{ context: 'drawer', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', caps: ['j', 'k'], hint: 'note', help: 'down / up the notes, the one touched last on top' },
|
||||||
@@ -200,9 +196,9 @@ export const bindings = [
|
|||||||
{ context: 'drawer', keys: ['*'], action: 'nothing' },
|
{ context: 'drawer', keys: ['*'], action: 'nothing' },
|
||||||
|
|
||||||
// --- the agenda --------------------------------------------------------------
|
// --- the agenda --------------------------------------------------------------
|
||||||
{ context: 'agenda', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', caps: ['j', 'k'], hint: 'move', help: 'down / up the cards, then what was written on this day in earlier years, then into the calendar' },
|
{ context: 'agenda', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k', caps: ['j', 'k'], hint: 'move', help: 'down / up the cards, then what was written on this day in earlier years, then on to the tile below' },
|
||||||
{ context: 'agenda', keys: ['k', 'ArrowUp'], action: 'up' },
|
{ context: 'agenda', keys: ['k', 'ArrowUp'], action: 'up' },
|
||||||
{ context: 'agenda', keys: ['h', 'ArrowLeft'], action: 'left', label: 'h', help: 'to the board' },
|
{ context: 'agenda', keys: ['h', 'ArrowLeft'], action: 'left', label: 'h', help: 'to the tile on the left' },
|
||||||
{ context: 'agenda', keys: ['Enter'], action: 'open', hint: 'open', help: 'to the card, on its own board beside — or to the entry' },
|
{ context: 'agenda', keys: ['Enter'], action: 'open', hint: 'open', help: 'to the card, on its own board beside — or to the entry' },
|
||||||
{ context: 'agenda', keys: ['Space'], action: 'finish', hint: 'done', help: 'done, where it stands' },
|
{ context: 'agenda', keys: ['Space'], action: 'finish', hint: 'done', help: 'done, where it stands' },
|
||||||
{ context: 'agenda', keys: ['D'], action: 'due', hint: 'due', help: 'due date: fri, tm, +3d … — :due on the line' },
|
{ context: 'agenda', keys: ['D'], action: 'due', hint: 'due', help: 'due date: fri, tm, +3d … — :due on the line' },
|
||||||
@@ -211,7 +207,6 @@ export const bindings = [
|
|||||||
{ context: 'agenda', keys: ['H'], action: 'sendLeft', label: 'H / L', caps: ['H', 'L'], hint: 'list', help: 'to the list left / right on its board' },
|
{ context: 'agenda', keys: ['H'], action: 'sendLeft', label: 'H / L', caps: ['H', 'L'], hint: 'list', help: 'to the list left / right on its board' },
|
||||||
{ context: 'agenda', keys: ['L'], action: 'sendRight' },
|
{ context: 'agenda', keys: ['L'], action: 'sendRight' },
|
||||||
{ context: 'agenda', keys: ['T'], action: 'today', hint: 'diary', help: "today's diary entry" },
|
{ context: 'agenda', keys: ['T'], action: 'today', hint: 'diary', help: "today's diary entry" },
|
||||||
{ context: 'agenda', keys: ['q'], action: 'leave', label: 'q · Esc', help: 'leave the special workspace — a document opened from it comes back here on q' },
|
|
||||||
|
|
||||||
// --- the calendar ------------------------------------------------------------
|
// --- the calendar ------------------------------------------------------------
|
||||||
{ context: 'calendar', keys: ['h', 'ArrowLeft', 'l', 'ArrowRight'], action: 'day', caps: ['h', 'l'], hint: 'day' },
|
{ context: 'calendar', keys: ['h', 'ArrowLeft', 'l', 'ArrowRight'], action: 'day', caps: ['h', 'l'], hint: 'day' },
|
||||||
@@ -219,7 +214,6 @@ export const bindings = [
|
|||||||
{ context: 'calendar', keys: ['H', 'L'], action: 'month', hint: 'month' },
|
{ context: 'calendar', keys: ['H', 'L'], action: 'month', hint: 'month' },
|
||||||
{ context: 'calendar', keys: ['.'], action: 'today', hint: 'today' },
|
{ context: 'calendar', keys: ['.'], action: 'today', hint: 'today' },
|
||||||
{ context: 'calendar', keys: ['Enter'], action: 'open', hint: 'open' },
|
{ context: 'calendar', keys: ['Enter'], action: 'open', hint: 'open' },
|
||||||
{ context: 'calendar', keys: ['q'], action: 'leave' },
|
|
||||||
|
|
||||||
// --- the sheets over everything --------------------------------------------------
|
// --- the sheets over everything --------------------------------------------------
|
||||||
{ context: 'sheet', keys: ['Escape'], action: 'close', typing: true, caps: ['q', 'esc'], hint: 'close' },
|
{ context: 'sheet', keys: ['Escape'], action: 'close', typing: true, caps: ['q', 'esc'], hint: 'close' },
|
||||||
@@ -286,7 +280,7 @@ export const bindings = [
|
|||||||
|
|
||||||
/** The help's key columns, in order, and the context each draws from. */
|
/** The help's key columns, in order, and the context each draws from. */
|
||||||
export const sections: readonly { section: Section; title: string; contexts: readonly Context[]; rule?: string }[] = [
|
export const sections: readonly { section: Section; title: string; contexts: readonly Context[]; rule?: string }[] = [
|
||||||
{ section: 'keys', title: 'keys', contexts: ['always', 'workspace', 'global', 'tree', 'special'] },
|
{ section: 'keys', title: 'keys', contexts: ['always', 'workspace', 'global', 'tree', 'views'] },
|
||||||
{ section: 'board', title: 'board keys', contexts: ['board'], rule: 'the first list is the inbox, the last list is done' },
|
{ section: 'board', title: 'board keys', contexts: ['board'], rule: 'the first list is the inbox, the last list is done' },
|
||||||
{ section: 'card', title: 'card keys', contexts: ['card'] },
|
{ section: 'card', title: 'card keys', contexts: ['card'] },
|
||||||
{ section: 'scratch', title: 'scratchpad keys', contexts: ['drawer'] },
|
{ section: 'scratch', title: 'scratchpad keys', contexts: ['drawer'] },
|
||||||
|
|||||||
+13
-9
@@ -4,12 +4,13 @@
|
|||||||
* a context takes it.
|
* a context takes it.
|
||||||
*
|
*
|
||||||
* always Ctrl-S, Alt-h/j/k/l — heard in the editor too
|
* always Ctrl-S, Alt-h/j/k/l — heard in the editor too
|
||||||
* a sheet help, info, trash, the card's back …; nothing under one hears a key
|
* a sheet help, info, trash …; nothing under one hears a key
|
||||||
* drawer the scratchpad, while it is down
|
* drawer the scratchpad, while it is down
|
||||||
* workspace Esc out of fullscreen, Q
|
* workspace Esc out of fullscreen, Q
|
||||||
* writing a document being written takes every plain key back
|
* writing a document being written takes every plain key back
|
||||||
* tile the focused kind: tree, board, agenda or calendar
|
* tile what has the focus: the tree, or a view — board, agenda,
|
||||||
* special what the three tiles of the special workspace share
|
* calendar, a card's back
|
||||||
|
* views what the views share: q, gt, b
|
||||||
* global spaces, t, T, C, S, :, ?
|
* global spaces, t, T, C, S, :, ?
|
||||||
*
|
*
|
||||||
* The `[y/N]` prompt sits over all of it, in App.svelte, and the command
|
* The `[y/N]` prompt sits over all of it, in App.svelte, and the command
|
||||||
@@ -20,7 +21,7 @@ import { bindings, type Binding, type Context } from './keymap'
|
|||||||
import { workspace } from './store.svelte'
|
import { workspace } from './store.svelte'
|
||||||
|
|
||||||
/** The sheets with keys of their own, under the same name as the sheet. */
|
/** The sheets with keys of their own, under the same name as the sheet. */
|
||||||
const sheets = new Set<string>(['help', 'overview', 'media', 'trash', 'card'])
|
const sheets = new Set<string>(['help', 'overview', 'media', 'trash'])
|
||||||
|
|
||||||
/** The stack, the topmost first. */
|
/** The stack, the topmost first. */
|
||||||
export function stack(): Context[] {
|
export function stack(): Context[] {
|
||||||
@@ -29,16 +30,19 @@ export function stack(): Context[] {
|
|||||||
if (overlay !== 'none') {
|
if (overlay !== 'none') {
|
||||||
// The info sheet hears the keys of the part on show, then its own Tab.
|
// The info sheet hears the keys of the part on show, then its own Tab.
|
||||||
if (overlay === 'info') modes.push(workspace.infoSection, 'info')
|
if (overlay === 'info') modes.push(workspace.infoSection, 'info')
|
||||||
// While the description is written, the card's back has no keys of its own.
|
else if (sheets.has(overlay)) modes.push(overlay as Context)
|
||||||
else if (sheets.has(overlay) && !(overlay === 'card' && workspace.describing)) modes.push(overlay as Context)
|
|
||||||
return [...modes, 'sheet']
|
return [...modes, 'sheet']
|
||||||
}
|
}
|
||||||
if (workspace.scratchOpen && !workspace.scratchEditing) modes.push('drawer')
|
if (workspace.scratchOpen && !workspace.scratchEditing) modes.push('drawer')
|
||||||
modes.push('workspace')
|
modes.push('workspace')
|
||||||
if (workspace.view === 'special') modes.push(workspace.specialFocus, 'special')
|
const focus = workspace.focus
|
||||||
else {
|
if (focus === 'list' || focus === 'node') {
|
||||||
if (workspace.editing && workspace.pane === 'document') modes.push('writing')
|
if (workspace.editing && focus === 'node') modes.push('writing')
|
||||||
modes.push('tree')
|
modes.push('tree')
|
||||||
|
} else {
|
||||||
|
// While the description is written, the card's back has no keys of its own.
|
||||||
|
if (!(focus === 'card' && workspace.describing)) modes.push(focus)
|
||||||
|
modes.push('views')
|
||||||
}
|
}
|
||||||
modes.push('global')
|
modes.push('global')
|
||||||
return modes
|
return modes
|
||||||
|
|||||||
+225
-170
@@ -26,22 +26,23 @@ import { formatBytes, formatDay, formatStamp, formatWeekday, isTouchDevice, pars
|
|||||||
|
|
||||||
export type Pane = 'list' | 'document'
|
export type Pane = 'list' | 'document'
|
||||||
/**
|
/**
|
||||||
* The tree of documents, or the special workspace: the board of the space
|
* What a tile shows: a node of the tree — a document or a file — or one of
|
||||||
* being browsed, the agenda and the calendar, tiled side by side and a key
|
* the views over the boards and the diary, which are tiled beside the
|
||||||
* away from anywhere, like the desktop's scratchpad.
|
* documents like any other window.
|
||||||
*/
|
*/
|
||||||
export type View = 'tree' | 'special'
|
export type TileKind = 'node' | 'board' | 'agenda' | 'calendar' | 'card'
|
||||||
/** The tile of the special workspace that has focus. */
|
export type View = Exclude<TileKind, 'node'>
|
||||||
export type SpecialTile = 'board' | 'agenda' | 'calendar'
|
|
||||||
/** What floats over the tiles; one at a time. */
|
/** What floats over the tiles; one at a time. */
|
||||||
export type Sheet = 'help' | 'media' | 'overview' | 'actions' | 'verify' | 'info' | 'trash' | 'card'
|
export type Sheet = 'help' | 'media' | 'overview' | 'actions' | 'verify' | 'info' | 'trash'
|
||||||
/** The info sheet's parts: what the open document holds, and what is around it. */
|
/** The info sheet's parts: what the open document holds, and what is around it. */
|
||||||
export type InfoSection = 'outline' | 'history' | 'backlinks' | 'share'
|
export type InfoSection = 'outline' | 'history' | 'backlinks' | 'share'
|
||||||
export type Flash = { text: string; kind: 'info' | 'error' } | null
|
export type Flash = { text: string; kind: 'info' | 'error' } | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where a space was left: the folder, the open documents, the row, the pane.
|
* Where a space was left: the folder, the tiles, the row, the pane. A tile
|
||||||
* `openId` is the focused tile's, all a spot held before there were tiles.
|
* is the id of the node it shows, or the name of the view. `openId` is the
|
||||||
|
* focused tile's, all a spot held before there were tiles, and `open` the
|
||||||
|
* documents alone, all it held before there were views.
|
||||||
*/
|
*/
|
||||||
type Spot = {
|
type Spot = {
|
||||||
parentId: number
|
parentId: number
|
||||||
@@ -49,6 +50,7 @@ type Spot = {
|
|||||||
cursor: number
|
cursor: number
|
||||||
pane: Pane
|
pane: Pane
|
||||||
open?: number[]
|
open?: number[]
|
||||||
|
tiles?: (number | View)[]
|
||||||
focused?: number
|
focused?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,12 +65,15 @@ function doneSaid(moved: Moved): string {
|
|||||||
let tiles = 0
|
let tiles = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One document on screen, and everything that belongs to it rather than to
|
* One window on screen, and everything that belongs to it rather than to
|
||||||
* the workspace: what it shows, the draft being written, whether that is
|
* the workspace: what it shows, the draft being written, whether that is
|
||||||
* saved, and the timer that will save it.
|
* saved, and the timer that will save it. A view keeps its state in the
|
||||||
|
* workspace — there is one board, one agenda, one calendar — so its tile
|
||||||
|
* holds only its kind.
|
||||||
*/
|
*/
|
||||||
export class Tile {
|
export class Tile {
|
||||||
readonly id = ++tiles
|
readonly id = ++tiles
|
||||||
|
kind = $state<TileKind>('node')
|
||||||
open = $state<WorkspaceNode | null>(null)
|
open = $state<WorkspaceNode | null>(null)
|
||||||
draft = $state<Draft>({ name: '', body: '', created_at: 0 })
|
draft = $state<Draft>({ name: '', body: '', created_at: 0 })
|
||||||
editing = $state(false)
|
editing = $state(false)
|
||||||
@@ -77,12 +82,6 @@ export class Tile {
|
|||||||
dirty = $state(false)
|
dirty = $state(false)
|
||||||
/** The editor's vim mode, for the status line while this tile has focus. */
|
/** The editor's vim mode, for the status line while this tile has focus. */
|
||||||
vimMode = $state('normal')
|
vimMode = $state('normal')
|
||||||
/*
|
|
||||||
* Set when a card's document was opened from the board, so closing the
|
|
||||||
* document goes back to the board — on the same card — rather than leaving
|
|
||||||
* you in a tree you never asked for.
|
|
||||||
*/
|
|
||||||
returnTo = $state<'special' | null>(null)
|
|
||||||
/*
|
/*
|
||||||
* The document `o` just made. If it is left with neither a name nor a word
|
* The document `o` just made. If it is left with neither a name nor a word
|
||||||
* in it, it is removed on the way out rather than left as an empty row.
|
* in it, it is removed on the way out rather than left as an empty row.
|
||||||
@@ -98,9 +97,19 @@ export class Tile {
|
|||||||
this.autosave = null
|
this.autosave = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Put the tile back to showing nothing. Where it goes back to stays. */
|
constructor(kind: TileKind = 'node') {
|
||||||
|
this.kind = kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Showing nothing: a node tile with no node in it. */
|
||||||
|
get empty(): boolean {
|
||||||
|
return this.kind === 'node' && !this.open
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Put the tile back to showing nothing. */
|
||||||
clear() {
|
clear() {
|
||||||
this.stopAutosave()
|
this.stopAutosave()
|
||||||
|
this.kind = 'node'
|
||||||
this.open = null
|
this.open = null
|
||||||
this.editing = false
|
this.editing = false
|
||||||
this.dirty = false
|
this.dirty = false
|
||||||
@@ -173,12 +182,6 @@ class Workspace {
|
|||||||
set dirty(dirty: boolean) {
|
set dirty(dirty: boolean) {
|
||||||
this.tile.dirty = dirty
|
this.tile.dirty = dirty
|
||||||
}
|
}
|
||||||
get returnTo() {
|
|
||||||
return this.tile.returnTo
|
|
||||||
}
|
|
||||||
set returnTo(to: 'special' | null) {
|
|
||||||
this.tile.returnTo = to
|
|
||||||
}
|
|
||||||
get vimMode() {
|
get vimMode() {
|
||||||
return this.tile.vimMode
|
return this.tile.vimMode
|
||||||
}
|
}
|
||||||
@@ -191,6 +194,23 @@ class Workspace {
|
|||||||
return this.tiles.filter((tile) => tile.open?.id === id)
|
return this.tiles.filter((tile) => tile.open?.id === id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The tiles focused before the one focused now, the last the latest:
|
||||||
|
* closing a tile goes back to the one that had the focus before it — to
|
||||||
|
* the board a card's notes were opened from, to the calendar a day was.
|
||||||
|
*/
|
||||||
|
#trail: Tile[] = []
|
||||||
|
|
||||||
|
/** What has the keys: the list, or the kind of the focused tile. */
|
||||||
|
get focus(): 'list' | TileKind {
|
||||||
|
return this.pane === 'list' || this.tile.empty ? 'list' : this.tile.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The tile showing `kind`, if one is. */
|
||||||
|
view(kind: View): Tile | undefined {
|
||||||
|
return this.tiles.find((tile) => tile.kind === kind)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus a tile, and follow it in the list and the address bar, as opening
|
* Focus a tile, and follow it in the list and the address bar, as opening
|
||||||
* its document did.
|
* its document did.
|
||||||
@@ -198,34 +218,48 @@ class Workspace {
|
|||||||
focusTile(index: number) {
|
focusTile(index: number) {
|
||||||
const tile = this.tiles[index]
|
const tile = this.tiles[index]
|
||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
if (tile !== this.tile) {
|
||||||
|
this.#leaveCard()
|
||||||
|
this.#trail = [...this.#trail.filter((t) => t !== tile && t !== this.tile), this.tile].slice(-20)
|
||||||
|
}
|
||||||
this.focused = index
|
this.focused = index
|
||||||
if (!tile.open) return
|
if (tile.empty) return
|
||||||
this.pane = 'document'
|
this.pane = 'document'
|
||||||
|
if (!tile.open) return
|
||||||
const row = this.nodes.findIndex((n) => n.id === tile.open?.id)
|
const row = this.nodes.findIndex((n) => n.id === tile.open?.id)
|
||||||
if (row >= 0) this.cursor = row
|
if (row >= 0) this.cursor = row
|
||||||
this.#syncUrl(tile.open.path)
|
this.#syncUrl(tile.open.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A tile of its own for what opens next, beside what is on screen. */
|
||||||
|
#addTile(tile = new Tile()) {
|
||||||
|
this.tiles.push(tile)
|
||||||
|
this.focusTile(this.tiles.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tiles with nothing in them go, as long as one is left: a closed document
|
* Tiles with nothing in them go, as long as one is left: a closed document
|
||||||
* closes its tile, as `:q` closes a window in vim.
|
* closes its tile, as `:q` closes a window in vim. When the focused one
|
||||||
|
* went, the focus goes back along the trail.
|
||||||
*/
|
*/
|
||||||
prune() {
|
prune() {
|
||||||
const current = this.tile
|
const current = this.tile
|
||||||
if (this.tiles.length > 1) {
|
if (this.tiles.length > 1) {
|
||||||
const kept = this.tiles.filter((tile) => tile.open)
|
const kept = this.tiles.filter((tile) => !tile.empty)
|
||||||
this.tiles = kept.length > 0 ? kept : [current]
|
this.tiles = kept.length > 0 ? kept : [current]
|
||||||
const at = this.tiles.indexOf(current)
|
const back = this.tiles.includes(current) ? current : [...this.#trail].reverse().find((tile) => this.tiles.includes(tile))
|
||||||
this.focused = at >= 0 ? at : Math.min(this.focused, this.tiles.length - 1)
|
this.focused = back ? this.tiles.indexOf(back) : Math.min(this.focused, this.tiles.length - 1)
|
||||||
}
|
}
|
||||||
if (!this.open) this.pane = 'list'
|
this.#trail = this.#trail.filter((tile) => this.tiles.includes(tile) && tile !== this.tile)
|
||||||
|
if (this.tile.empty) this.pane = 'list'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `:close`: the focused tile goes, or with only one, its document. */
|
/** `:close`: the focused tile goes, or with only one, what it shows. */
|
||||||
closeTile() {
|
closeTile() {
|
||||||
|
if (this.tile.kind === 'card' && this.describing && !this.stopDescribing()) return
|
||||||
this.tile.clear()
|
this.tile.clear()
|
||||||
this.prune()
|
this.prune()
|
||||||
if (this.open) this.focusTile(this.focused)
|
if (!this.tile.empty) this.focusTile(this.focused)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,10 +272,7 @@ class Workspace {
|
|||||||
if (isContainer(selected)) return this.say(`${selected.slug} is a ${selected.kind} — l goes into it`, 'error')
|
if (isContainer(selected)) return this.say(`${selected.slug} is a ${selected.kind} — l goes into it`, 'error')
|
||||||
const shown = this.tiles.findIndex((tile) => tile.open?.id === selected.id)
|
const shown = this.tiles.findIndex((tile) => tile.open?.id === selected.id)
|
||||||
if (shown >= 0) return this.focusTile(shown)
|
if (shown >= 0) return this.focusTile(shown)
|
||||||
if (this.open) {
|
if (!this.tile.empty) this.#addTile()
|
||||||
this.tiles.push(new Tile())
|
|
||||||
this.focused = this.tiles.length - 1
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await this.openSelected()
|
await this.openSelected()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -261,11 +292,10 @@ class Workspace {
|
|||||||
for (const tile of others) tile.stopAutosave()
|
for (const tile of others) tile.stopAutosave()
|
||||||
this.tiles = [this.tile]
|
this.tiles = [this.tile]
|
||||||
this.focused = 0
|
this.focused = 0
|
||||||
|
this.#trail = []
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The board of the current space, loaded only while it is being looked at. */
|
/** The board as the server sent it, loaded only while it is being looked at. */
|
||||||
view = $state<View>('tree')
|
|
||||||
/** The board as the server sent it. */
|
|
||||||
rawBoard = $state<Board | null>(null)
|
rawBoard = $state<Board | null>(null)
|
||||||
/*
|
/*
|
||||||
* The board as it is walked and drawn: the done list keeps only what was
|
* The board as it is walked and drawn: the done list keeps only what was
|
||||||
@@ -329,10 +359,8 @@ class Workspace {
|
|||||||
overlay = $state<'none' | Sheet>('none')
|
overlay = $state<'none' | Sheet>('none')
|
||||||
/** The part of the info sheet on show; it stays for the next time it opens. */
|
/** The part of the info sheet on show; it stays for the next time it opens. */
|
||||||
infoSection = $state<InfoSection>('outline')
|
infoSection = $state<InfoSection>('outline')
|
||||||
/** Which of the special workspace's tiles the keys go to. */
|
|
||||||
specialFocus = $state<SpecialTile>('board')
|
|
||||||
/**
|
/**
|
||||||
* The space whose board the special workspace shows: the one being browsed
|
* The space whose board the board's tile shows: the one being browsed
|
||||||
* when it has a board, or else the last one shown, so that coming from the
|
* when it has a board, or else the last one shown, so that coming from the
|
||||||
* diary does not leave the board's tile empty.
|
* diary does not leave the board's tile empty.
|
||||||
*/
|
*/
|
||||||
@@ -794,33 +822,49 @@ class Workspace {
|
|||||||
if (start) await this.enterSpace(start.id)
|
if (start) await this.enterSpace(start.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#rememberSpot() {
|
/** `views` false: the views are leaving with the focus, and are not left behind. */
|
||||||
|
#rememberSpot(views = true) {
|
||||||
if (this.spaceId === null || this.parentId === null) return
|
if (this.spaceId === null || this.parentId === null) return
|
||||||
const open = this.tiles.flatMap((tile) => (tile.open ? [tile.open.id] : []))
|
// A card's back is the card under the board's cursor, which is not kept.
|
||||||
|
const kept = this.tiles.filter((tile) => !tile.empty && (tile.kind === 'node' || (views && tile.kind !== 'card')))
|
||||||
this.#spots[this.spaceId] = {
|
this.#spots[this.spaceId] = {
|
||||||
parentId: this.parentId,
|
parentId: this.parentId,
|
||||||
openId: this.open?.id ?? null,
|
openId: this.open?.id ?? null,
|
||||||
cursor: this.cursor,
|
cursor: this.cursor,
|
||||||
pane: this.pane,
|
pane: this.pane,
|
||||||
open,
|
tiles: kept.map((tile) => (tile.kind === 'node' ? tile.open!.id : tile.kind)),
|
||||||
focused: Math.max(open.indexOf(this.open?.id ?? -1), 0),
|
focused: Math.max(kept.indexOf(this.tile), 0),
|
||||||
}
|
}
|
||||||
localStorage.setItem('diary:spots', JSON.stringify(this.#spots))
|
localStorage.setItem('diary:spots', JSON.stringify(this.#spots))
|
||||||
}
|
}
|
||||||
|
|
||||||
async enterSpace(id: number) {
|
async enterSpace(id: number) {
|
||||||
const leaving = this.spaceId !== null && this.spaceId !== id
|
const leaving = this.spaceId !== null && this.spaceId !== id
|
||||||
|
/*
|
||||||
|
* The views look at every board and the whole diary, so with one of them
|
||||||
|
* focused they come along — moved, not copied — as the special workspace
|
||||||
|
* once did: a digit on the board shows that space's board, and the
|
||||||
|
* agenda stays beside it.
|
||||||
|
*/
|
||||||
|
let carried: Tile[] = []
|
||||||
|
let carriedFocus: Tile | undefined
|
||||||
if (leaving) {
|
if (leaving) {
|
||||||
await this.flush()
|
await this.flush()
|
||||||
this.#rememberSpot()
|
const carry = this.focus !== 'list' && this.focus !== 'node'
|
||||||
|
this.#rememberSpot(!carry)
|
||||||
const from = this.spaces.findIndex((s) => s.id === this.spaceId)
|
const from = this.spaces.findIndex((s) => s.id === this.spaceId)
|
||||||
const to = this.spaces.findIndex((s) => s.id === id)
|
const to = this.spaces.findIndex((s) => s.id === id)
|
||||||
this.switchDirection = to >= from ? 1 : -1
|
this.switchDirection = to >= from ? 1 : -1
|
||||||
|
if (carry) {
|
||||||
|
carried = this.tiles.filter((tile) => tile.kind !== 'node' && tile.kind !== 'card')
|
||||||
|
carriedFocus = this.tile.kind === 'card' ? (this.view('board') ?? carried[0]) : this.tile
|
||||||
|
}
|
||||||
// Each space has tiles of its own; these come back with this one.
|
// Each space has tiles of its own; these come back with this one.
|
||||||
for (const tile of this.tiles) tile.stopAutosave()
|
for (const tile of this.tiles) tile.stopAutosave()
|
||||||
this.tiles = [new Tile()]
|
this.tiles = carried.length > 0 ? carried : [new Tile()]
|
||||||
this.focused = 0
|
this.focused = carriedFocus ? this.tiles.indexOf(carriedFocus) : 0
|
||||||
this.pane = 'list'
|
this.#trail = []
|
||||||
|
this.pane = carried.length > 0 ? 'document' : 'list'
|
||||||
}
|
}
|
||||||
this.spaceId = id
|
this.spaceId = id
|
||||||
localStorage.setItem('diary:space', String(id))
|
localStorage.setItem('diary:space', String(id))
|
||||||
@@ -836,44 +880,49 @@ class Workspace {
|
|||||||
await this.goTo(id)
|
await this.goTo(id)
|
||||||
}
|
}
|
||||||
if (spot && this.parentId === spot.parentId) {
|
if (spot && this.parentId === spot.parentId) {
|
||||||
const ids = spot.open ?? (spot.openId !== null && this.nodes.some((n) => n.id === spot.openId) ? [spot.openId] : [])
|
const entries =
|
||||||
let opened = 0
|
spot.tiles ?? spot.open ?? (spot.openId !== null && this.nodes.some((n) => n.id === spot.openId) ? [spot.openId] : [])
|
||||||
for (const open of ids) {
|
const restored: Tile[] = []
|
||||||
if (opened > 0) {
|
for (const entry of entries) {
|
||||||
|
if (typeof entry === 'string') {
|
||||||
|
let tile = this.view(entry)
|
||||||
|
if (!tile) this.tiles.push((tile = new Tile(entry)))
|
||||||
|
restored.push(tile)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const empty = this.tiles.findIndex((tile) => tile.empty)
|
||||||
|
if (empty >= 0) this.focused = empty
|
||||||
|
else {
|
||||||
this.tiles.push(new Tile())
|
this.tiles.push(new Tile())
|
||||||
this.focused = this.tiles.length - 1
|
this.focused = this.tiles.length - 1
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Reopened under the board, if that is what is on screen.
|
await this.openNode(entry)
|
||||||
await this.openNode(open, false, true)
|
restored.push(this.tile)
|
||||||
opened++
|
|
||||||
} catch {
|
} catch {
|
||||||
/* deleted or moved away since: one tile fewer */
|
/* deleted or moved away since: one tile fewer */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.prune()
|
this.prune()
|
||||||
if (opened > 0) {
|
const back = carriedFocus ?? restored[Math.min(spot.focused ?? 0, restored.length - 1)]
|
||||||
this.focusTile(Math.min(spot.focused ?? 0, this.tiles.length - 1))
|
if (back && this.tiles.includes(back)) {
|
||||||
this.pane = spot.pane
|
this.focusTile(this.tiles.indexOf(back))
|
||||||
|
if (!carriedFocus) this.pane = spot.pane
|
||||||
} else {
|
} else {
|
||||||
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Switching space with the special workspace up shows the new space's
|
this.#trail = []
|
||||||
// board; a space without one leaves the last board where it is.
|
await this.#loadViews()
|
||||||
if (this.view === 'special' && this.space?.has_board) {
|
|
||||||
this.listCursor = 0
|
|
||||||
this.cardCursor = 0
|
|
||||||
await this.reloadBoard(id, undefined)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Show the children of `id`, which must be a space or a folder. */
|
/** Show the children of `id`, which must be a space or a folder. */
|
||||||
async goTo(id: number, keep?: number) {
|
async goTo(id: number, keep?: number) {
|
||||||
await this.flush()
|
await this.flush()
|
||||||
const folder = await api.getNode(id)
|
const folder = await api.getNode(id)
|
||||||
// Walking into another space by a link leaves this one where it was.
|
// Walking into another space by a link leaves this one where it was; the
|
||||||
if (this.spaceId !== null && folder.path[0]?.id !== this.spaceId) this.#rememberSpot()
|
// views stay on screen, so they go along rather than stay behind too.
|
||||||
|
if (this.spaceId !== null && folder.path[0]?.id !== this.spaceId) this.#rememberSpot(false)
|
||||||
this.parentId = id
|
this.parentId = id
|
||||||
this.parentSealed = folder.sealed
|
this.parentSealed = folder.sealed
|
||||||
this.query = ''
|
this.query = ''
|
||||||
@@ -905,8 +954,6 @@ 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.
|
|
||||||
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
|
||||||
@@ -936,19 +983,10 @@ class Workspace {
|
|||||||
this.cursor = Math.min(Math.max(this.cursor + delta, 0), this.nodes.length - 1)
|
this.cursor = Math.min(Math.max(this.cursor + delta, 0), this.nodes.length - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
async openNode(id: number, edit = false, underBoard = false) {
|
async openNode(id: number, edit = false) {
|
||||||
if (this.open?.id !== id) await this.flushTile()
|
if (this.open?.id !== id) await this.flushTile()
|
||||||
|
|
||||||
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
|
|
||||||
// opened behind it; closing a document goes back to the board.
|
|
||||||
if (this.view === 'special' && !underBoard) {
|
|
||||||
if (isContainer(node)) this.closeSpecial()
|
|
||||||
else {
|
|
||||||
this.view = 'tree'
|
|
||||||
this.returnTo = 'special'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isContainer(node)) {
|
if (isContainer(node)) {
|
||||||
await this.goTo(node.id)
|
await this.goTo(node.id)
|
||||||
return
|
return
|
||||||
@@ -961,6 +999,10 @@ class Workspace {
|
|||||||
if (edit && node.kind === 'document') this.editing = true
|
if (edit && node.kind === 'document') this.editing = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// A view is never put away by what is opened from it — a card's notes,
|
||||||
|
// a day of the calendar, a search from the board: that opens beside it,
|
||||||
|
// and closing it goes back there.
|
||||||
|
if (this.tile.kind !== 'node') this.#addTile()
|
||||||
|
|
||||||
this.open = node
|
this.open = node
|
||||||
this.draft = { name: node.name, body: node.body, created_at: node.created_at }
|
this.draft = { name: node.name, body: node.body, created_at: node.created_at }
|
||||||
@@ -1112,7 +1154,7 @@ class Workspace {
|
|||||||
this.say(`restored the space ${name}`)
|
this.say(`restored the space ${name}`)
|
||||||
} else {
|
} else {
|
||||||
const parent = node.path.at(-2)
|
const parent = node.path.at(-2)
|
||||||
if (parent && node.path[0]?.id === this.spaceId && this.view === 'tree') {
|
if (parent && node.path[0]?.id === this.spaceId) {
|
||||||
if (parent.id === this.parentId && !this.query) await this.refresh(node.id)
|
if (parent.id === this.parentId && !this.query) await this.refresh(node.id)
|
||||||
else if (this.overlay !== 'trash') await this.goTo(parent.id, node.id)
|
else if (this.overlay !== 'trash') await this.goTo(parent.id, node.id)
|
||||||
}
|
}
|
||||||
@@ -1256,7 +1298,6 @@ class Workspace {
|
|||||||
this.prune()
|
this.prune()
|
||||||
this.rawBoard = null
|
this.rawBoard = null
|
||||||
this.boardSpaceId = null
|
this.boardSpaceId = null
|
||||||
this.view = 'tree'
|
|
||||||
this.spaces = await api.listSpaces()
|
this.spaces = await api.listSpaces()
|
||||||
const next = this.spaces.find((space) => space.role === 'diary') ?? this.spaces[0]
|
const next = this.spaces.find((space) => space.role === 'diary') ?? this.spaces[0]
|
||||||
if (next) await this.enterSpace(next.id)
|
if (next) await this.enterSpace(next.id)
|
||||||
@@ -1356,7 +1397,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
|
||||||
await this.openSpecial('agenda')
|
await this.showView('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. */
|
||||||
@@ -1364,7 +1405,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.openSpecial('calendar')
|
await this.showView('calendar')
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCalendar() {
|
async loadCalendar() {
|
||||||
@@ -1385,7 +1426,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)
|
||||||
// Opened from the special workspace, so `q` on the entry comes back here.
|
// Beside the calendar, so `q` on the entry comes back to it.
|
||||||
if (entry) return this.openPath(entry.path)
|
if (entry) return this.openPath(entry.path)
|
||||||
await this.openDay(day)
|
await this.openDay(day)
|
||||||
}
|
}
|
||||||
@@ -1423,10 +1464,10 @@ class Workspace {
|
|||||||
|
|
||||||
/** Enter in the agenda: to the card, on its own board, beside the agenda. */
|
/** Enter in the agenda: to the card, on its own board, beside the agenda. */
|
||||||
async goToCard(card: AgendaCard) {
|
async goToCard(card: AgendaCard) {
|
||||||
|
// From the agenda's tile, which comes along into the card's space.
|
||||||
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 = 'special'
|
await this.showView('board')
|
||||||
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. */
|
||||||
@@ -1435,7 +1476,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 === 'special' && this.boardSpaceId === card.space_id) await this.reloadBoard()
|
if (this.boardSpaceId === card.space_id) await this.reloadBoard()
|
||||||
this.say(doneSaid(moved))
|
this.say(doneSaid(moved))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1448,7 +1489,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 === 'special' && this.boardSpaceId === card.space_id) await this.reloadBoard()
|
if (this.boardSpaceId === card.space_id) await this.reloadBoard()
|
||||||
return updated
|
return updated
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1465,7 +1506,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 === 'special' && this.boardSpaceId === card.space_id) await this.reloadBoard()
|
if (this.boardSpaceId === 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}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1533,56 +1574,85 @@ class Workspace {
|
|||||||
* where a card is would be worse than a round trip.
|
* where a card is would be worse than a round trip.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* The special workspace, with `focus` the tile the keys go to. Everything
|
* The views — the board, the agenda, the calendar, a card's back — are
|
||||||
* in it is read again on the way in: it is looked at in passing, and what
|
* tiles, opened beside what is on screen. Each is read again whenever it is
|
||||||
* it says should be what is true now.
|
* gone to: it is looked at in passing, and what it says should be what is
|
||||||
|
* true now.
|
||||||
*/
|
*/
|
||||||
async openSpecial(focus: SpecialTile = this.specialFocus) {
|
|
||||||
|
/** `b`, `t`, `C`, Enter on a card: to that view's tile, opening it first when it is not up. */
|
||||||
|
async showView(kind: View) {
|
||||||
|
await this.#loadView(kind)
|
||||||
|
if (!this.view(kind)) {
|
||||||
|
// The one empty tile there can be takes it; otherwise it goes beside.
|
||||||
|
if (this.tile.empty) this.tile.kind = kind
|
||||||
|
else this.tiles.push(new Tile(kind))
|
||||||
|
}
|
||||||
|
this.focusTile(this.tiles.indexOf(this.view(kind)!))
|
||||||
|
if (kind === 'board' && this.boardSpaceId === null) this.say('no space has a board — :set board turns one on', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The views `S` puts up together, in the order they are tiled. */
|
||||||
|
static readonly PLAN: View[] = ['board', 'agenda', 'calendar']
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `S`: the board, the agenda and the calendar, tiled ahead of the
|
||||||
|
* documents with the board focused — or, when all three are up, every view
|
||||||
|
* put away again.
|
||||||
|
*/
|
||||||
|
async togglePlan() {
|
||||||
|
if (Workspace.PLAN.every((kind) => this.view(kind))) {
|
||||||
|
for (const tile of this.tiles) if (tile.kind !== 'node') tile.clear()
|
||||||
|
this.prune()
|
||||||
|
if (!this.tile.empty) this.focusTile(this.focused)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await Promise.all(Workspace.PLAN.map((kind) => this.#loadView(kind)))
|
||||||
|
const current = this.tile
|
||||||
|
const missing = Workspace.PLAN.filter((kind) => !this.view(kind)).map((kind) => new Tile(kind))
|
||||||
|
this.tiles = [...missing, ...this.tiles]
|
||||||
|
this.focused = this.tiles.indexOf(current)
|
||||||
|
this.prune()
|
||||||
|
this.focusTile(this.tiles.indexOf(this.view('board')!))
|
||||||
|
if (this.boardSpaceId === null) this.say('no space has a board — :set board turns one on', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read again what a view shows. */
|
||||||
|
async #loadView(kind: View) {
|
||||||
|
if (kind === 'board' || kind === 'card') await this.#loadBoard()
|
||||||
|
else if (kind === 'agenda') await this.loadAgenda()
|
||||||
|
else {
|
||||||
|
if (!this.calendarDay) this.calendarDay = formatDay(Math.floor(Date.now() / 1000))
|
||||||
|
await this.loadCalendar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Every view on screen read again, after a space switch brought them. */
|
||||||
|
async #loadViews() {
|
||||||
|
if (this.view('board') || this.view('card')) await this.#loadBoard()
|
||||||
|
if (this.view('calendar')) await this.#loadView('calendar')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The board of the space being browsed, or else the one shown last, or
|
||||||
|
* else the first space's that has one; null when no space has a board.
|
||||||
|
*/
|
||||||
|
async #loadBoard() {
|
||||||
const space = this.space
|
const space = this.space
|
||||||
const shown = this.boardSpace?.has_board ? this.boardSpace : this.spaces.find((s) => s.has_board)
|
const shown = this.boardSpace?.has_board ? this.boardSpace : this.spaces.find((s) => s.has_board)
|
||||||
const board = space?.has_board ? space : shown
|
const board = space?.has_board ? space : shown
|
||||||
if (board) await this.reloadBoard(board.id, board.id === this.boardSpaceId ? this.card?.id : undefined)
|
if (!board) {
|
||||||
else {
|
|
||||||
this.rawBoard = null
|
this.rawBoard = null
|
||||||
this.boardSpaceId = null
|
this.boardSpaceId = null
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (!this.calendarDay) this.calendarDay = formatDay(Math.floor(Date.now() / 1000))
|
// Another space's board starts at its top left, as a space switch did.
|
||||||
await Promise.all([this.loadAgenda(), this.loadCalendar()])
|
if (board.id !== this.boardSpaceId) {
|
||||||
this.view = 'special'
|
this.listCursor = 0
|
||||||
this.specialFocus = focus
|
this.cardCursor = 0
|
||||||
if (focus === 'board' && !board) this.say('no space has a board — :set board turns one on', 'error')
|
}
|
||||||
}
|
await this.reloadBoard(board.id, board.id === this.boardSpaceId ? this.card?.id : undefined)
|
||||||
|
|
||||||
closeSpecial() {
|
|
||||||
this.view = 'tree'
|
|
||||||
this.returnTo = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/** `S`: the special workspace up, on the tile it was left on, or away again. */
|
|
||||||
async toggleSpecial() {
|
|
||||||
if (this.view === 'special') this.closeSpecial()
|
|
||||||
else await this.openSpecial()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `b`, `t` and `C`: to that tile of the special workspace, putting it up
|
|
||||||
* first when it is not. They only ever go somewhere; leaving is `S`,
|
|
||||||
* `q` or `Esc`, from whichever tile.
|
|
||||||
*/
|
|
||||||
async showSpecial(focus: SpecialTile) {
|
|
||||||
if (this.view === 'special') this.specialFocus = focus
|
|
||||||
else await this.openSpecial(focus)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** `Tab` / `Shift-Tab` in the special workspace: the next tile round, or the one before. */
|
|
||||||
cycleSpecial(by: 1 | -1) {
|
|
||||||
const order: SpecialTile[] = ['board', 'agenda', 'calendar']
|
|
||||||
this.specialFocus = order[(order.indexOf(this.specialFocus) + by + order.length) % order.length]
|
|
||||||
}
|
|
||||||
|
|
||||||
async openBoard() {
|
|
||||||
await this.openSpecial('board')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get boardSpace(): NodeSummary | undefined {
|
get boardSpace(): NodeSummary | undefined {
|
||||||
@@ -1677,7 +1747,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 === 'special') {
|
if (this.focus === 'board' || this.focus === 'card') {
|
||||||
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 })
|
||||||
@@ -1733,11 +1803,23 @@ class Workspace {
|
|||||||
* too, and the cursor follows it wherever a move puts it.
|
* too, and the cursor follows it wherever a move puts it.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
openCardSheet() {
|
/** Enter on a card: its back, in a tile beside the board. */
|
||||||
|
async turnCard() {
|
||||||
if (!this.card) return this.say('no card selected', 'error')
|
if (!this.card) return this.say('no card selected', 'error')
|
||||||
this.taskCursor = 0
|
this.taskCursor = 0
|
||||||
this.describing = false
|
await this.showView('card')
|
||||||
this.openSheet('card')
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Leaving a card's back while its description is being written: what is
|
||||||
|
* written is kept, as leaving a document's tile keeps its draft.
|
||||||
|
*/
|
||||||
|
#leaveCard() {
|
||||||
|
if (this.tile.kind !== 'card' || !this.describing) return
|
||||||
|
void this.guard(async () => {
|
||||||
|
if (this.descriptionDirty) await this.saveDescription()
|
||||||
|
this.stopDescribing(true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get scratchNote(): ScratchNote | undefined {
|
get scratchNote(): ScratchNote | undefined {
|
||||||
@@ -1864,25 +1946,17 @@ class Workspace {
|
|||||||
await this.dropScratchNote(false)
|
await this.dropScratchNote(false)
|
||||||
this.scratchEditing = false
|
this.scratchEditing = false
|
||||||
this.scratchOpen = false
|
this.scratchOpen = false
|
||||||
if (this.view === 'special') this.closeSpecial()
|
|
||||||
await this.refresh(node.id)
|
await this.refresh(node.id)
|
||||||
await this.openNode(node.id)
|
await this.openNode(node.id)
|
||||||
this.say(`kept as ${node.path.map((crumb) => crumb.slug).join('/')}`)
|
this.say(`kept as ${node.path.map((crumb) => crumb.slug).join('/')}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `q`: the sheet away, unless that would lose a description being written. */
|
|
||||||
closeCardSheet() {
|
|
||||||
if (this.describing && !this.stopDescribing()) return
|
|
||||||
this.overlay = 'none'
|
|
||||||
}
|
|
||||||
|
|
||||||
openSheet(sheet: Sheet) {
|
openSheet(sheet: Sheet) {
|
||||||
this.overlay = sheet
|
this.overlay = sheet
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Esc, q, a click beside it: the sheet away — the card's asks first. */
|
/** Esc, q, a click beside it: the sheet away. */
|
||||||
closeSheet() {
|
closeSheet() {
|
||||||
if (this.overlay === 'card') return this.closeCardSheet()
|
|
||||||
this.overlay = 'none'
|
this.overlay = 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2065,31 +2139,12 @@ class Workspace {
|
|||||||
await api.updateCard(card.id, { node_id: node.id })
|
await api.updateCard(card.id, { node_id: node.id })
|
||||||
nodeId = node.id
|
nodeId = node.id
|
||||||
}
|
}
|
||||||
this.view = 'tree'
|
|
||||||
if (this.parentId !== space) await this.goTo(space)
|
if (this.parentId !== space) await this.goTo(space)
|
||||||
else await this.refresh()
|
else await this.refresh()
|
||||||
// The notes open beside whatever was being read, and `q` takes their
|
// The notes open beside the board, and `q` takes their tile away again
|
||||||
// tile away again on the way back to the board.
|
// on the way back to it.
|
||||||
if (this.open && this.open.id !== nodeId) {
|
|
||||||
this.tiles.push(new Tile())
|
|
||||||
this.focused = this.tiles.length - 1
|
|
||||||
}
|
|
||||||
await this.openNode(nodeId, card.node_id === null)
|
await this.openNode(nodeId, card.node_id === null)
|
||||||
this.prune()
|
|
||||||
if (card.node_id === null) this.enterInsert = true
|
if (card.node_id === null) this.enterInsert = true
|
||||||
this.returnTo = 'special'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Back to the special workspace a document was opened from — a card's
|
|
||||||
* notes, a day of the calendar, an entry of the agenda — on the tile it
|
|
||||||
* was opened from.
|
|
||||||
*/
|
|
||||||
async returnToSpecial() {
|
|
||||||
this.returnTo = null
|
|
||||||
this.closeTile()
|
|
||||||
this.pane = 'list'
|
|
||||||
await this.openSpecial(this.specialFocus)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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. */
|
||||||
@@ -2098,11 +2153,11 @@ class Workspace {
|
|||||||
if (!space) return
|
if (!space) return
|
||||||
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.showView('board')
|
||||||
else if (this.boardSpaceId === space.id) {
|
else if (this.boardSpaceId === space.id) {
|
||||||
this.rawBoard = null
|
this.rawBoard = null
|
||||||
this.boardSpaceId = null
|
this.boardSpaceId = null
|
||||||
if (this.view === 'special') await this.openSpecial()
|
if (this.view('board') || this.view('card')) await this.#loadBoard()
|
||||||
}
|
}
|
||||||
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`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user