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.
Files changed (40) hide show
  1. package/README.md +27 -16
  2. package/package.json +1 -2
  3. package/src/core/frontmatter.ts +3 -3
  4. package/src/core/graph/model.ts +2 -2
  5. package/src/core/graph/wikilinks.ts +1 -1
  6. package/src/core/types.ts +1 -1
  7. package/src/pi/viewer/tui/surface/detail.ts +1 -1
  8. package/src/web/client/dist/app.js +210 -178
  9. package/src/web/client/graph/Graph.tsx +18 -0
  10. package/src/web/client/graph/column.model.ts +19 -2
  11. package/src/web/client/graph/graph.model.ts +1 -1
  12. package/src/web/client/graph/positions.ts +1 -1
  13. package/src/web/client/graph/renderer.ts +9 -1
  14. package/src/web/client/note/Note.tsx +66 -91
  15. package/src/web/client/note/drafts.ts +172 -0
  16. package/src/web/client/note/note.model.ts +2 -34
  17. package/src/web/client/search/SearchPalette.tsx +9 -8
  18. package/src/web/client/shell/ContextRail.tsx +8 -9
  19. package/src/web/client/shell/Shell.tsx +371 -254
  20. package/src/web/client/shell/StatusBar.tsx +1 -1
  21. package/src/web/client/shell/focus.model.ts +2 -2
  22. package/src/web/client/shell/keys.model.ts +26 -31
  23. package/src/web/client/shell/keys.ts +1 -0
  24. package/src/web/client/shell/shell.model.ts +31 -107
  25. package/src/web/client/shell/theme.model.ts +4 -4
  26. package/src/web/client/shell/theme.ts +12 -93
  27. package/src/web/client/shell/workspace.css.ts +117 -0
  28. package/src/web/client/tree/Tree.tsx +43 -14
  29. package/src/web/client/tree/tree.model.ts +19 -0
  30. package/src/web/client/workspace.ts +19 -1
  31. package/src/web/server/routes.ts +34 -3
  32. package/src/web/server/server.ts +5 -0
  33. package/src/web/server/workspace-state.ts +121 -0
  34. package/src/web/shared/workspace.ts +270 -0
  35. package/src/web/client/selection.storage.ts +0 -69
  36. package/src/web/client/shell/Columns.tsx +0 -139
  37. package/src/web/client/shell/Divider.tsx +0 -15
  38. package/src/web/client/shell/Header.tsx +0 -119
  39. package/src/web/client/shell/drag.model.ts +0 -35
  40. 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. Obsidian's ghost styling is an *offer to create
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
- <span class="weave-palette-hint">{PALETTE_HINT}</span>
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
+ }