mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-03 16:41:27 +01:00
keys: Ctrl moves between tiles, Ctrl-Q jots from anywhere, the plan gets a screen of its own
Alt never reached the app, so the tile keys are Ctrl-h/j/k/l and Ctrl-Shift-h/j/k/l, taken before vim sees them, and h/l walk the tiles while reading. Ctrl-Q lowers the scratchpad from the editor straight into the last note, and raises it back to the document, still writing. The drawer sits in the porthole's opening with a full rim. The board, the agenda, the calendar and a card's back live on a plan screen that S goes to and comes back from, so documents and views never share the screen. The calendar fits its tile instead of scrolling.
This commit is contained in:
1 parent
b1107013ee
commit
650fc40fa7
26 files changed
+340
-221
No files matched your search
@@ -118,11 +118,12 @@ it; in the document pane it is that document. In the browse pane:
|
||||
| `Tab`, `Shift-Tab` | next / previous space |
|
||||
| `w` | overview of every space; `hjkl` then `Enter` |
|
||||
| `f` | fullscreen the focused pane, or the focused tile |
|
||||
| `Alt-h/j/k/l` | focus the tile in that direction, from the editor too; `Alt-h` past the leftmost tile focuses the list |
|
||||
| `Alt-Shift-h/j/k/l` | move the focused tile that way |
|
||||
| `Ctrl-h/j/k/l` | focus the tile in that direction, from the editor too; `Ctrl-h` past the leftmost tile focuses the list |
|
||||
| `h` / `l` | while reading, the tile to the left / right; `h` past the leftmost goes to the list |
|
||||
| `Ctrl-Shift-h/j/k/l` | move the focused tile that way |
|
||||
| `L`, `Shift-Enter` | open the highlighted row in a new tile instead of the focused one |
|
||||
| `gt` / `gT` | next / previous tile, in the editor too |
|
||||
| `Alt-q` | close the focused tile (`:close`); asks first if its draft could not be written |
|
||||
| `q` | close the document, and its tile when there are several (`:close`); asks first if its draft could not be written |
|
||||
| `j` / `k` | move down / up, or scroll the document when it has focus |
|
||||
| `gg` / `G` | first / last entry, or top / bottom of the document; a first `g`, `d`, `]` or `[` shows in the status line for a second while it waits for the second key |
|
||||
| `Ctrl-D` / `Ctrl-U` | half a page down / up the open document |
|
||||
@@ -143,14 +144,15 @@ it; in the document pane it is that document. In the browse pane:
|
||||
| `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 |
|
||||
| `za` | fold a document's own files open under it in the list, or away again |
|
||||
| `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 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 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 a tile |
|
||||
| `S` | the plan screen — the board, the agenda and the calendar, in place of the documents and the list — or back to the documents as they were left |
|
||||
| `b` | the board of this space, on the plan screen; a digit there shows another space's |
|
||||
| `t` | the agenda: what is late, due this week, or in hand, on every board, on the plan screen |
|
||||
| `T` | today's diary entry, started if there is none |
|
||||
| `C` | the diary by month (`:cal`) — see [The diary](#the-diary) |
|
||||
| `[d` / `]d` | the diary entry before / after the one open, by date |
|
||||
| `c` | capture a card into this space's inbox without leaving where you are — reading a document, a card about it; `Tab` on the line makes it a note, or a line in today's entry |
|
||||
| `Q` | the scratchpad, lowered over whatever is on screen — see [The scratchpad](docs/wiki/Writing.md#the-scratchpad) |
|
||||
| `Ctrl-Q` | from anywhere, the editor too: the scratchpad down with the last note being written, and again back to where you were, still writing (Firefox quits on it instead) |
|
||||
| `Esc`, `q` | close the document, and its tile when there are several; `Esc` first puts down what `m` picked up |
|
||||
| `h` | up one level in the tree |
|
||||
| `/` | full-text search in this space (`:noh` clears it); `Tab` or `Ctrl-N`/`Ctrl-P` walk the results without leaving the line, `Enter` opens the one lit |
|
||||
@@ -222,13 +224,21 @@ its own scroll position and its own editor, and the keys always act on the
|
||||
focused one. Opening a document that is already in a tile focuses that tile
|
||||
rather than opening it twice.
|
||||
|
||||
`Alt-h/j/k/l` moves the focus between tiles and `Alt-Shift-h/j/k/l` moves the
|
||||
tile itself; `gt` / `gT` and `:bn` / `:bp` cycle through them, `:ls` lists
|
||||
them, `:b <n or name>` focuses one, `:close` (`Alt-q`) closes one and `:only`
|
||||
every other. `:next` and `:prev` still walk the list. Each space remembers its
|
||||
tiles, so switching away and back finds them as they were. A card's notes
|
||||
open in a tile beside what was being read, and `q` there goes back to the
|
||||
board. Under 768px wide only the focused tile is shown.
|
||||
`Ctrl-h/j/k/l` moves the focus between tiles, from the editor too, and
|
||||
`Ctrl-Shift-h/j/k/l` moves the tile itself; while reading, `h` and `l` go to
|
||||
the tile left and right. `gt` / `gT` and `:bn` / `:bp` cycle through them,
|
||||
`:ls` lists them, `:b <n or name>` focuses one, `:close` (`q` while reading)
|
||||
closes one and `:only` every other. `:next` and `:prev` still walk the list.
|
||||
Each space remembers its tiles, so switching away and back finds them as they
|
||||
were. Under 768px wide only the focused tile is shown.
|
||||
|
||||
The views — the board, the agenda, the calendar and a card's back — have a
|
||||
screen of their own, the plan, which `S` goes to and comes back from; `b`, `t`
|
||||
and `C` go there too, to their own view. The documents are put away behind it
|
||||
as they are, and the plan stays as it is from space to space. Opening a
|
||||
document from the plan — a card's notes, a day of the calendar — goes back to
|
||||
the documents and opens it beside them; `q` on a view does the same, and on a
|
||||
card's back goes back to the board.
|
||||
|
||||
## The SFTP mount
|
||||
|
||||
@@ -257,8 +267,8 @@ start as `sftp_host_key` in the data directory.
|
||||
## Boards
|
||||
|
||||
`b`, the board door on the rail, or `:board` shows the board of the space
|
||||
being browsed in a tile beside what is open, and `q` or `Esc` closes it; `S`
|
||||
puts it up with the agenda and the calendar. Every space has one except the
|
||||
being browsed on the plan screen, beside the agenda and the calendar, and `q`
|
||||
or `Esc` goes back to the documents. Every space has one except the
|
||||
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
|
||||
same). A new board starts with `backlog`, `doing` and `done`, and the columns mean
|
||||
|
||||
@@ -3,42 +3,39 @@
|
||||
## Views
|
||||
|
||||
The board, the agenda, the [calendar](Diary.md#the-calendar) and the back of
|
||||
a card are views: tiles like the documents' tiles, opened beside what is on
|
||||
screen and walked the same way. `S` (or `:plan`) puts the board, the agenda
|
||||
and the calendar up together, ahead of the documents with the board focused —
|
||||
the board on the left, the agenda over the calendar on the right when nothing
|
||||
else is open — and puts every view away again.
|
||||
a card are views, and they have a screen of their own: the plan. `S` (or
|
||||
`:plan`) goes there — the board on the left, the agenda over the calendar on
|
||||
the right, and no list — and back to the documents, which wait behind it as
|
||||
they were left. The views are tiles like the documents', walked the same way.
|
||||
|
||||

|
||||
|
||||
| key | what it does |
|
||||
| --- | --- |
|
||||
| `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, opening its tile when it is not up |
|
||||
| `S` | to the plan screen, or back to the documents |
|
||||
| `b`, `t`, `C` | to the board, the agenda or the calendar, on the plan screen |
|
||||
| `gt` / `gT` | the next / previous tile |
|
||||
| `Alt-h` / `Alt-j` / `Alt-k` / `Alt-l` | the tile that way; left of the leftmost is the list |
|
||||
| `Ctrl-h` / `Ctrl-j` / `Ctrl-k` / `Ctrl-l` | the tile that way |
|
||||
| `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 |
|
||||
| `1` … `9` | switch space; with a view focused, the views come along |
|
||||
| `q`, `Esc` | close the view's tile, back to the tile focused before it |
|
||||
| `1` … `9` | switch space; the plan stays, and the board shows that space's |
|
||||
| `q`, `Esc` | from a card's back to the board; from the board, the agenda or the calendar back to the documents |
|
||||
|
||||
The keys go to the tile with focus, which wears the light border and its key
|
||||
lit in its head; the other views keep their cursors, dimmed. A view is never
|
||||
replaced by what is opened from it — a card's notes, a day of the calendar, an
|
||||
entry of the agenda, a row of the list: that opens in a tile beside it, and
|
||||
`q` on it comes back to the view. Each view is read again whenever it is gone
|
||||
to.
|
||||
lit in its head; the other views keep their cursors, dimmed. What is opened
|
||||
from a view — a card's notes, a day of the calendar, an entry of the agenda —
|
||||
is a document, so it opens on the documents' screen, beside what is there, and
|
||||
`S` comes back to the plan. Each view is read again whenever it is gone to.
|
||||
|
||||
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 plan is the same in every space, the way the desktop's special workspace
|
||||
stays up: a digit on the board shows that space's board, with the agenda still
|
||||
beside it. Each space keeps its own documents behind it, and they are there
|
||||
again on going back.
|
||||
|
||||
## The board
|
||||
|
||||
Every space except the diary has a board. `b`, the board button on the rail, or
|
||||
`:board` shows it in a tile beside what is open, and `q` or `Esc` closes it.
|
||||
`:board` shows it on the plan screen, and `q` or `Esc` goes back to the documents.
|
||||
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
|
||||
has one, with the digit that goes there.
|
||||
@@ -119,8 +116,8 @@ outside the browser, see [Capture](Capture.md).
|
||||
### Notes
|
||||
|
||||
`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, in a tile beside the board. Closing it goes
|
||||
back to the board, on the same card.
|
||||
root of the space the first time, among the documents. `S` goes back to the
|
||||
board, on the same card.
|
||||
|
||||
The link is loose on purpose: deleting the document leaves the card, because the
|
||||
work is still open when its notes are gone.
|
||||
@@ -186,8 +183,8 @@ does not make a second copy. The board and the agenda show the rule as
|
||||
|
||||
## The agenda
|
||||
|
||||
`t` (or `:agenda`) gathers what needs you from every board at once, in a tile
|
||||
of its own.
|
||||
`t` (or `:agenda`) gathers what needs you from every board at once, on the
|
||||
plan screen.
|
||||
|
||||

|
||||
|
||||
@@ -201,7 +198,7 @@ of its own.
|
||||
| --- | --- |
|
||||
| `j` / `k` | down / up, on into the diary entries, then into the tile below |
|
||||
| `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, beside, or open the entry among the documents |
|
||||
| `Space` | finish the card where it stands |
|
||||
| `D` | due date |
|
||||
| `H` / `L` | to the list left / right on its board |
|
||||
|
||||
+13
-13
@@ -19,11 +19,10 @@ arguments and `↑` / `↓` recall earlier lines.
|
||||
| `Tab · Shift-Tab` | next / previous space |
|
||||
| `w` | overview of every space — hjkl, then Enter |
|
||||
| `f` | fullscreen the focused pane or tile; again or Esc to leave |
|
||||
| `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 |
|
||||
| `Ctrl-h/j/k/l` | focus the tile that way, from the editor too — left of the leftmost is the list; h / l do the same while reading |
|
||||
| `Ctrl-Shift-h/j/k/l` | move the focused tile that way |
|
||||
| `L · Shift-Enter` | open the row in a new tile beside the others |
|
||||
| `gt / gT` | next / previous tile — a view too |
|
||||
| `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 |
|
||||
| `gg / G` | first / last row, or top / bottom of the document — a first g, d, ] or [ waits a second in the status line |
|
||||
| `Ctrl-D / Ctrl-U` | half a page down / up the open document |
|
||||
@@ -44,13 +43,13 @@ arguments and `↑` / `↓` recall earlier lines.
|
||||
| `za` | a document's own files, folded open under it in the list — again folds them away; p drops marked files in there |
|
||||
| `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 |
|
||||
| `S` | the board, the agenda and the calendar, tiled ahead of what is open with the board focused — S again puts every view away |
|
||||
| `t` | agenda — late, due this week, in hand, on every board — in a tile of its own |
|
||||
| `S` | the plan screen: the board, the agenda and the calendar, in place of the documents and the list — S again goes back to the documents as they were |
|
||||
| `t` | agenda — late, due this week, in hand, on every board — on the plan screen |
|
||||
| `T` | today's diary entry, started if there is none — from templates/day when the diary has one |
|
||||
| `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 |
|
||||
| `C` | the diary by month, on the plan screen: h/l a day, j/k a week, H/L a month, . today, Enter opens or starts the day among the documents |
|
||||
| `[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 |
|
||||
| `b` | the board, in a tile: this space's, or the last one shown — 1 … 9 there shows another's |
|
||||
| `b` | the board, on the plan screen: 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 — reading a document, a card about it; Tab on the line makes it a note, or a line in today's entry |
|
||||
| `Esc · q` | close the document, and its tile when there are several — Esc first puts down what m picked up |
|
||||
| `/` | full-text search, inside the current space and its text files — Tab or Ctrl-N/P walk the results, Enter opens one |
|
||||
@@ -64,7 +63,7 @@ arguments and `↑` / `↓` recall earlier lines.
|
||||
| `Ctrl-S` | save, from anywhere |
|
||||
| `?` | this screen — the keys to start with, then on a second ? every key and command; h/l a column, / filters it down |
|
||||
| `,` | the settings sheet: vim and the clipboard, the vault, the backup, signed-in browsers, ssh keys, capture tokens |
|
||||
| `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 |
|
||||
| `q · Esc` | on the plan screen: from a card's back to the board, from the board, the agenda or the calendar back to the documents |
|
||||
|
||||
### On a board
|
||||
|
||||
@@ -79,7 +78,7 @@ The first list is the inbox, the last list is done.
|
||||
| `H / L` | move the card to the list left / right |
|
||||
| `Space` | into the done list, or back out to the inbox |
|
||||
| `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, opened among the documents; S comes back |
|
||||
| `o · c` | new card on this list |
|
||||
| `D` | due date: fri, tm, +3d, 10-31 … |
|
||||
| `R` | rename the card, or the list when it is empty |
|
||||
@@ -103,7 +102,7 @@ The first list is the inbox, the last list is done.
|
||||
| `a` | put on a label, or take it off — :label on the line, Tab offers the board's |
|
||||
| `R · D` | rename the card, its due date |
|
||||
| `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, opened among the documents; S comes back |
|
||||
|
||||
### In the agenda
|
||||
|
||||
@@ -123,6 +122,7 @@ The first list is the inbox, the last list is done.
|
||||
| key | what it does |
|
||||
| --- | --- |
|
||||
| `Q` | lower the scratchpad over whatever is on screen, or raise it again — :scratch <text> takes a note without it |
|
||||
| `Ctrl-Q` | from anywhere, the editor too: the scratchpad down with the last note being written, and again back to where you were, still writing — Firefox quits on it instead |
|
||||
| `j / k` | down / up the notes, the one touched last on top |
|
||||
| `o` | a new note, written straight away — it saves itself |
|
||||
| `Enter · e · i` | write the note — Esc in normal mode, or :q, comes back to the notes |
|
||||
@@ -154,8 +154,8 @@ whole workspace. A command that does a second thing takes a word for it:
|
||||
|
||||
| command | what it does |
|
||||
| --- | --- |
|
||||
| `:board` | this space's board, in a tile — the last one shown when this space has none |
|
||||
| `: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 |
|
||||
| `:board` | this space's board, on the plan screen — the last one shown when this space has none |
|
||||
| `:plan, :special` | the plan screen: the board, the agenda and the calendar, in place of the documents — S goes there and back again; 1 … 9 on the board picks the board |
|
||||
| `:card, :c [title]` | add a card — reading a document, one about it, titled after it unless named, and gf on it comes back; on the board to the list under the cursor, elsewhere to this space's inbox |
|
||||
| `:due <when\|->` | due date of the card in focus — on the board, its back, in the agenda, or the open document's: 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 |
|
||||
@@ -196,7 +196,7 @@ whole workspace. A command that does a second thing takes a word for it:
|
||||
|
||||
| command | what it does |
|
||||
| --- | --- |
|
||||
| `:close, :clo` | close the focused tile — Alt-q; asks when its draft could not be written |
|
||||
| `:close, :clo` | close the focused tile — q while reading; on the plan screen, back to the documents; asks when its draft could not be written |
|
||||
| `:only, :on` | close every tile but the focused one |
|
||||
| `:ls, :buffers, :files` | list the open tiles, % on the focused one |
|
||||
| `:buffer, :b <n\|name>` | focus a tile by its number in :ls, or by the start of its name |
|
||||
|
||||
+5
-5
@@ -13,9 +13,9 @@ an entry written after midnight on a trip still lands on the right day.
|
||||
|
||||
## The calendar
|
||||
|
||||
`C` (or `:cal`) shows the diary a month at a time, in a
|
||||
[tile of its own](Boards-and-Agenda.md#views) — under the agenda when `S` puts
|
||||
them up together.
|
||||
`C` (or `:cal`) shows the diary a month at a time, on the
|
||||
[plan screen](Boards-and-Agenda.md#views), under the agenda. The month is
|
||||
fitted into its tile rather than scrolled.
|
||||
|
||||

|
||||
|
||||
@@ -25,8 +25,8 @@ them up together.
|
||||
| `j` / `k` | a week forward / back |
|
||||
| `H` / `L` | a month back / forward |
|
||||
| `.` | today |
|
||||
| `Enter` | open the day's entry beside the calendar, or start one dated that day |
|
||||
| `q`, `Esc` | close the calendar's tile; `q` on an entry opened from it comes back here |
|
||||
| `Enter` | open the day's entry among the documents, or start one dated that day; `S` comes back |
|
||||
| `q`, `Esc` | back to the documents |
|
||||
|
||||
A dot marks a day with an entry. A number says how many when there are more
|
||||
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
|
||||
`h` comes back up.
|
||||
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 in a tile, and
|
||||
`Esc` to close it. `S` puts it up with the agenda and the calendar, and puts
|
||||
them away again; `Alt-h`/`j`/`k`/`l` walk between the tiles.
|
||||
inbox of this space's board. Press `b` to see the board on the plan screen,
|
||||
beside the agenda and the calendar, and `Esc` to go back to the documents.
|
||||
`S` goes to the plan and back; `Ctrl-h`/`j`/`k`/`l` walk between the tiles.
|
||||
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.
|
||||
|
||||
|
||||
+11
-10
@@ -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
|
||||
|
||||
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 a tile
|
||||
beside what is open; `S` puts it up with the board and the calendar, and puts
|
||||
them away again, and `Alt-h`/`j`/`k`/`l` walk the tiles.
|
||||
when anything is late or due. Any other time, press `t`. It is on the plan
|
||||
screen, with the board and the calendar, in place of the documents; `S` goes
|
||||
there and back again, and `Ctrl-h`/`j`/`k`/`l` walk the tiles.
|
||||
|
||||

|
||||
|
||||
@@ -56,8 +56,9 @@ to it and press `L` instead: it opens in a **tile** of its own.
|
||||
|
||||

|
||||
|
||||
`Alt-h` and `Alt-l` move between the tiles and back to the list, `Alt-q`
|
||||
closes one, and `f` makes the focused one fill the screen. Each space remembers
|
||||
`h` and `l` move between the tiles and back to the list, `Ctrl-h` and
|
||||
`Ctrl-l` from the editor too, `q` closes one, and `f` makes the focused one
|
||||
fill the screen. Each space remembers
|
||||
its tiles, so switching away and back finds them as they were. More in
|
||||
[Spaces and documents](Spaces-and-Documents.md).
|
||||
|
||||
@@ -89,8 +90,8 @@ on the same line makes it a note on the scratchpad, and `Tab` again a timed line
|
||||
in today's diary entry. A note waits there until `K`, `C` or `A` turns it into a
|
||||
document, a card, or more of a document.
|
||||
|
||||
Press `b` to see the **board**, in a tile beside the document. A digit there
|
||||
shows the board of another space.
|
||||
Press `b` to see the **board**, on the plan screen. A digit there shows the
|
||||
board of another space.
|
||||
|
||||

|
||||
|
||||
@@ -99,9 +100,9 @@ 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 over to its labels, description and checklist, in a tile beside, and
|
||||
`gf` opens its notes: a document made for the card the first time you ask,
|
||||
also beside, and `q` on it comes back to the board. `:repeat weekly` makes a
|
||||
card come back in the inbox each time it is finished. `q` on the board closes
|
||||
its tile.
|
||||
opened among the documents, and `S` comes back to the board. `:repeat weekly` makes a
|
||||
card come back in the inbox each time it is finished. `q` on the board goes
|
||||
back to the documents.
|
||||
|
||||
### Finding things
|
||||
|
||||
|
||||
@@ -103,11 +103,12 @@ Several documents can be open at once, each in a tile of its own.
|
||||
it in the focused one.
|
||||
- Tiles share the space the way a tiling window manager does: two side by side,
|
||||
a third splits the newer one, and so on.
|
||||
- `Alt-h/j/k/l` moves the focus between tiles, and `Alt-h` past the leftmost
|
||||
goes back to the list. `Alt-Shift-h/j/k/l` moves the tile itself.
|
||||
- `Ctrl-h/j/k/l` moves the focus between tiles, from the editor too, and
|
||||
`Ctrl-h` past the leftmost goes back to the list. While reading, `h` and `l`
|
||||
do the same. `Ctrl-Shift-h/j/k/l` moves the tile itself.
|
||||
- `gt` / `gT` (or `:bn` / `:bp`) cycle through them, `:ls` lists them, and
|
||||
`:b <n or name>` focuses one.
|
||||
- `Alt-q` (or `:close`) closes the focused tile, `:only` every other one, and
|
||||
- `q` (or `:close`) closes the focused tile, `:only` every other one, and
|
||||
`f` makes the focused tile fill the screen.
|
||||
|
||||
Opening a document that is already in a tile focuses that tile rather than
|
||||
|
||||
@@ -60,7 +60,10 @@ Each save keeps the text it replaces, so nothing written is lost. See
|
||||
Not every note knows yet where it belongs. `Q` (or `:scratch`) lowers the
|
||||
scratchpad from under the bar, over whatever is on screen, the way the
|
||||
desktop lowers its own; `Q`, `q` or `Esc` raises it again, and what was on
|
||||
screen is as it was. Its notes have no name, no space and no folder: a note is
|
||||
screen is as it was. `Ctrl-Q` works from anywhere, the editor included: the
|
||||
scratchpad comes down with the last note being written, at its end, and
|
||||
`Ctrl-Q` again puts it away and goes back to the document, still writing where
|
||||
it was left. Firefox quits on `Ctrl-Q`, so there it is `Q` only. Its notes have no name, no space and no folder: a note is
|
||||
a body and the time it was last touched, newest on top.
|
||||
|
||||
| key | what it does |
|
||||
@@ -73,6 +76,7 @@ a body and the time it was last touched, newest on top.
|
||||
| `A` | put it at the end of a document (`:append [document]`) |
|
||||
| `dd` | drop the note (asks y/N) |
|
||||
| `Q`, `q`, `Esc` | raise the scratchpad |
|
||||
| `Ctrl-Q` | from anywhere, while writing too: down with the last note being written, and back again |
|
||||
|
||||
A note is written in the same editor as a document, and saves itself the same
|
||||
way. `Esc` in normal mode, or `:q`, goes back to the notes; a note left empty is
|
||||
|
||||
+7
-2
@@ -246,8 +246,8 @@
|
||||
[[...keysOf('tree', 'down'), ...keysOf('tree', 'up')], 'browse, l opens, L opens beside'],
|
||||
[keysOf('global', 'space'), 'switch space, Tab cycles'],
|
||||
[keysOf('global', 'overview'), 'overview of every space'],
|
||||
[keysOf('global', 'plan'), 'board, agenda and calendar'],
|
||||
[[...keysOf('global', 'board'), ...keysOf('global', 'agenda'), ...keysOf('global', 'calendar')], 'one of them, in a tile'],
|
||||
[keysOf('global', 'plan'), 'the plan: board, agenda, calendar'],
|
||||
[[...keysOf('global', 'board'), ...keysOf('global', 'agenda'), ...keysOf('global', 'calendar')], 'one of them, on the plan'],
|
||||
[keysOf('tree', 'capture'), 'capture a card to the inbox'],
|
||||
[keysOf('global', 'find'), 'find in every space'],
|
||||
[keysOf('tree', 'search'), 'search this space'],
|
||||
@@ -293,6 +293,7 @@
|
||||
class="panes switch"
|
||||
class:reading={workspace.tiles.all.some((tile) => !tile.empty)}
|
||||
class:full={workspace.fullscreen}
|
||||
class:plan={workspace.tiles.planning}
|
||||
class:focus-doc={workspace.focus !== 'list'}
|
||||
style="--dir: {workspace.switchDirection}"
|
||||
>
|
||||
@@ -433,6 +434,10 @@
|
||||
height: calc(var(--h) * 100% - var(--gap));
|
||||
}
|
||||
|
||||
/* The plan screen: the views alone, with no list beside them. */
|
||||
.panes.plan { grid-template-columns: 1fr; }
|
||||
.panes.plan .window.list { display: none; }
|
||||
|
||||
/* `f`: the focused pane fills the porthole — the list, or one tile. */
|
||||
.panes.full { grid-template-columns: 1fr; }
|
||||
.panes.full.focus-doc .window.list,
|
||||
|
||||
@@ -83,14 +83,18 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* A tile's window is its frame, so it only fills it. */
|
||||
/*
|
||||
* A tile's window is its frame, so it only fills it — and the month is
|
||||
* fitted into what the tile leaves it, never scrolled: the weeks share the
|
||||
* height, up to a row a pointer still hits comfortably.
|
||||
*/
|
||||
.sheet {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 20px 24px;
|
||||
overflow: hidden;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
header { display: flex; align-items: center; gap: 12px; }
|
||||
@@ -99,21 +103,26 @@
|
||||
header .title { color: var(--light); }
|
||||
|
||||
.grid {
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 4px;
|
||||
grid-template-rows: auto;
|
||||
grid-auto-rows: minmax(0, 40px);
|
||||
gap: 3px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.weekday { text-align: center; font-size: 10px; color: var(--muted); }
|
||||
.grid > li { min-height: 0; }
|
||||
|
||||
.day {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 100%;
|
||||
aspect-ratio: 1.4;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-s);
|
||||
@@ -126,12 +135,13 @@
|
||||
.day.written { color: var(--fg); }
|
||||
.day.today { border-color: var(--rim); }
|
||||
.day.at { background: var(--light); color: var(--ink); }
|
||||
.dot, .many { position: absolute; bottom: 4px; }
|
||||
.dot, .many { position: absolute; bottom: 3px; }
|
||||
.dot { width: 5px; height: 5px; border-radius: 999px; background: var(--light); }
|
||||
.many { font-size: 9px; color: var(--light); }
|
||||
.day.at :is(.dot) { background: var(--ink); }
|
||||
.day.at .many { color: var(--ink); }
|
||||
|
||||
.chosen { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
||||
.chosen h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -155,6 +155,7 @@
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { api } from './api'
|
||||
import { hooks } from './commands'
|
||||
import { claim } from './modes'
|
||||
import { editorTheme, highlighting, languages } from './editorTheme'
|
||||
import { embedSnippet } from './markdown'
|
||||
import { workspace, type Tile } from './store.svelte'
|
||||
@@ -180,9 +181,11 @@
|
||||
|
||||
/*
|
||||
* Esc, before vim sees it: in normal mode, or with no vim, it leaves, as
|
||||
* `:q` does — unless it is closing the completions.
|
||||
* `:q` does — unless it is closing the completions. The Ctrl keys heard
|
||||
* everywhere — between the tiles, the scratchpad — go before vim too.
|
||||
*/
|
||||
function onkeydown(event: KeyboardEvent) {
|
||||
if (claim(event)) return
|
||||
if (event.key !== 'Escape' || (workspace.vimEnabled && mode !== 'normal')) return
|
||||
if (view && completionStatus(view.state) !== null) return
|
||||
event.preventDefault()
|
||||
@@ -317,6 +320,7 @@
|
||||
parent: host!,
|
||||
})
|
||||
|
||||
if (writing?.append) view.dispatch({ selection: { anchor: text.length } })
|
||||
if (writing || workspace.tile === tile) view.focus()
|
||||
|
||||
const cm = workspace.vimEnabled ? getCM(view) : null
|
||||
@@ -333,7 +337,10 @@
|
||||
// A fresh entry starts in insert mode, and so does an empty short text,
|
||||
// which is there to be written; anything else starts in normal mode,
|
||||
// the way opening a file in vim does.
|
||||
if (tile?.enterInsert || (writing && !text.trim())) {
|
||||
if (writing?.append && text.trim()) {
|
||||
Vim.handleKey(cm, 'G', 'workspace')
|
||||
Vim.handleKey(cm, 'A', 'workspace')
|
||||
} else if (tile?.enterInsert || (writing && !text.trim())) {
|
||||
if (tile) tile.enterInsert = false
|
||||
Vim.handleKey(cm, 'i', 'workspace')
|
||||
}
|
||||
|
||||
+18
-13
@@ -83,6 +83,7 @@
|
||||
<footer class="label">
|
||||
<span>writing — it saves itself</span>
|
||||
<span><span class="kbd">esc</span> back to the notes</span>
|
||||
<span><span class="kbd">⌃ Q</span> back to where you were</span>
|
||||
<span><span class="kbd">:keep</span> make it a document</span>
|
||||
</footer>
|
||||
{:else}
|
||||
@@ -92,31 +93,35 @@
|
||||
|
||||
<style>
|
||||
/*
|
||||
* Hung from under the bar across the frame's opening, as the desktop's
|
||||
* scratchpad slides down from under its bar: hull, a rim, the lower corners
|
||||
* rounded, and the light along its lower edge to say it has the keys.
|
||||
* The upper part of the porthole, come down over what is in it: the same
|
||||
* opening under the bar, the same rounded corners, and the focused
|
||||
* window's lit rim, since it has the keys while it is down. It is
|
||||
* uncovered from the top, as the desktop's scratchpad comes from under its
|
||||
* bar.
|
||||
*/
|
||||
.drawer {
|
||||
--pane-bg: color-mix(in oklab, var(--surface) 40%, var(--hull));
|
||||
position: absolute;
|
||||
top: calc(var(--bar-h) + var(--frame));
|
||||
left: calc(var(--frame) + 24px);
|
||||
right: calc(var(--rail-w) + 24px);
|
||||
top: var(--bar-h);
|
||||
left: var(--frame);
|
||||
right: var(--rail-w);
|
||||
z-index: 25;
|
||||
height: min(62vh, 640px);
|
||||
height: min(62vh, 640px, calc(100% - var(--bar-h) - var(--status-h)));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 14px 20px 12px;
|
||||
background: var(--hull);
|
||||
border: 1px solid var(--surface-alt);
|
||||
border-top: 0;
|
||||
border-bottom-color: var(--light-dim);
|
||||
border-radius: 0 0 var(--frame-radius) var(--frame-radius);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--frame-radius);
|
||||
background:
|
||||
linear-gradient(var(--pane-bg), var(--pane-bg)) padding-box,
|
||||
linear-gradient(135deg, var(--light), var(--light-dim)) border-box;
|
||||
box-shadow: 0 18px 40px color-mix(in oklab, var(--ink) 70%, transparent);
|
||||
animation: lower var(--t-slow) var(--ease-snap) both;
|
||||
}
|
||||
@keyframes lower {
|
||||
from { transform: translateY(-104%); }
|
||||
from { clip-path: inset(0 0 100% 0 round var(--frame-radius)); }
|
||||
to { clip-path: inset(0 0 0 0 round var(--frame-radius)); }
|
||||
}
|
||||
|
||||
header { display: flex; align-items: center; gap: 10px; }
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
actions(index)
|
||||
}}
|
||||
class:active={index === workspace.tree.cursor}
|
||||
class:open={workspace.tiles.all.some((tile) => tile.open?.id === node.id)}
|
||||
class:open={workspace.tiles.every.some((tile) => tile.open?.id === node.id)}
|
||||
class:marked={workspace.tree.isMarked(node.id)}
|
||||
class:nested
|
||||
data-index={index}
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
const { pending = '' }: { pending?: string } = $props()
|
||||
|
||||
const mode = $derived(
|
||||
workspace.editing ? (workspace.vimEnabled ? workspace.vimMode.toUpperCase() : 'EDIT') : 'BROWSE',
|
||||
workspace.editing
|
||||
? workspace.vimEnabled
|
||||
? workspace.vimMode.toUpperCase()
|
||||
: 'EDIT'
|
||||
: workspace.tiles.planning
|
||||
? 'PLAN'
|
||||
: 'BROWSE',
|
||||
)
|
||||
|
||||
// The path is the file name here: work/einarbeiten/01-was-ist-eebus.
|
||||
|
||||
+25
-13
@@ -82,7 +82,7 @@ function focusPane(pane: 'list' | 'document') {
|
||||
}
|
||||
|
||||
/*
|
||||
* Alt + h/j/k/l: the focus goes to the tile that way, out of the editor
|
||||
* Ctrl + 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
|
||||
* goes that way instead, swapping places with the one it lands on.
|
||||
*/
|
||||
@@ -93,7 +93,8 @@ function tileKey(direction: Direction, shift: boolean) {
|
||||
}
|
||||
const next = neighbour(dwindle(workspace.tiles.all.length), workspace.tiles.focused, direction)
|
||||
if (next < 0) {
|
||||
if (direction === 'h' && !shift) focusPane('list')
|
||||
// The plan screen has no list beside it.
|
||||
if (direction === 'h' && !shift && !workspace.tiles.planning) focusPane('list')
|
||||
return
|
||||
}
|
||||
if (!shift) return void focusTile(next)
|
||||
@@ -103,15 +104,24 @@ function tileKey(direction: Direction, shift: boolean) {
|
||||
workspace.tiles.focused = next
|
||||
}
|
||||
|
||||
/** The drawer up, and the keys back where they were: in the editor, when a document was being written. */
|
||||
async function raise() {
|
||||
await workspace.scratch.close()
|
||||
if (!workspace.scratch.open && workspace.editing && workspace.pane === 'document') hooks.focusEditor()
|
||||
}
|
||||
|
||||
export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
||||
always: {
|
||||
save: () => {
|
||||
if (workspace.drafts.writing) guard(() => workspace.drafts.saveWriting())
|
||||
else if (workspace.open) guard(() => workspace.drafts.save())
|
||||
},
|
||||
close: () => void runCommand('close'),
|
||||
focus: (key) => tileKey(key.slice(-1) as Direction, false),
|
||||
swap: (key) => tileKey(key.slice(-1) as Direction, true),
|
||||
// Under the drawer the tiles are out of sight, so the keys are the note's.
|
||||
focus: (key) => (workspace.scratch.open ? false : tileKey(key.slice(-1) as Direction, false)),
|
||||
swap: (key) => (workspace.scratch.open ? false : tileKey(key.slice(-1).toLowerCase() as Direction, true)),
|
||||
// Ctrl-Q: down with the last note being written, and up again back to
|
||||
// what was being done — the document still in the mode it was left in.
|
||||
scratch: () => guard(() => (workspace.scratch.open ? raise() : workspace.scratch.jot())),
|
||||
},
|
||||
|
||||
sheet: {
|
||||
@@ -318,7 +328,7 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
||||
},
|
||||
line: () => line(':'),
|
||||
help: () => void runCommand('help'),
|
||||
raise: () => void workspace.scratch.close(),
|
||||
raise: () => guard(raise),
|
||||
nothing: () => {},
|
||||
},
|
||||
|
||||
@@ -355,6 +365,8 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
||||
open: (key) => {
|
||||
const enter = key === 'Enter' || key === 'Shift-Enter'
|
||||
if (enter && reading() && hooks.fileAction()) return
|
||||
// Reading, `l` is the tile to the right, as `h` is the one to the left.
|
||||
if (!enter && workspace.pane === 'document' && workspace.open) return tileKey('l', false)
|
||||
// Shift-Enter, like `L`: beside what is open, in a tile of its own.
|
||||
if (key === 'Shift-Enter' && !reading()) return guard(() => workspace.tiles.openBeside())
|
||||
const shown = workspace.tiles.all.findIndex((tile) => tile.open && tile.open.id === workspace.tree.selected?.id)
|
||||
@@ -364,10 +376,10 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
||||
beside: () => {
|
||||
if (!reading()) guard(() => workspace.tiles.openBeside())
|
||||
},
|
||||
// Leftwards at every depth: from the document to the list, from the
|
||||
// list up a level.
|
||||
// Leftwards at every depth: from a document to the tile left of it, from
|
||||
// the leftmost to the list, from the list up a level.
|
||||
left: () => {
|
||||
if (workspace.open && workspace.pane === 'document') focusPane('list')
|
||||
if (workspace.open && workspace.pane === 'document') tileKey('h', false)
|
||||
else guard(() => workspace.tree.up())
|
||||
},
|
||||
sideways: (key) => {
|
||||
@@ -516,7 +528,7 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
||||
open: () => void workspace.guard(() => workspace.diary.openCalendarDay()).then(() => hooks.focusEditor()),
|
||||
},
|
||||
|
||||
/** What every view shares: `q` closes its tile, back to the one before. */
|
||||
/** What every view shares: `q` closes a card's back, and from the others goes back to the documents. */
|
||||
views: {
|
||||
close: () => void runCommand('close'),
|
||||
},
|
||||
@@ -549,9 +561,9 @@ export const actions: { [C in Context]: Record<ActionOf<C>, Run> } = {
|
||||
agenda: () => void runCommand('agenda'),
|
||||
today: () => void runCommand('today'),
|
||||
calendar: () => void runCommand('cal'),
|
||||
// `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
|
||||
// ever go to their tile.
|
||||
// `S`, as the desktop's special workspace is bound: the plan screen —
|
||||
// the board, the agenda and the calendar — and back to the documents.
|
||||
// `b`, `t` and `C` go there too, to their own view.
|
||||
plan: () => void runCommand('plan'),
|
||||
line: () => line(':'),
|
||||
help: () => void runCommand('help'),
|
||||
|
||||
@@ -591,14 +591,14 @@ export const commands: CommandSpec[] = [
|
||||
{
|
||||
name: 'board',
|
||||
group: 'board',
|
||||
help: "this space's board, in a tile — the last one shown when this space has none",
|
||||
help: "this space's board, on the plan screen — the last one shown when this space has none",
|
||||
run: () => workspace.guard(() => workspace.tiles.show('board')),
|
||||
},
|
||||
{
|
||||
name: 'plan',
|
||||
aliases: ['special'],
|
||||
group: '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',
|
||||
help: 'the plan screen: the board, the agenda and the calendar, in place of the documents — S goes there and back again; 1 … 9 on the board picks the board',
|
||||
run: () => workspace.guard(() => workspace.tiles.togglePlan()),
|
||||
},
|
||||
{
|
||||
@@ -874,7 +874,7 @@ export const commands: CommandSpec[] = [
|
||||
name: 'close',
|
||||
aliases: ['clo'],
|
||||
group: 'tiles',
|
||||
help: 'close the focused tile — Alt-q; asks when its draft could not be written',
|
||||
help: 'close the focused tile — q while reading; on the plan screen, back to the documents; asks when its draft could not be written',
|
||||
run: () =>
|
||||
workspace.guard(async () => {
|
||||
await workspace.drafts.flushTile()
|
||||
|
||||
+11
-11
@@ -46,7 +46,7 @@ export interface Binding {
|
||||
context: Context
|
||||
/**
|
||||
* What is pressed, as `keyName` spells it: `j`, `G`, `Enter`, `Space`,
|
||||
* `Shift-Tab`, `Ctrl-d`, `Alt-h`, `1`, `Shift-1`, a two-key `gg`, or `*`
|
||||
* `Shift-Tab`, `Ctrl-d`, `Ctrl-H`, `1`, `Shift-1`, a two-key `gg`, or `*`
|
||||
* for any key without Ctrl, Alt or Meta. A binding without keys only
|
||||
* describes — a mouse gesture, the editor's own keymap.
|
||||
*/
|
||||
@@ -78,12 +78,11 @@ export const bindings = [
|
||||
{ context: 'global', keys: ['Tab', 'Shift-Tab'], action: 'cycle', label: 'Tab · Shift-Tab', help: 'next / previous space' },
|
||||
{ context: 'global', keys: ['w'], action: 'overview', help: 'overview of every space — hjkl, then Enter' },
|
||||
{ context: 'global', keys: ['f'], action: 'fullscreen', help: 'fullscreen the focused pane or tile; again or Esc to leave' },
|
||||
{ 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', essential: true },
|
||||
{ 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: ['Ctrl-h', 'Ctrl-j', 'Ctrl-k', 'Ctrl-l'], action: 'focus', typing: true, label: 'Ctrl-h/j/k/l', help: 'focus the tile that way, from the editor too — left of the leftmost is the list; h / l do the same while reading', essential: true },
|
||||
{ context: 'always', keys: ['Ctrl-H', 'Ctrl-J', 'Ctrl-K', 'Ctrl-L'], action: 'swap', typing: true, label: 'Ctrl-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: 'global', keys: ['gt'], action: 'nextTile', label: 'gt / gT', help: 'next / previous tile — a view too' },
|
||||
{ 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: 'tree', keys: ['j', 'ArrowDown'], action: 'down', label: 'j / k · ↓ ↑', help: 'move down / up the list — scroll the document when it has focus', essential: true },
|
||||
{ context: 'tree', keys: ['k', 'ArrowUp'], action: 'up' },
|
||||
{ context: 'tree', keys: ['gg'], action: 'top', label: 'gg / G', help: 'first / last row, or top / bottom of the document — a first g, d, ] or [ waits a second in the status line' },
|
||||
@@ -109,14 +108,14 @@ export const bindings = [
|
||||
{ context: 'tree', keys: ['za'], action: 'files', help: "a document's own files, folded open under it in the list — again folds them away; p drops marked files in there" },
|
||||
{ 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: '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', essential: true },
|
||||
{ context: 'global', keys: ['t'], action: 'agenda', help: 'agenda — late, due this week, in hand, on every board — in a tile of its own', essential: true },
|
||||
{ context: 'global', keys: ['S'], action: 'plan', help: 'the plan screen: the board, the agenda and the calendar, in place of the documents and the list — S again goes back to the documents as they were', essential: true },
|
||||
{ context: 'global', keys: ['t'], action: 'agenda', help: 'agenda — late, due this week, in hand, on every board — on the plan screen', essential: true },
|
||||
{ context: 'global', keys: ['T'], action: 'today', help: "today's diary entry, started if there is none — from templates/day when the diary has one", essential: true },
|
||||
{ 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: 'global', keys: ['C'], action: 'calendar', help: 'the diary by month, on the plan screen: h/l a day, j/k a week, H/L a month, . today, Enter opens or starts the day among the documents' },
|
||||
{ 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: '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', essential: true },
|
||||
{ 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: 'global', keys: ['b'], action: 'board', help: "the board, on the plan screen: this space's, or the last one shown — 1 … 9 there shows another's" },
|
||||
{ context: 'tree', keys: ['c'], action: 'capture', help: "capture a card into this space's inbox, without leaving here — reading a document, a card about it; Tab on the line makes it a note, or a line in today's entry", essential: true },
|
||||
{ context: 'tree', keys: ['q'], action: 'close', label: 'Esc · q', help: 'close the document, and its tile when there are several — Esc first puts down what m picked up', essential: true },
|
||||
{ context: 'tree', keys: ['Escape'], action: 'escape' },
|
||||
@@ -136,12 +135,13 @@ export const bindings = [
|
||||
// --- the workspace around the panes ------------------------------------------
|
||||
{ context: 'workspace', keys: ['Escape'], action: 'escape', typing: true },
|
||||
{ context: 'workspace', keys: ['Q'], action: 'scratch', section: 'scratch', help: 'lower the scratchpad over whatever is on screen, or raise it again — :scratch <text> takes a note without it', essential: true },
|
||||
{ context: 'always', keys: ['Ctrl-q'], action: 'scratch', typing: true, section: 'scratch', label: 'Ctrl-Q', help: 'from anywhere, the editor too: the scratchpad down with the last note being written, and again back to where you were, still writing — Firefox quits on it instead', essential: true },
|
||||
// While a document is being written, a key that lands outside the editor
|
||||
// hands the focus back to it instead of walking the list unseen.
|
||||
{ context: 'writing', keys: ['*'], action: 'refocus' },
|
||||
|
||||
// --- what the views share: the board, the agenda, the calendar, a card's back
|
||||
{ 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" },
|
||||
{ context: 'views', keys: ['q', 'Escape'], action: 'close', label: 'q · Esc', caps: ['q'], hint: 'close', help: "on the plan screen: from a card's back to the board, from the board, the agenda or the calendar back to the documents" },
|
||||
|
||||
// --- 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, to the tile beside' },
|
||||
@@ -156,7 +156,7 @@ export const bindings = [
|
||||
{ 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: ['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, opened among the documents; S 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: ['D'], action: 'due', hint: 'due', help: 'due date: fri, tm, +3d, 10-31 …' },
|
||||
{ context: 'board', keys: ['R'], action: 'rename', hint: 'rename', help: 'rename the card, or the list when it is empty' },
|
||||
@@ -183,7 +183,7 @@ export const bindings = [
|
||||
{ context: 'card', keys: ['D'], action: 'due', hint: 'due' },
|
||||
{ context: 'card', keys: ['H'], action: 'sendLeft', label: 'H / L', caps: ['H', 'L'], hint: 'send', help: 'send the card to the list left / right' },
|
||||
{ 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, opened among the documents; S comes back" },
|
||||
{ context: 'card', keys: [':'], action: 'line' },
|
||||
|
||||
// --- the scratchpad, while it is down --------------------------------------------
|
||||
|
||||
+18
-2
@@ -3,7 +3,7 @@
|
||||
* contexts in keymap.ts, the topmost first. A key goes down the stack until
|
||||
* a context takes it.
|
||||
*
|
||||
* always Ctrl-S, Alt-h/j/k/l — heard in the editor too
|
||||
* always Ctrl-S, Ctrl-Q, Ctrl-h/j/k/l — heard in the editor too
|
||||
* a sheet help, info, trash …; nothing under one hears a key
|
||||
* drawer the scratchpad, while it is down
|
||||
* workspace Esc out of fullscreen, Q
|
||||
@@ -52,7 +52,7 @@ const lone = new Set(['Shift', 'Control', 'Alt', 'AltGraph', 'Meta', 'CapsLock',
|
||||
|
||||
/**
|
||||
* A key as the table spells it: `j`, `G`, `Enter`, `Shift-Tab`, `Ctrl-d`,
|
||||
* `Alt-Shift-h`, `1`, `Shift-1`. Digits, and letters under Alt, are read
|
||||
* `Ctrl-H`, `1`, `Shift-1`. Digits, and letters under Alt, are read
|
||||
* from `event.code`, so they are the same key on every layout — and on a
|
||||
* Mac, where Alt turns h into ˙. Shift is only spelled out where the key
|
||||
* itself does not already say it. A modifier on its own is no key at all.
|
||||
@@ -134,3 +134,19 @@ export function dispatch(event: KeyboardEvent, pending: string, line: boolean, t
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* A Ctrl key heard in every context, taken before the editor sees it: vim
|
||||
* has uses of its own for Ctrl-h and Ctrl-k, and what it handles never
|
||||
* reaches `dispatch`. Ctrl-S is left to the editor, which saves its own
|
||||
* text. True when it was taken.
|
||||
*/
|
||||
export function claim(event: KeyboardEvent): boolean {
|
||||
const name = keyName(event)
|
||||
if (!name?.startsWith('Ctrl-')) return false
|
||||
const binding = find('always', name)
|
||||
if (!binding?.typing || binding.action === 'save' || !run(binding, name, event)) return false
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return true
|
||||
}
|
||||
@@ -334,7 +334,7 @@ export class Cards {
|
||||
/** The cards about each document open in a tile, read again. */
|
||||
async #reloadCardsAbout() {
|
||||
await Promise.all(
|
||||
this.ws.tiles.all.map(async (tile) => {
|
||||
this.ws.tiles.every.map(async (tile) => {
|
||||
const id = tile.open?.kind === 'document' ? tile.open.id : null
|
||||
if (id === null) return
|
||||
const cards = await api.nodeCards(id).catch(() => null)
|
||||
|
||||
@@ -89,7 +89,7 @@ export class Drafts {
|
||||
/** Back online: what is waiting goes now rather than at the next retry. */
|
||||
watchConnection() {
|
||||
window.addEventListener('online', () => {
|
||||
for (const tile of this.ws.tiles.all) {
|
||||
for (const tile of this.ws.tiles.every) {
|
||||
if (!tile.dirty) continue
|
||||
tile.stopAutosave()
|
||||
void this.#writeBack(tile)
|
||||
@@ -107,7 +107,7 @@ export class Drafts {
|
||||
* unsaved work without a word.
|
||||
*/
|
||||
async flush() {
|
||||
for (const tile of this.ws.tiles.all) await this.flushTile(tile)
|
||||
for (const tile of this.ws.tiles.every) await this.flushTile(tile)
|
||||
}
|
||||
|
||||
/** The same for one tile only, as it is about to show something else. */
|
||||
@@ -173,7 +173,7 @@ export class Drafts {
|
||||
* then refused, and `:e!` takes the rewritten text.
|
||||
*/
|
||||
async reread() {
|
||||
for (const tile of this.ws.tiles.all) {
|
||||
for (const tile of this.ws.tiles.every) {
|
||||
if (!tile.open || tile.editing || tile.dirty) continue
|
||||
const node = await api.getNode(tile.open.id).catch(() => null)
|
||||
if (!node) continue
|
||||
@@ -236,7 +236,7 @@ export class Drafts {
|
||||
|
||||
/** Every tile showing `id` reads it again, after it was written somewhere else. */
|
||||
async rereadNode(id: number) {
|
||||
for (const tile of this.ws.tiles.all) {
|
||||
for (const tile of this.ws.tiles.every) {
|
||||
if (tile.open?.id !== id || tile.dirty) continue
|
||||
const node = await api.getNode(id)
|
||||
tile.open = node
|
||||
|
||||
@@ -43,6 +43,18 @@ export class Scratchpad {
|
||||
this.open = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Ctrl-Q, from anywhere: the drawer down with the note written last being
|
||||
* written on, at its end — or, when it is down, up again.
|
||||
*/
|
||||
async jot() {
|
||||
if (this.open) return this.close()
|
||||
await this.toggle()
|
||||
this.cursor = 0
|
||||
this.editNote()
|
||||
if (this.ws.drafts.writing) this.ws.drafts.writing.append = true
|
||||
}
|
||||
|
||||
/** `o`: a new note at the top, written straight away. */
|
||||
newNote() {
|
||||
this.#writeNote(null, '')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isContainer, type WorkspaceNode } from '../api'
|
||||
import { formatDay } from '../util'
|
||||
import type { Workspace } from '../store.svelte'
|
||||
import type { Pane, Workspace } from '../store.svelte'
|
||||
|
||||
/**
|
||||
* What a tile shows: a node of the tree — a document or a file — or one of
|
||||
@@ -68,9 +68,10 @@ export class Tile {
|
||||
}
|
||||
|
||||
/**
|
||||
* The tiles on screen and which has the focus: opened, closed, walked, and
|
||||
* the views — the board, the agenda, the calendar, a card's back — put up
|
||||
* among them.
|
||||
* The tiles on screen and which has the focus: opened, closed, walked. There
|
||||
* are two screens of them, and one is shown at a time: the documents, beside
|
||||
* the list, and the plan — the board, the agenda, the calendar, a card's
|
||||
* back — which `S` goes to and comes back from.
|
||||
*/
|
||||
export class Tiles {
|
||||
readonly ws: Workspace
|
||||
@@ -91,9 +92,19 @@ export class Tiles {
|
||||
return this.all[this.focused] ?? this.all[0]
|
||||
}
|
||||
|
||||
/** Every tile showing `id`. */
|
||||
/** The plan screen is on show, and the documents are put away behind it. */
|
||||
planning = $state(false)
|
||||
/** The screen not on show, as it was left. */
|
||||
#away = $state<{ all: Tile[]; focused: number; trail: Tile[]; pane: Pane }>({ all: [], focused: 0, trail: [], pane: 'list' })
|
||||
|
||||
/** The tiles of both screens: a draft behind the plan is still written. */
|
||||
get every(): Tile[] {
|
||||
return [...this.all, ...this.#away.all]
|
||||
}
|
||||
|
||||
/** Every tile showing `id`, on either screen. */
|
||||
showing(id: number): Tile[] {
|
||||
return this.all.filter((tile) => tile.open?.id === id)
|
||||
return this.every.filter((tile) => tile.open?.id === id)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -158,8 +169,13 @@ export class Tiles {
|
||||
if (this.current.empty) this.ws.pane = 'list'
|
||||
}
|
||||
|
||||
/** `:close`: the focused tile goes, or with only one, what it shows. */
|
||||
/**
|
||||
* `:close`: the focused tile goes, or with only one, what it shows. On the
|
||||
* plan only a card's back goes; closing the board, the agenda or the
|
||||
* calendar goes back to the documents.
|
||||
*/
|
||||
close() {
|
||||
if (this.planning && this.current.kind !== 'card') return this.leavePlan()
|
||||
this.ws.drafts.leaveCard()
|
||||
this.current.clear()
|
||||
this.prune()
|
||||
@@ -201,46 +217,60 @@ export class Tiles {
|
||||
|
||||
/*
|
||||
* The views — the board, the agenda, the calendar, a card's back — are
|
||||
* tiles, opened beside what is on screen. Each is read again whenever it is
|
||||
* gone to: it is looked at in passing, and what it says should be what is
|
||||
* true now.
|
||||
* the tiles of the plan screen. Each is read again whenever it is gone to:
|
||||
* it is looked at in passing, and what it says should be what is true now.
|
||||
*/
|
||||
|
||||
/** `b`, `t`, `C`, Enter on a card: to that view's tile, opening it first when it is not up. */
|
||||
/** The views the plan screen holds, in the order they are tiled. */
|
||||
static readonly PLAN: View[] = ['board', 'agenda', 'calendar']
|
||||
|
||||
/** The other screen on show, and this one put away as it is. */
|
||||
swap() {
|
||||
const away = this.#away
|
||||
this.#away = { all: this.all, focused: this.focused, trail: this.trail, pane: this.ws.pane }
|
||||
this.all = away.all.length > 0 ? away.all : [new Tile()]
|
||||
this.focused = Math.min(away.focused, this.all.length - 1)
|
||||
this.trail = away.trail
|
||||
this.ws.pane = away.pane
|
||||
this.planning = !this.planning
|
||||
}
|
||||
|
||||
/** To the plan screen, with every view on it, from wherever the documents were. */
|
||||
enterPlan() {
|
||||
if (this.planning) return
|
||||
this.ws.drafts.leaveCard()
|
||||
this.swap()
|
||||
const cards = this.all.filter((tile) => tile.kind === 'card')
|
||||
this.all = [...Tiles.PLAN.map((kind) => this.view(kind) ?? new Tile(kind)), ...cards]
|
||||
this.focused = Math.min(this.focused, this.all.length - 1)
|
||||
this.ws.pane = 'document'
|
||||
}
|
||||
|
||||
/** Back to the documents, as they were left. */
|
||||
leavePlan() {
|
||||
if (!this.planning) return
|
||||
this.ws.drafts.leaveCard()
|
||||
this.swap()
|
||||
// What was closed behind the plan — deleted, moved away — is gone from here too.
|
||||
this.prune()
|
||||
if (!this.current.empty) this.focusOn(this.focused)
|
||||
}
|
||||
|
||||
/** `b`, `t`, `C`, Enter on a card: to the plan screen, and that view on it focused. */
|
||||
async show(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.current.empty) this.current.kind = kind
|
||||
else this.all.push(new Tile(kind))
|
||||
}
|
||||
// Coming to the plan, every view on it is read again, not only this one.
|
||||
const kinds = this.planning ? [kind] : Tiles.PLAN
|
||||
await Promise.all(kinds.map((each) => this.loadView(each)))
|
||||
this.enterPlan()
|
||||
if (!this.view(kind)) this.all.push(new Tile(kind))
|
||||
this.focusOn(this.all.indexOf(this.view(kind)!))
|
||||
if (kind === 'board' && this.ws.cards.boardSpaceId === null) this.ws.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.
|
||||
*/
|
||||
/** `S`: the plan screen with the board focused, or back to the documents. */
|
||||
async togglePlan() {
|
||||
if (Tiles.PLAN.every((kind) => this.view(kind))) {
|
||||
for (const tile of this.all) if (tile.kind !== 'node') tile.clear()
|
||||
this.prune()
|
||||
if (!this.current.empty) this.focusOn(this.focused)
|
||||
return
|
||||
}
|
||||
await Promise.all(Tiles.PLAN.map((kind) => this.loadView(kind)))
|
||||
const current = this.current
|
||||
const missing = Tiles.PLAN.filter((kind) => !this.view(kind)).map((kind) => new Tile(kind))
|
||||
this.all = [...missing, ...this.all]
|
||||
this.focused = this.all.indexOf(current)
|
||||
this.prune()
|
||||
this.focusOn(this.all.indexOf(this.view('board')!))
|
||||
if (this.ws.cards.boardSpaceId === null) this.ws.say('no space has a board — :set board turns one on', 'error')
|
||||
if (this.planning) return this.leavePlan()
|
||||
await this.show('board')
|
||||
}
|
||||
|
||||
/** Read again what a view shows. */
|
||||
@@ -253,7 +283,7 @@ export class Tiles {
|
||||
}
|
||||
}
|
||||
|
||||
/** Every view on screen read again, after a space switch brought them. */
|
||||
/** Every view on the plan read again, after a space switch. */
|
||||
async loadViews() {
|
||||
if (this.view('board') || this.view('card')) await this.ws.cards.loadBoard()
|
||||
if (this.view('calendar')) await this.loadView('calendar')
|
||||
|
||||
@@ -5,9 +5,10 @@ import type { Workspace, Pane } from '../store.svelte'
|
||||
|
||||
/**
|
||||
* Where a space was left: the folder, the tiles, the row, the pane. A tile
|
||||
* 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.
|
||||
* is the id of the node it shows; the name of a view is what a spot held
|
||||
* while views were tiled among the documents, and is passed over now.
|
||||
* `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 = {
|
||||
parentId: number
|
||||
@@ -144,47 +145,49 @@ export class Tree {
|
||||
if (start) await this.enterSpace(start.id)
|
||||
}
|
||||
|
||||
/** `views` false: the views are leaving with the focus, and are not left behind. */
|
||||
#rememberSpot(views = true) {
|
||||
/** Where this space is left, its documents on screen. */
|
||||
#rememberSpot() {
|
||||
if (this.spaceId === null || this.parentId === null) return
|
||||
// A card's back is the card under the board's cursor, which is not kept.
|
||||
const kept = this.ws.tiles.all.filter((tile) => !tile.empty && (tile.kind === 'node' || (views && tile.kind !== 'card')))
|
||||
const kept = this.ws.tiles.all.filter((tile) => tile.open)
|
||||
this.#spots[this.spaceId] = {
|
||||
parentId: this.parentId,
|
||||
openId: this.ws.open?.id ?? null,
|
||||
cursor: this.cursor,
|
||||
pane: this.ws.pane,
|
||||
tiles: kept.map((tile) => (tile.kind === 'node' ? tile.open!.id : tile.kind)),
|
||||
tiles: kept.map((tile) => tile.open!.id),
|
||||
focused: Math.max(kept.indexOf(this.ws.tile), 0),
|
||||
}
|
||||
localStorage.setItem('diary:spots', JSON.stringify(this.#spots))
|
||||
}
|
||||
|
||||
async enterSpace(id: number) {
|
||||
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.
|
||||
* The views look at every board and the whole diary, so the plan screen
|
||||
* stays as it is — a digit on the board shows that space's board — and
|
||||
* it is the documents behind it that are this space's.
|
||||
*/
|
||||
let carried: Tile[] = []
|
||||
let carriedFocus: Tile | undefined
|
||||
const planning = this.ws.tiles.planning
|
||||
if (planning) this.ws.tiles.swap()
|
||||
try {
|
||||
await this.#enterSpace(id)
|
||||
} finally {
|
||||
if (planning) this.ws.tiles.swap()
|
||||
}
|
||||
await this.ws.tiles.loadViews()
|
||||
}
|
||||
|
||||
async #enterSpace(id: number) {
|
||||
const leaving = this.spaceId !== null && this.spaceId !== id
|
||||
if (leaving) {
|
||||
await this.ws.drafts.flush()
|
||||
const carry = this.ws.focus !== 'list' && this.ws.focus !== 'node'
|
||||
this.#rememberSpot(!carry)
|
||||
this.#rememberSpot()
|
||||
const from = this.spaces.findIndex((s) => s.id === this.spaceId)
|
||||
const to = this.spaces.findIndex((s) => s.id === id)
|
||||
this.ws.switchDirection = to >= from ? 1 : -1
|
||||
if (carry) {
|
||||
carried = this.ws.tiles.all.filter((tile) => tile.kind !== 'node' && tile.kind !== 'card')
|
||||
carriedFocus = this.ws.tile.kind === 'card' ? (this.ws.tiles.view('board') ?? carried[0]) : this.ws.tile
|
||||
}
|
||||
// Each space has tiles of its own; these come back with this one.
|
||||
for (const tile of this.ws.tiles.all) tile.stopAutosave()
|
||||
this.ws.tiles.replace(carried, carriedFocus)
|
||||
this.ws.pane = carried.length > 0 ? 'document' : 'list'
|
||||
this.ws.tiles.replace([])
|
||||
this.ws.pane = 'list'
|
||||
}
|
||||
this.spaceId = id
|
||||
localStorage.setItem('diary:space', String(id))
|
||||
@@ -204,12 +207,7 @@ export class Tree {
|
||||
spot.tiles ?? spot.open ?? (spot.openId !== null && this.nodes.some((n) => n.id === spot.openId) ? [spot.openId] : [])
|
||||
const restored: Tile[] = []
|
||||
for (const entry of entries) {
|
||||
if (typeof entry === 'string') {
|
||||
let tile = this.ws.tiles.view(entry)
|
||||
if (!tile) this.ws.tiles.all.push((tile = new Tile(entry)))
|
||||
restored.push(tile)
|
||||
continue
|
||||
}
|
||||
if (typeof entry === 'string') continue
|
||||
const empty = this.ws.tiles.all.findIndex((tile) => tile.empty)
|
||||
if (empty >= 0) this.ws.tiles.focused = empty
|
||||
else {
|
||||
@@ -224,25 +222,23 @@ export class Tree {
|
||||
}
|
||||
}
|
||||
this.ws.tiles.prune()
|
||||
const back = carriedFocus ?? restored[Math.min(spot.focused ?? 0, restored.length - 1)]
|
||||
const back = restored[Math.min(spot.focused ?? 0, restored.length - 1)]
|
||||
if (back && this.ws.tiles.all.includes(back)) {
|
||||
this.ws.tiles.focusOn(this.ws.tiles.all.indexOf(back))
|
||||
if (!carriedFocus) this.ws.pane = spot.pane
|
||||
this.ws.pane = spot.pane
|
||||
} else {
|
||||
this.cursor = Math.min(spot.cursor, Math.max(this.nodes.length - 1, 0))
|
||||
}
|
||||
}
|
||||
this.ws.tiles.trail = []
|
||||
await this.ws.tiles.loadViews()
|
||||
}
|
||||
|
||||
/** Show the children of `id`, which must be a space or a folder. */
|
||||
async goTo(id: number, keep?: number) {
|
||||
await this.ws.drafts.flush()
|
||||
const folder = await api.getNode(id)
|
||||
// Walking into another space by a link leaves this one where it was; the
|
||||
// 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)
|
||||
// Walking into another space by a link leaves this one where it was.
|
||||
if (this.spaceId !== null && folder.path[0]?.id !== this.spaceId) this.#rememberSpot()
|
||||
this.parentId = id
|
||||
this.parentSealed = folder.sealed
|
||||
this.query = ''
|
||||
@@ -347,7 +343,14 @@ export class Tree {
|
||||
}
|
||||
|
||||
async openNode(id: number, edit = false) {
|
||||
if (this.ws.open?.id !== id) await this.ws.drafts.flushTile()
|
||||
/*
|
||||
* The plan screen holds the views alone: what is opened from it — a
|
||||
* card's notes, a day of the calendar, a search — opens on the documents'
|
||||
* screen, beside what is there, and `S` goes back.
|
||||
*/
|
||||
const fromPlan = this.ws.tiles.planning
|
||||
if (fromPlan) this.ws.tiles.leavePlan()
|
||||
else if (this.ws.open?.id !== id) await this.ws.drafts.flushTile()
|
||||
|
||||
const node = await api.getNode(id)
|
||||
if (isContainer(node)) {
|
||||
@@ -362,10 +365,7 @@ export class Tree {
|
||||
if (edit && node.kind === 'document') this.ws.editing = true
|
||||
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.ws.tile.kind !== 'node') this.ws.tiles.add()
|
||||
if (fromPlan && !this.ws.tile.empty) this.ws.tiles.add()
|
||||
|
||||
this.ws.open = node
|
||||
this.ws.draft = { name: node.name, body: node.body, created_at: node.created_at }
|
||||
@@ -549,7 +549,7 @@ export class Tree {
|
||||
async deleteSpace(id: number) {
|
||||
const { removed } = await api.deleteNode(id)
|
||||
this.lastDeleted = id
|
||||
for (const tile of this.ws.tiles.all) tile.clear()
|
||||
for (const tile of this.ws.tiles.every) if (tile.kind === 'node') tile.clear()
|
||||
this.ws.tiles.prune()
|
||||
this.ws.cards.rawBoard = null
|
||||
this.ws.cards.boardSpaceId = null
|
||||
@@ -585,7 +585,7 @@ export class Tree {
|
||||
*/
|
||||
async moveNodes(nodes: NodeSummary[], parent: number, where = 'here') {
|
||||
if (nodes.length === 0) return this.ws.say('nothing marked — press m on a row first', 'error')
|
||||
const leaving = this.ws.tiles.all.filter((tile) => nodes.some((n) => n.id === tile.open?.id))
|
||||
const leaving = this.ws.tiles.every.filter((tile) => nodes.some((n) => n.id === tile.open?.id))
|
||||
for (const tile of leaving) {
|
||||
await this.ws.drafts.flushTile(tile)
|
||||
tile.clear()
|
||||
|
||||
@@ -51,6 +51,6 @@ export class Vault {
|
||||
failed > 0 || shared > 0 ? 'error' : undefined,
|
||||
)
|
||||
await this.ws.tree.refresh()
|
||||
for (const tile of this.ws.tiles.all) if (tile.open?.kind === 'file') tile.open = await api.getNode(tile.open.id)
|
||||
for (const tile of this.ws.tiles.every) if (tile.open?.kind === 'file') tile.open = await api.getNode(tile.open.id)
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export class Workspace {
|
||||
// the draft included, so pick up where it stopped rather than start over.
|
||||
if (this.tree.spaces.length > 0) {
|
||||
await this.tree.refresh()
|
||||
for (const tile of this.tiles.all) {
|
||||
for (const tile of this.tiles.every) {
|
||||
if (tile.open && tile.dirty) await this.guard(() => this.drafts.save({ quiet: true, tile }))
|
||||
}
|
||||
if (this.drafts.writing?.dirty) await this.guard(() => this.drafts.saveWriting())
|
||||
@@ -178,7 +178,8 @@ export class Workspace {
|
||||
this.vault.status = null
|
||||
this.tree.spaces = []
|
||||
this.tree.nodes = []
|
||||
for (const tile of this.tiles.all) tile.clear()
|
||||
if (this.tiles.planning) this.tiles.leavePlan()
|
||||
for (const tile of this.tiles.every) tile.clear()
|
||||
this.tiles.prune()
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export class Writing {
|
||||
dirty = $state(false)
|
||||
/** The text was found in this browser, newer than what the server has. */
|
||||
readonly recovered: boolean
|
||||
/** Opened to be typed into straight away: the editor starts in insert mode, at the end. */
|
||||
append = false
|
||||
|
||||
#write: (text: string) => Promise<void>
|
||||
#fail: (error: unknown, retry: number) => void
|
||||
|
||||
Reference in new issue
Block a user