added document move

This commit is contained in:
nvrl committed 2026-09-21 00:05:24 +02:00
1 parent 714a84089a
commit 1a37420461
4 files changed
+57 -4

No files matched your search

+9
View File
@@ -276,6 +276,15 @@
event.preventDefault() event.preventDefault()
void workspace.guard(() => workspace.openSelected()) void workspace.guard(() => workspace.openSelected())
break break
case 'm':
// Pick a node up here, walk to a folder, put it down with `p`.
event.preventDefault()
workspace.mark()
break
case 'p':
event.preventDefault()
void workspace.guard(() => workspace.drop())
break
case 'o': case 'o':
event.preventDefault() event.preventDefault()
void runCommand('new') void runCommand('new')
+1
View File
@@ -12,6 +12,7 @@
['O', 'new folder here'], ['O', 'new folder here'],
['i · a', 'edit the open document'], ['i · a', 'edit the open document'],
['R', 'rename what is marked — document, folder or card'], ['R', 'rename what is marked — document, folder or card'],
['m / p', 'pick a node up, then drop it into the folder you walk to'],
['b', "open this space's board"], ['b', "open this space's board"],
['Esc · q', 'back out to the list'], ['Esc · q', 'back out to the list'],
['/', 'full-text search, inside the current space'], ['/', 'full-text search, inside the current space'],
+7 -2
View File
@@ -30,7 +30,7 @@
<div class="sidebar"> <div class="sidebar">
<div class="head"> <div class="head">
<span class="accent">~/{workspace.space?.slug ?? 'diary'}</span> <span class="accent name">~/{workspace.space?.slug ?? 'diary'}</span>
<span class="faint count"> <span class="faint count">
{workspace.nodes.length} {workspace.nodes.length === 1 ? 'item' : 'items'} {workspace.nodes.length} {workspace.nodes.length === 1 ? 'item' : 'items'}
</span> </span>
@@ -79,6 +79,7 @@
onclick={() => pick(index)} onclick={() => pick(index)}
> >
<span class="caret">{index === workspace.cursor ? '>' : ' '}</span> <span class="caret">{index === workspace.cursor ? '>' : ' '}</span>
{#if workspace.marked?.id === node.id}<span class="marked" title="marked — p drops it in a folder">m</span>{/if}
<span class="time">{aside(node)}</span> <span class="time">{aside(node)}</span>
<span class="title">{node.kind === 'document' ? '' : '/'}{label(node)}</span> <span class="title">{node.kind === 'document' ? '' : '/'}{label(node)}</span>
{#if node.shared}<span class="shared" title="shared">◉</span>{/if} {#if node.shared}<span class="shared" title="shared">◉</span>{/if}
@@ -158,9 +159,13 @@
.time { color: var(--fg-faint); flex: none; font-size: 12px; } .time { color: var(--fg-faint); flex: none; font-size: 12px; }
.title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; } .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.shared { color: var(--accent); flex: none; } .shared { color: var(--accent); flex: none; }
.marked { color: var(--warn); flex: none; font-weight: 700; }
.empty { padding: 12px 10px; } .empty { padding: 12px 10px; }
.count { flex: 1; text-align: right; overflow: hidden; white-space: nowrap; } .name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The count is short and fixed-width-ish; the space name is the part that
can give way when the sidebar is narrow. */
.count { flex: none; white-space: nowrap; }
.tools { display: flex; gap: 2px; flex: none; } .tools { display: flex; gap: 2px; flex: none; }
.tools button { padding: 0 6px; line-height: 18px; } .tools button { padding: 0 6px; line-height: 18px; }
.tools .board { color: var(--accent); } .tools .board { color: var(--accent); }
+40 -2
View File
@@ -49,6 +49,14 @@ class Workspace {
listCursor = $state(0) listCursor = $state(0)
cardCursor = $state(0) cardCursor = $state(0)
/*
* The node picked up by `m`, waiting for the `p` that drops it into whatever
* folder the list is showing then. A move is two keystrokes apart in time
* because the destination is somewhere else in the tree — there is no way to
* name it and be somewhere else at once.
*/
marked = $state<NodeSummary | null>(null)
pane = $state<Pane>('list') pane = $state<Pane>('list')
vimMode = $state('normal') vimMode = $state('normal')
flash = $state<Flash>(null) flash = $state<Flash>(null)
@@ -279,8 +287,9 @@ class Workspace {
await this.goTo(parent.id, from.id) await this.goTo(parent.id, from.id)
} }
async refresh() { /** `keep` follows a node that just arrived, instead of holding the cursor. */
const keepId = this.selected?.id async refresh(keep?: number) {
const keepId = keep ?? this.selected?.id
this.nodes = this.query this.nodes = this.query
? await api.listNodes({ q: this.query, space: this.spaceId ?? undefined }) ? await api.listNodes({ q: this.query, space: this.spaceId ?? undefined })
: await api.listNodes({ parent: this.parentId ?? undefined }) : await api.listNodes({ parent: this.parentId ?? undefined })
@@ -473,6 +482,35 @@ class Workspace {
this.say(`moved #${id}`) this.say(`moved #${id}`)
} }
/** `m`: pick the node under the cursor up, or put it back down unmoved. */
mark() {
const node = this.selected
if (!node) return this.say('nothing selected', 'error')
if (this.marked?.id === node.id) {
this.marked = null
return this.say('unmarked')
}
this.marked = node
this.say(`marked ${node.name.trim() || node.slug} — p drops it in this folder`)
}
/** `p`: drop the marked node into the folder the list is showing. */
async drop() {
const node = this.marked
const parent = this.parentId
if (!node) return this.say('nothing marked — press m on a row first', 'error')
if (parent === null) return this.say('no folder open', 'error')
if (node.parent_id === parent) {
this.marked = null
return this.say('already here')
}
await api.moveNode(node.id, parent)
this.marked = null
await this.refresh(node.id)
this.say(`moved ${node.name.trim() || node.slug} here`)
}
/* /*
* Boards. One per space, which is what makes "what am I working on" a * Boards. One per space, which is what makes "what am I working on" a
* question with an answer: work and uni are asked separately, and the diary * question with an answer: work and uni are asked separately, and the diary