pi-weave 0.3.0 → 0.3.1
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 +27 -16
- package/package.json +1 -2
- package/src/core/frontmatter.ts +3 -3
- package/src/core/graph/model.ts +2 -2
- package/src/core/graph/wikilinks.ts +1 -1
- package/src/core/types.ts +1 -1
- package/src/pi/viewer/tui/surface/detail.ts +1 -1
- package/src/web/client/dist/app.js +210 -178
- package/src/web/client/graph/Graph.tsx +18 -0
- package/src/web/client/graph/column.model.ts +19 -2
- package/src/web/client/graph/graph.model.ts +1 -1
- package/src/web/client/graph/positions.ts +1 -1
- package/src/web/client/graph/renderer.ts +9 -1
- package/src/web/client/note/Note.tsx +66 -91
- package/src/web/client/note/drafts.ts +172 -0
- package/src/web/client/note/note.model.ts +2 -34
- package/src/web/client/search/SearchPalette.tsx +9 -8
- package/src/web/client/shell/ContextRail.tsx +8 -9
- package/src/web/client/shell/Shell.tsx +371 -254
- package/src/web/client/shell/StatusBar.tsx +1 -1
- package/src/web/client/shell/focus.model.ts +2 -2
- package/src/web/client/shell/keys.model.ts +26 -31
- package/src/web/client/shell/keys.ts +1 -0
- package/src/web/client/shell/shell.model.ts +31 -107
- package/src/web/client/shell/theme.model.ts +4 -4
- package/src/web/client/shell/theme.ts +12 -93
- package/src/web/client/shell/workspace.css.ts +117 -0
- package/src/web/client/tree/Tree.tsx +43 -14
- package/src/web/client/tree/tree.model.ts +19 -0
- package/src/web/client/workspace.ts +19 -1
- package/src/web/server/routes.ts +34 -3
- package/src/web/server/server.ts +5 -0
- package/src/web/server/workspace-state.ts +121 -0
- package/src/web/shared/workspace.ts +270 -0
- package/src/web/client/selection.storage.ts +0 -69
- package/src/web/client/shell/Columns.tsx +0 -139
- package/src/web/client/shell/Divider.tsx +0 -15
- package/src/web/client/shell/Header.tsx +0 -119
- package/src/web/client/shell/drag.model.ts +0 -35
- package/src/web/client/shell/layout.model.ts +0 -132
|
@@ -334,7 +334,7 @@ export function resolveMarkdownLink(index: WikiIndex, href: string): string | nu
|
|
|
334
334
|
* Whether an unresolved wikilink should be drawn as a ghost.
|
|
335
335
|
*
|
|
336
336
|
* See {@link WikiIndex}: only when the payload independently confirms this
|
|
337
|
-
* note has unresolved links.
|
|
337
|
+
* note has unresolved links. Ghost styling is an *offer to create
|
|
338
338
|
* the note*, so showing it for a link that in fact resolves would be an offer
|
|
339
339
|
* to create a note that already exists.
|
|
340
340
|
*/
|
|
@@ -847,32 +847,6 @@ export const SAVING_LABEL = "Saving…";
|
|
|
847
847
|
export const EDITOR_ARIA_LABEL = "Note body";
|
|
848
848
|
export const DISCARD_PROMPT = "Discard unsaved changes to this note?";
|
|
849
849
|
|
|
850
|
-
/**
|
|
851
|
-
* Whether the draft differs from the note as last loaded. Trailing whitespace
|
|
852
|
-
* counts: the save trims, so calling such a draft clean would skip the prompt
|
|
853
|
-
* on the one edit that gets silently reverted.
|
|
854
|
-
*/
|
|
855
|
-
export function draftDirty(draft: string, body: string): boolean {
|
|
856
|
-
return draft !== body;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Whether a save's reply still belongs to the editor that issued it. A
|
|
861
|
-
* counter, not the slug: reopening the same note is a new session too.
|
|
862
|
-
*/
|
|
863
|
-
export function saveLanded(issued: number, current: number): boolean {
|
|
864
|
-
return issued === current;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
/**
|
|
868
|
-
* Whether the draft moved since the bytes a save sent — keystrokes made
|
|
869
|
-
* during the request were never in it, so a success must not close over them.
|
|
870
|
-
* Unlike {@link saveLanded}, this is about the *text*, not the session.
|
|
871
|
-
*/
|
|
872
|
-
export function draftMoved(sent: string, current: string | null): boolean {
|
|
873
|
-
return current !== null && current !== sent;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
850
|
/** The note header: title, provenance badge, relative time, tags. */
|
|
877
851
|
export interface NoteHeaderView {
|
|
878
852
|
readonly title: string;
|
|
@@ -959,13 +933,7 @@ export const PREVIEW_GAP = 8;
|
|
|
959
933
|
/** The card's kind line for a link nothing in the vault answers to. */
|
|
960
934
|
export const GHOST_KIND = "no note";
|
|
961
935
|
|
|
962
|
-
|
|
963
|
-
* The card's DOM id, shared by the element and the `aria-describedby` it is
|
|
964
|
-
* announced through. A constant rather than an inline literal on both sides
|
|
965
|
-
* because the link is a *string* of rendered HTML — see
|
|
966
|
-
* `Note.tsx`'s layout effect — and the pair cannot be typed into agreement.
|
|
967
|
-
*/
|
|
968
|
-
export const PREVIEW_ID = "weave-preview";
|
|
936
|
+
|
|
969
937
|
|
|
970
938
|
/** A `PreviewElement` the preview's delegated handlers may land on. */
|
|
971
939
|
export interface PreviewAnchor {
|
|
@@ -9,12 +9,12 @@ import type { SearchState } from "./search.model";
|
|
|
9
9
|
|
|
10
10
|
export interface SearchPaletteProps {
|
|
11
11
|
graph: GraphPayload | null;
|
|
12
|
-
onSelect: (id: string) => void;
|
|
12
|
+
onSelect: (id: string, newTab?: boolean) => void;
|
|
13
13
|
onClose: () => void;
|
|
14
14
|
ports: { fetch: FetchLike };
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function Row({ row, onPick, onHover }: { row: SearchRowView; onPick: () => void; onHover: () => void }) {
|
|
17
|
+
function Row({ row, onPick, onHover }: { row: SearchRowView; onPick: (newTab: boolean) => void; onHover: () => void }) {
|
|
18
18
|
return (
|
|
19
19
|
<li
|
|
20
20
|
id={row.domId}
|
|
@@ -22,7 +22,7 @@ function Row({ row, onPick, onHover }: { row: SearchRowView; onPick: () => void;
|
|
|
22
22
|
aria-selected={row.active}
|
|
23
23
|
class={`weave-hit weave-hit-${row.kind}${row.active ? " weave-hit-on" : ""}`}
|
|
24
24
|
onMouseMove={onHover}
|
|
25
|
-
onClick={onPick}
|
|
25
|
+
onClick={(event) => onPick(event.metaKey || event.ctrlKey)}
|
|
26
26
|
>
|
|
27
27
|
<span class="weave-hit-badge">{row.badge}</span>
|
|
28
28
|
<span class="weave-hit-label">{row.label}</span>
|
|
@@ -70,9 +70,9 @@ export function SearchPalette(props: SearchPaletteProps) {
|
|
|
70
70
|
}, [state.query, props.ports.fetch]);
|
|
71
71
|
|
|
72
72
|
const model = paletteModel(state, props.graph);
|
|
73
|
-
const pick = (index: number | null): void => {
|
|
73
|
+
const pick = (index: number | null, newTab = false): void => {
|
|
74
74
|
const id = resultIdAt(model.rows, index);
|
|
75
|
-
if (id !== null) props.onSelect(id);
|
|
75
|
+
if (id !== null) props.onSelect(id, newTab);
|
|
76
76
|
props.onClose();
|
|
77
77
|
};
|
|
78
78
|
|
|
@@ -92,7 +92,7 @@ export function SearchPalette(props: SearchPaletteProps) {
|
|
|
92
92
|
if (!next.handled) return;
|
|
93
93
|
event.preventDefault();
|
|
94
94
|
setState((current) => ({ ...current, cursor: next.cursor }));
|
|
95
|
-
if (next.dismiss) pick(next.activate);
|
|
95
|
+
if (next.dismiss) pick(next.activate, event.metaKey || event.ctrlKey);
|
|
96
96
|
}}
|
|
97
97
|
>
|
|
98
98
|
<input
|
|
@@ -108,14 +108,15 @@ export function SearchPalette(props: SearchPaletteProps) {
|
|
|
108
108
|
/>
|
|
109
109
|
{model.status === null ? (
|
|
110
110
|
<ul id="weave-search-results" class="weave-hits" role="listbox" aria-label={PALETTE_TITLE}>
|
|
111
|
-
{model.rows.map((row, index) => <Row key={row.id} row={row} onPick={() => pick(index)} onHover={() => setState((current) => ({ ...current, cursor: index }))} />)}
|
|
111
|
+
{model.rows.map((row, index) => <Row key={row.id} row={row} onPick={(newTab) => pick(index, newTab)} onHover={() => setState((current) => ({ ...current, cursor: index }))} />)}
|
|
112
112
|
</ul>
|
|
113
113
|
) : (
|
|
114
114
|
<p class="weave-palette-status" role="status">{model.status}</p>
|
|
115
115
|
)}
|
|
116
116
|
<p class="weave-palette-foot">
|
|
117
117
|
<span>{model.countLabel}</span>
|
|
118
|
-
<
|
|
118
|
+
<button type="button" class="weave-chip" disabled={model.count === 0} onClick={() => pick(state.cursor, true)}>Open in new tab</button>
|
|
119
|
+
<span class="weave-palette-hint">{PALETTE_HINT} · ⌘/Ctrl ↵ new tab</span>
|
|
119
120
|
</p>
|
|
120
121
|
</div>
|
|
121
122
|
</div>
|
|
@@ -22,13 +22,13 @@ import { Icon } from "../tree/Tree";
|
|
|
22
22
|
export interface ContextRailProps {
|
|
23
23
|
graph: GraphPayload | null;
|
|
24
24
|
selectedId: string | null;
|
|
25
|
-
onSelect: (id: string) => void;
|
|
25
|
+
onSelect: (id: string, newTab?: boolean) => void;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function Row({ row, onSelect }: { row: ContextRow; onSelect: () => void }) {
|
|
28
|
+
function Row({ row, onSelect }: { row: ContextRow; onSelect: (newTab: boolean) => void }) {
|
|
29
29
|
return (
|
|
30
30
|
<li class={`weave-ctx-row${row.selected ? " weave-row-on" : ""}`}>
|
|
31
|
-
<button type="button" class="weave-ctx-link" onClick={onSelect}>
|
|
31
|
+
<button type="button" class="weave-ctx-link" onClick={(event) => onSelect(event.metaKey || event.ctrlKey)}>
|
|
32
32
|
<span class="weave-kind" aria-hidden="true">
|
|
33
33
|
<Icon name={row.kindIcon} class="weave-icon" />
|
|
34
34
|
</span>
|
|
@@ -64,26 +64,26 @@ function Heading({ view, onToggle }: { view: RailSectionView; onToggle: () => vo
|
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function Group({ view, onSelect, onToggle }: { view: RailSectionView; onSelect: (id: string) => void; onToggle: () => void }) {
|
|
67
|
+
function Group({ view, onSelect, onToggle }: { view: RailSectionView; onSelect: (id: string, newTab?: boolean) => void; onToggle: () => void }) {
|
|
68
68
|
return (
|
|
69
69
|
<section class="weave-ctx-group">
|
|
70
70
|
<Heading view={view} onToggle={onToggle} />
|
|
71
71
|
<ul class="weave-ctx-rows" id={railPanelId(view.heading)} hidden={view.collapsed}>
|
|
72
72
|
{view.rows.map((row) => (
|
|
73
|
-
<Row key={row.id} row={row} onSelect={() => onSelect(row.target)} />
|
|
73
|
+
<Row key={row.id} row={row} onSelect={(newTab) => onSelect(row.target, newTab)} />
|
|
74
74
|
))}
|
|
75
75
|
</ul>
|
|
76
76
|
</section>
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function Tag({ tag, onSelect }: { tag: TagGroupRow; onSelect: (id: string) => void }) {
|
|
80
|
+
function Tag({ tag, onSelect }: { tag: TagGroupRow; onSelect: (id: string, newTab?: boolean) => void }) {
|
|
81
81
|
return (
|
|
82
82
|
<li class="weave-ctx-tag">
|
|
83
83
|
<span class="weave-tag">#{tag.tag}</span>
|
|
84
84
|
<ul class="weave-ctx-rows">
|
|
85
85
|
{tag.siblings.map((row) => (
|
|
86
|
-
<Row key={row.id} row={row} onSelect={() => onSelect(row.target)} />
|
|
86
|
+
<Row key={row.id} row={row} onSelect={(newTab) => onSelect(row.target, newTab)} />
|
|
87
87
|
))}
|
|
88
88
|
</ul>
|
|
89
89
|
</li>
|
|
@@ -102,7 +102,6 @@ export function ContextRail(props: ContextRailProps) {
|
|
|
102
102
|
const tags = railTagsView(model.tags, toggles);
|
|
103
103
|
return (
|
|
104
104
|
<div class="weave-rail" aria-label={CONTEXT_EMPTY.title}>
|
|
105
|
-
<h2 class="weave-col-title">{CONTEXT_EMPTY.title}</h2>
|
|
106
105
|
{model.empty === null ? null : <p class="weave-ctx-empty">{model.empty}</p>}
|
|
107
106
|
{model.groups.map((group) => {
|
|
108
107
|
const view = railSectionView(group, toggles);
|
|
@@ -120,4 +119,4 @@ export function ContextRail(props: ContextRailProps) {
|
|
|
120
119
|
)}
|
|
121
120
|
</div>
|
|
122
121
|
);
|
|
123
|
-
}
|
|
122
|
+
}
|