pi-weave 0.3.10 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/web/client/dist/app.js +57 -63
- package/src/web/client/graph/Graph.tsx +5 -0
- package/src/web/client/graph/spotlight.model.ts +4 -0
- package/src/web/client/shell/Shell.tsx +19 -3
- package/src/web/client/shell/icons.model.ts +1 -1
- package/src/web/client/shell/theme.ts +8 -14
- package/src/web/client/trail/TrailStrip.tsx +0 -1
- package/src/web/client/trail/trail.model.ts +61 -6
- package/src/web/client/tree/Tree.tsx +3 -7
|
@@ -51,6 +51,8 @@ import {
|
|
|
51
51
|
SHEET_LABEL,
|
|
52
52
|
SHEET_OPEN,
|
|
53
53
|
SHEET_OPEN_SPLIT,
|
|
54
|
+
SPOTLIGHT_BY_AGENT,
|
|
55
|
+
SPOTLIGHT_BY_AGENT_HINT,
|
|
54
56
|
SPOTLIGHT_CLEAR,
|
|
55
57
|
SPOTLIGHT_CLEAR_HINT,
|
|
56
58
|
SPOTLIGHT_LABEL,
|
|
@@ -126,6 +128,8 @@ export interface GraphProps {
|
|
|
126
128
|
* They light alongside the spotlight's own matches; omitted means none.
|
|
127
129
|
*/
|
|
128
130
|
found?: readonly string[];
|
|
131
|
+
/** Whether `query` is the agent's newest search rather than the person's (`trail.model.ts`'s `agentSpotlight`). */
|
|
132
|
+
byAgent?: boolean;
|
|
129
133
|
/**
|
|
130
134
|
* Where the spotlight field writes. **Its presence is what makes this the
|
|
131
135
|
* full graph view**: with it the column shows the floating spotlight field
|
|
@@ -347,6 +351,7 @@ export function Graph(props: GraphProps) {
|
|
|
347
351
|
aria-label={SPOTLIGHT_LABEL} spellcheck={false} autoComplete="off" onInput={(event) => props.onQuery?.(event.currentTarget.value)} />
|
|
348
352
|
{spot === null ? null : (
|
|
349
353
|
<>
|
|
354
|
+
{props.byAgent === true ? <span class="weave-spotlight-by" title={SPOTLIGHT_BY_AGENT_HINT}>{SPOTLIGHT_BY_AGENT}</span> : null}
|
|
350
355
|
<span class={`weave-spotlight-count${spot.count === 0 ? " weave-spotlight-none" : ""}`} role="status">{litLabel(spot.count)}</span>
|
|
351
356
|
<button type="button" class="weave-spotlight-clear" title={SPOTLIGHT_CLEAR_HINT} aria-label={SPOTLIGHT_CLEAR_HINT}
|
|
352
357
|
onClick={() => { props.onQuery?.(""); field.current?.focus(); }}>{SPOTLIGHT_CLEAR}</button>
|
|
@@ -47,6 +47,10 @@ export const SPOTLIGHT_LABEL = "Light up in graph";
|
|
|
47
47
|
export const SPOTLIGHT_CLEAR = "Esc";
|
|
48
48
|
export const SPOTLIGHT_CLEAR_HINT = "Clear the spotlight (Esc)";
|
|
49
49
|
|
|
50
|
+
/** The tag on the field while it shows the agent's newest search, and its hint. */
|
|
51
|
+
export const SPOTLIGHT_BY_AGENT = "agent";
|
|
52
|
+
export const SPOTLIGHT_BY_AGENT_HINT = "Your agent searched for this. Type to light up something else.";
|
|
53
|
+
|
|
50
54
|
/** The legend while a spotlight is up, replacing the neighbourhood's. */
|
|
51
55
|
export const SPOTLIGHT_LEGEND = { lit: "lit", dimmed: "not lit" } as const;
|
|
52
56
|
|
|
@@ -20,7 +20,7 @@ import type { WorkspaceHandle } from "../workspace";
|
|
|
20
20
|
import { ContextRail } from "./ContextRail";
|
|
21
21
|
import { contextCount, contextModel } from "../context/context.model";
|
|
22
22
|
import { startTrailPoll } from "../trail/trail";
|
|
23
|
-
import { TRAIL_TICK_MS, trailLive, trailMarks, trailTurns } from "../trail/trail.model";
|
|
23
|
+
import { TRAIL_TICK_MS, agentSearch, agentSpotlight, trailLive, trailMarks, trailTurns } from "../trail/trail.model";
|
|
24
24
|
import type { TrailReplay } from "../trail/trail.model";
|
|
25
25
|
import { ResizeHandle } from "./ResizeHandle";
|
|
26
26
|
import { deeplinkSelection, formatHash } from "./deeplink.model";
|
|
@@ -142,6 +142,9 @@ export function Shell(props: ShellProps) {
|
|
|
142
142
|
// What the search palette listed when it lit the graph up, for that query.
|
|
143
143
|
// Typing a different query in the spotlight leaves these behind.
|
|
144
144
|
const [graphFound, setGraphFound] = useState<{ query: string; ids: readonly string[] } | null>(null);
|
|
145
|
+
// The agent search the spotlight is showing (its trail `seq`), or `null`
|
|
146
|
+
// when the spotlight is the person's — see `agentSpotlight`.
|
|
147
|
+
const [agentLit, setAgentLit] = useState<number | null>(null);
|
|
145
148
|
const draggedTab = useRef<string | null>(null);
|
|
146
149
|
const [dropTab, setDropTab] = useState<{ paneId: string; before: string | null } | null>(null);
|
|
147
150
|
const [dropPane, setDropPane] = useState<string | null>(null);
|
|
@@ -228,8 +231,10 @@ export function Shell(props: ShellProps) {
|
|
|
228
231
|
const lightUp = (query: string, ids: readonly string[] = []): void => {
|
|
229
232
|
setGraphQuery(query);
|
|
230
233
|
setGraphFound({ query, ids });
|
|
234
|
+
setAgentLit(null);
|
|
231
235
|
showGraph();
|
|
232
236
|
};
|
|
237
|
+
const typeSpotlight = (query: string): void => { setGraphQuery(query); setAgentLit(null); };
|
|
233
238
|
live.current = { layout, overlay, selectedId, graphPreviewId, select };
|
|
234
239
|
const mayMutate = (slugs: readonly string[]): boolean => mayMutateDrafts(drafts, slugs, () => window.confirm("Discard unsaved changes to the affected notes before changing files?"));
|
|
235
240
|
const save: SaveNote = async (slug, body, options) => {
|
|
@@ -323,6 +328,17 @@ export function Shell(props: ShellProps) {
|
|
|
323
328
|
}, [trailGlowing]);
|
|
324
329
|
const glows = useMemo(() => trailMarks(trail, trailNow, replay), [trail, trailNow, replay]);
|
|
325
330
|
const turns = useMemo(() => trailTurns(trail, data.graph), [trail, data.graph]);
|
|
331
|
+
// The agent's searches light up in the graph view's spotlight, like the
|
|
332
|
+
// palette's "light up in graph", without switching to the graph.
|
|
333
|
+
const search = useMemo(() => agentSearch(trail), [trail]);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
const composing = graphQuery.trim() !== "" && document.activeElement?.classList.contains("weave-spotlight-input") === true;
|
|
336
|
+
const next = agentSpotlight(agentLit, search, composing);
|
|
337
|
+
if (next === null) return;
|
|
338
|
+
setGraphQuery(next.query);
|
|
339
|
+
setGraphFound({ query: next.query, ids: next.ids });
|
|
340
|
+
setAgentLit(next.agent);
|
|
341
|
+
}, [search?.seq]);
|
|
326
342
|
// A floating card stays out of the way when it has nothing to say (no
|
|
327
343
|
// selection, no agent turn), and over the graph, whose own sheet is its context.
|
|
328
344
|
const coveredPane = layout.split === "down" ? layout.panes[0] : layout.panes.at(-1);
|
|
@@ -514,7 +530,7 @@ export function Shell(props: ShellProps) {
|
|
|
514
530
|
</nav>
|
|
515
531
|
<div class="weave-sidebar-dock weave-notes-dock" data-open={layout.treeVisible} inert={!layout.treeVisible} aria-hidden={!layout.treeVisible} style={{ "--weave-sidebar-width": `${Math.min(layout.treeWidth, width - 90)}px` }}><aside class="weave-sidebar weave-sidebar-notes" aria-label="Notes sidebar">
|
|
516
532
|
<div class="weave-sidebar-heading"><button type="button" aria-pressed={!recentMode} onClick={() => setRecentMode(false)}>Files</button><button type="button" aria-pressed={recentMode} onClick={() => setRecentMode(true)}>Recent</button><button type="button" aria-label="Hide notes sidebar" onClick={() => { root.current?.querySelector<HTMLButtonElement>('[aria-label="Toggle notes sidebar"]')?.focus(); setLayout({ ...layout, treeVisible: false }); }}>«</button></div>
|
|
517
|
-
{recentMode ? <RecentList visits={visits} graph={data.graph} selectedId={selectedId} onSelect={select} /> : <Tree graph={data.graph} selectedId={selectedId} recentIds={data.recentIds}
|
|
533
|
+
{recentMode ? <RecentList visits={visits} graph={data.graph} selectedId={selectedId} onSelect={select} /> : <Tree graph={data.graph} selectedId={selectedId} recentIds={data.recentIds} onSelect={(id, newTab, keepSidebar) => select(id, newTab, layout.activePane, keepSidebar)} onMutate={mayMutate} onRefresh={() => workspace.current?.refresh()} now={now} />}
|
|
518
534
|
<div class="weave-vault-label"><span>Workspace</span><strong title={props.cwd}>{props.cwd.split(/[\\/]/).filter(Boolean).pop() ?? "Weave"}</strong></div>
|
|
519
535
|
</aside><ResizeHandle label="Resize notes sidebar" min={180} max={420} value={layout.treeWidth} onChange={(delta) => setLayout((value) => ({ ...value, treeWidth: Math.max(180, Math.min(420, value.treeWidth + delta)) }))} /></div>
|
|
520
536
|
<main class={`weave-panes weave-split-${layout.split}`} style={layout.panes.length === 2 && width >= 850 ? { [layout.split === "right" ? "gridTemplateColumns" : "gridTemplateRows"]: `minmax(0, ${layout.ratio}fr) 4px minmax(0, ${1 - layout.ratio}fr)` } : {}}>
|
|
@@ -535,7 +551,7 @@ export function Shell(props: ShellProps) {
|
|
|
535
551
|
aria-hidden={!graphVisible} inert={!graphVisible} role={contextGraphActive ? "region" : undefined} aria-label={contextGraphActive ? "Note graph" : undefined}
|
|
536
552
|
style={{ ...graphBox, visibility: graphVisible ? "visible" : "hidden", pointerEvents: graphVisible ? "auto" : "none" }}>
|
|
537
553
|
<Graph graph={data.graph} selectedId={selectedId} previewId={graphPreviewId} onSelect={selectGraph} onOpen={openGraphNode} renderer={createSigmaRenderer} storage={localStorage} host={window} scheme={graphTheme} forces={layout.preferences.forces} groupColors={layout.preferences.groupColors} bootFailed={data.graphFailed} fit={fit} trail={glows}
|
|
538
|
-
{...(contextGraphActive ? {} : { query: graphQuery, ...(graphFound?.query === graphQuery ? { found: graphFound.ids } : {}), onQuery:
|
|
554
|
+
{...(contextGraphActive ? {} : { query: graphQuery, ...(graphFound?.query === graphQuery ? { found: graphFound.ids } : {}), byAgent: agentLit !== null, onQuery: typeSpotlight, onOpenSplit: openGraphNodeInSplit })} now={now} />
|
|
539
555
|
</div> : null}
|
|
540
556
|
</div>
|
|
541
557
|
<div class="weave-footer"><StatusBar model={statusBarModel(props.cwd, selectedId, searchShortcut(looksApple(props.platform)))} />
|
|
@@ -131,7 +131,7 @@ export const ICONS: Readonly<Record<IconName, IconDef>> = {
|
|
|
131
131
|
/** An open loop with its arrowhead. */
|
|
132
132
|
refresh: { d: ["M13 8a5 5 0 1 1-1.5-3.55", "M13 2.75V5.5h-2.75"], filled: false },
|
|
133
133
|
/** A hub with eight spokes. */
|
|
134
|
-
settings: { d: ["
|
|
134
|
+
settings: { d: ["M6.50 3.65L6.89 1.70L9.11 1.70L9.50 3.65A4.6 4.6 0 0 1 11.02 4.53L12.90 3.89L14.01 5.81L12.52 7.12A4.6 4.6 0 0 1 12.52 8.88L14.01 10.19L12.90 12.11L11.02 11.47A4.6 4.6 0 0 1 9.50 12.35L9.11 14.30L6.89 14.30L6.50 12.35A4.6 4.6 0 0 1 4.98 11.47L3.10 12.11L1.99 10.19L3.48 8.88A4.6 4.6 0 0 1 3.48 7.12L1.99 5.81L3.10 3.89L4.98 4.53A4.6 4.6 0 0 1 6.50 3.65Z", "M10 8a2 2 0 1 1-4 0a2 2 0 1 1 4 0Z"], filled: false },
|
|
135
135
|
/** A light theme. */
|
|
136
136
|
sun: { d: ["M10.5 8a2.5 2.5 0 1 1-5 0a2.5 2.5 0 1 1 5 0Z", "M8 1.75V3M8 13v1.25M1.75 8H3M13 8h1.25M3.6 3.6l.9.9M11.5 11.5l.9.9M3.6 12.4l.9-.9M11.5 4.5l.9-.9"], filled: false },
|
|
137
137
|
/** A dark theme. */
|
|
@@ -164,7 +164,11 @@ body{font-size:var(--weave-px-base)}
|
|
|
164
164
|
provenance glyph shape carries the distinction the colour swap drops;
|
|
165
165
|
elsewhere the hues are unaffected. The palette's hit rows need the same
|
|
166
166
|
remap: same selected ground, same failure. The label is restated last
|
|
167
|
-
so the selected row is always readable.
|
|
167
|
+
so the selected row is always readable. The ground itself is the Context
|
|
168
|
+
rail's selection: the --weave-new tint plus a 2px inset accent bar, kept
|
|
169
|
+
under the pointer too. It is the only tinted row: the tree is where you
|
|
170
|
+
are, not what the agent touched (the graph shows that). */
|
|
171
|
+
.weave-row-on,.weave-row-on:hover{background:var(--weave-new);box-shadow:inset 2px 0 0 var(--weave-accent)}
|
|
168
172
|
.weave-row-on .weave-twisty,.weave-row-on .weave-kind,.weave-row-on .weave-prov,
|
|
169
173
|
.weave-row-on .weave-meta,.weave-row-on .weave-label{color:var(--weave-fg)}
|
|
170
174
|
.weave-hit-on .weave-hit-icon,.weave-hit-on .weave-hit-detail,.weave-hit-on .weave-hit-meta{color:var(--weave-fg)}
|
|
@@ -179,16 +183,6 @@ body{font-size:var(--weave-px-base)}
|
|
|
179
183
|
40%{font-weight:700}
|
|
180
184
|
100%{background:transparent;font-weight:400}
|
|
181
185
|
}
|
|
182
|
-
/* Recall trail (issue #78): a note the agent searched, read or wrote. The
|
|
183
|
-
tint follows the graph's palette for the same kinds (warn → ok → accent)
|
|
184
|
-
and steps down through three strength buckets as the glow fades. */
|
|
185
|
-
.weave-row-trail{--weave-trail:var(--weave-warn);box-shadow:inset 2px 0 0 color-mix(in srgb,var(--weave-trail) var(--weave-trail-mix),transparent);background:color-mix(in srgb,var(--weave-trail) calc(var(--weave-trail-mix) / 6),transparent)}
|
|
186
|
-
.weave-row-trail-read{--weave-trail:var(--weave-ok)}
|
|
187
|
-
.weave-row-trail-wrote{--weave-trail:var(--weave-accent)}
|
|
188
|
-
.weave-row-trail-3{--weave-trail-mix:90%}
|
|
189
|
-
.weave-row-trail-2{--weave-trail-mix:55%}
|
|
190
|
-
.weave-row-trail-1{--weave-trail-mix:25%}
|
|
191
|
-
.weave-row-trail-wrote.weave-row-trail-3 .weave-label{font-weight:600}
|
|
192
186
|
.weave-twisty{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--weave-faint);cursor:pointer}
|
|
193
187
|
.weave-kind{flex:none;color:var(--weave-faint);font-size:var(--weave-px-ui)}
|
|
194
188
|
.weave-prov{flex:none;font-size:var(--weave-px-prov)}
|
|
@@ -602,6 +596,7 @@ body{font-size:var(--weave-px-base)}
|
|
|
602
596
|
color:var(--weave-warn);background:color-mix(in srgb,var(--weave-warn) 16%,transparent);border-radius:999px;
|
|
603
597
|
}
|
|
604
598
|
.weave-spotlight-none{color:var(--weave-dim);background:var(--weave-line)}
|
|
599
|
+
.weave-spotlight-by{flex:none;font-size:var(--weave-px-caption);color:var(--weave-dim);white-space:nowrap}
|
|
605
600
|
.weave-spotlight .weave-spotlight-clear{
|
|
606
601
|
flex:none;padding:1px 5px;cursor:pointer;font-family:var(--weave-mono);font-size:var(--weave-px-prov);
|
|
607
602
|
color:var(--weave-faint);background:transparent;border:1px solid var(--weave-line-strong);border-radius:var(--weave-radius);
|
|
@@ -693,9 +688,8 @@ body{font-size:var(--weave-px-base)}
|
|
|
693
688
|
.weave-trail{padding-top:6px;padding-bottom:6px;border-bottom:1px solid var(--weave-line)}
|
|
694
689
|
.weave-trail-head{display:flex;align-items:center;gap:6px;font-size:var(--weave-px-caption);font-weight:600;letter-spacing:.09em;color:var(--weave-faint);text-transform:uppercase}
|
|
695
690
|
.weave-trail-head .weave-ctx-count{margin-left:0}
|
|
696
|
-
.weave-trail-
|
|
697
|
-
.weave-trail-
|
|
698
|
-
.weave-trail-replay:hover,.weave-trail-turn:hover{color:var(--weave-fg)}
|
|
691
|
+
.weave-trail-turn{font:inherit;font-size:var(--weave-px-caption);letter-spacing:0;text-transform:none;color:var(--weave-dim);background:none;border:1px solid var(--weave-line);border-radius:var(--weave-radius);padding:0 6px;cursor:pointer}
|
|
692
|
+
.weave-trail-turn:hover{color:var(--weave-fg)}
|
|
699
693
|
.weave-trail-turn-on{color:var(--weave-accent);border-color:var(--weave-accent)}
|
|
700
694
|
.weave-trail-steps{margin:4px 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:2px}
|
|
701
695
|
.weave-trail-step{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px;padding-left:10px;position:relative;line-height:1.45}
|
|
@@ -27,7 +27,6 @@ export function TrailStrip(props: TrailStripProps) {
|
|
|
27
27
|
<h4 class="weave-ctx-head weave-trail-head">
|
|
28
28
|
<span class="weave-trail-title">{turnHeading(shown, shown === latest)}</span>
|
|
29
29
|
<span class="weave-ctx-count" title="Notes read or written">{shown.used}</span>
|
|
30
|
-
{shown.steps.length === 0 ? null : <button type="button" class="weave-trail-replay" title="Replay this turn's glow" onClick={() => props.onReplay(shown.turn)}>Replay</button>}
|
|
31
30
|
</h4>
|
|
32
31
|
{shown.steps.length === 0 ? <p class="weave-trail-empty">{TRAIL_EMPTY_TURN}</p> : null}
|
|
33
32
|
<ol class="weave-trail-steps">
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* steps one after another from the moment it was clicked.
|
|
10
10
|
* - {@link trailTurns}: the "This turn" strip — each turn's steps in order,
|
|
11
11
|
* worded as `searched "auth" → read Auth migration → appended Auth migration`.
|
|
12
|
+
* - {@link agentSearch}: the agent's newest search this turn, which the
|
|
13
|
+
* graph view lights up in its spotlight ({@link agentSpotlight}).
|
|
12
14
|
*
|
|
13
15
|
* Pure, DOM-free and clock-free (every function takes `now`), like the other
|
|
14
16
|
* `*.model.ts` files, so the whole feature is covered by unit tests.
|
|
@@ -179,17 +181,70 @@ export function trailTurns(trail: TrailPayload | null, graph: GraphPayload | nul
|
|
|
179
181
|
return views.slice(0, TRAIL_TURNS_SHOWN);
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
/** The strip's heading for a turn: the latest is "This turn", older ones are their time. */
|
|
183
184
|
/** What the strip says for a turn with no steps yet. */
|
|
184
185
|
export const TRAIL_EMPTY_TURN = "Nothing recalled yet.";
|
|
185
186
|
|
|
187
|
+
/** The strip's heading for a turn: the latest is "This turn", older ones are their time. */
|
|
186
188
|
export function turnHeading(view: TrailTurnView, latest: boolean): string {
|
|
187
189
|
return latest ? "This turn" : view.time === "" ? `Turn ${view.turn}` : view.time;
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
// --- the agent's search in the graph view's spotlight ----------------------------------
|
|
193
|
+
|
|
194
|
+
/** The agent's newest search this turn: what the graph view's spotlight lights. */
|
|
195
|
+
export interface AgentSearch {
|
|
196
|
+
/** The event's `seq`, so a new search is a new number even for the same words. */
|
|
197
|
+
seq: number;
|
|
198
|
+
query: string;
|
|
199
|
+
/** The notes the search returned, as node ids. */
|
|
200
|
+
ids: readonly string[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The agent's newest search in the current turn, or `null`.
|
|
205
|
+
*
|
|
206
|
+
* Only the current turn counts, so the spotlight a search lit goes out when
|
|
207
|
+
* the agent moves on to the next request. A search with no words (the wire
|
|
208
|
+
* allows one) has nothing to spell in the field and is skipped.
|
|
209
|
+
*/
|
|
210
|
+
export function agentSearch(trail: TrailPayload | null): AgentSearch | null {
|
|
211
|
+
if (trail === null) return null;
|
|
212
|
+
for (let index = trail.events.length - 1; index >= 0; index--) {
|
|
213
|
+
const event = trail.events[index]!;
|
|
214
|
+
if (event.turn !== trail.turn) return null;
|
|
215
|
+
if (event.kind !== "searched" || event.query === undefined || event.query.trim() === "") continue;
|
|
216
|
+
return { seq: event.seq, query: event.query, ids: event.slugs.map((slug) => `note:${slug}`) };
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** What the spotlight becomes: its query, the ids lit alongside, and the agent search that set it (`null`: the person's). */
|
|
222
|
+
export interface AgentSpotlight {
|
|
223
|
+
query: string;
|
|
224
|
+
ids: readonly string[];
|
|
225
|
+
agent: number | null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* How the graph view's spotlight follows the agent's searches, when
|
|
230
|
+
* {@link agentSearch} changes. `null` leaves the spotlight alone.
|
|
231
|
+
*
|
|
232
|
+
* A new search takes the spotlight over, the way the palette's "light up in
|
|
233
|
+
* graph" does, unless the person is in the middle of a query of their own:
|
|
234
|
+
* the field focused with words they typed. A focused field that is empty, or
|
|
235
|
+
* still shows an earlier agent search, is not theirs and follows along, so a
|
|
236
|
+
* field left focused never swallows a search. When the turn that searched is
|
|
237
|
+
* over, the spotlight it lit goes out, unless the person has made it theirs
|
|
238
|
+
* since by typing or lighting something up.
|
|
239
|
+
*/
|
|
240
|
+
export function agentSpotlight(
|
|
241
|
+
/** The `seq` of the agent search the spotlight shows now, or `null` when it is the person's (or empty). */
|
|
242
|
+
shown: number | null,
|
|
243
|
+
search: AgentSearch | null,
|
|
244
|
+
/** Whether the field has focus and holds words; `shown` says whose words they are. */
|
|
245
|
+
composing: boolean,
|
|
246
|
+
): AgentSpotlight | null {
|
|
247
|
+
if (search === null) return shown === null ? null : { query: "", ids: [], agent: null };
|
|
248
|
+
if (search.seq === shown || (composing && shown === null)) return null;
|
|
249
|
+
return { query: search.query, ids: search.ids, agent: search.seq };
|
|
195
250
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/** The vault and repository tree column. */
|
|
2
2
|
|
|
3
|
-
import type { TrailMark } from "../trail/trail.model";
|
|
4
|
-
import { trailRowClass } from "../trail/trail.model";
|
|
5
3
|
import { NOTE_DRAG_TYPE } from "../shell/shell.model";
|
|
6
4
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
7
5
|
import { createFolder, deleteFolder, deleteNote, moveNote, renameFolder, renameNote } from "../api";
|
|
@@ -48,8 +46,6 @@ export interface TreeProps {
|
|
|
48
46
|
graph: GraphPayload | null;
|
|
49
47
|
selectedId: string | null;
|
|
50
48
|
recentIds: ReadonlySet<string>;
|
|
51
|
-
/** Recall-trail glows by row id (`trail/trail.model.ts`). */
|
|
52
|
-
trail?: ReadonlyMap<string, TrailMark>;
|
|
53
49
|
onSelect: (id: string, newTab?: boolean, keepSidebar?: boolean) => void;
|
|
54
50
|
/**
|
|
55
51
|
* Ask before a mutation that would invalidate an open draft. `false` means
|
|
@@ -80,13 +76,13 @@ export function Icon({ name, class: className }: { name: IconName; class?: strin
|
|
|
80
76
|
);
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
function Row({ view, recentIds,
|
|
79
|
+
function Row({ view, recentIds, edit, onEdit, onRename, onSelect, onToggle, onMenu, onDrop }: { view: ReturnType<typeof rowViews>[number]; recentIds: ReadonlySet<string>; edit: string | null; onEdit: (value: string) => void; onRename: (value: string | null) => void; onSelect: (newTab: boolean) => void; onToggle: () => void; onMenu: (x: number, y: number) => void; onDrop: (id: string) => void }) {
|
|
84
80
|
const folder = dropFolder(view.id);
|
|
85
81
|
return (
|
|
86
82
|
<li
|
|
87
83
|
id={view.domId}
|
|
88
84
|
data-row-id={view.id}
|
|
89
|
-
class={`weave-row weave-row-${view.kind}${view.selected ? " weave-row-on" : ""}${recentIds.has(view.id) ? " weave-row-new" : ""}
|
|
85
|
+
class={`weave-row weave-row-${view.kind}${view.selected ? " weave-row-on" : ""}${recentIds.has(view.id) ? " weave-row-new" : ""}`}
|
|
90
86
|
role="treeitem"
|
|
91
87
|
aria-level={view.level}
|
|
92
88
|
aria-posinset={view.posinset}
|
|
@@ -301,7 +297,7 @@ export function Tree(props: TreeProps) {
|
|
|
301
297
|
setMenu({ id: "vault", label: "Vault", x: event.clientX, y: event.clientY });
|
|
302
298
|
}}
|
|
303
299
|
>
|
|
304
|
-
{rowViews(rows, props.selectedId, props.now).map((view) => <Row key={view.id} view={view} recentIds={props.recentIds}
|
|
300
|
+
{rowViews(rows, props.selectedId, props.now).map((view) => <Row key={view.id} view={view} recentIds={props.recentIds} edit={editing?.id === view.id ? editing.value : null} onEdit={(value) => setEditing((current) => current === null ? null : { ...current, value })} onRename={commitEdit} onSelect={(newTab) => { props.onSelect(view.id, newTab); if (view.hasKids) setState((current) => toggleExpanded(current, view.id)); }} onToggle={() => setState(toggleExpanded(state, view.id))} onMenu={(x, y) => setMenu({ id: view.id, label: view.label, x, y })} onDrop={(dragged) => { const folder = dropFolder(view.id); if (dragged.startsWith("note:") && folder !== undefined) { const target = mutableTreeRow(dragged); if (target !== null && !props.onMutate(affectedNoteSlugs(props.graph, target))) return; void run(moveNote(fetchJson, dragged.slice("note:".length), folder)); } }} />)}
|
|
305
301
|
</ul>
|
|
306
302
|
) : <p class="weave-tree-empty">{empty}</p>}
|
|
307
303
|
<p class="weave-tree-count">{rowCountLabel(rows)}</p>
|