pipeup 0.0.1 → 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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -2
  3. package/dist/pipeup.core.js +9 -0
  4. package/dist/pipeup.esm.js +9 -0
  5. package/dist/pipeup.min.js +9 -0
  6. package/dist/types/anchor/describe.d.ts +11 -0
  7. package/dist/types/anchor/fuzzy.d.ts +10 -0
  8. package/dist/types/anchor/locate.d.ts +13 -0
  9. package/dist/types/anchor/resolve.d.ts +13 -0
  10. package/dist/types/anchor/text.d.ts +12 -0
  11. package/dist/types/auto.d.ts +1 -0
  12. package/dist/types/core.d.ts +12 -0
  13. package/dist/types/crypto/identity.d.ts +10 -0
  14. package/dist/types/crypto/seal.d.ts +15 -0
  15. package/dist/types/document.d.ts +67 -0
  16. package/dist/types/export/format.d.ts +17 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/model/animals.d.ts +20 -0
  19. package/dist/types/model/fold.d.ts +6 -0
  20. package/dist/types/model/log.d.ts +33 -0
  21. package/dist/types/model/opid.d.ts +6 -0
  22. package/dist/types/model/ops.d.ts +30 -0
  23. package/dist/types/model/types.d.ts +70 -0
  24. package/dist/types/storage/feedback-file.d.ts +13 -0
  25. package/dist/types/storage/indexeddb.d.ts +15 -0
  26. package/dist/types/storage/store.d.ts +28 -0
  27. package/dist/types/ui/animals.d.ts +18 -0
  28. package/dist/types/ui/app.d.ts +15 -0
  29. package/dist/types/ui/bubbles.d.ts +10 -0
  30. package/dist/types/ui/column.d.ts +15 -0
  31. package/dist/types/ui/comment-mode.d.ts +15 -0
  32. package/dist/types/ui/composer.d.ts +15 -0
  33. package/dist/types/ui/context.d.ts +93 -0
  34. package/dist/types/ui/dom.d.ts +16 -0
  35. package/dist/types/ui/draft-view.d.ts +35 -0
  36. package/dist/types/ui/files.d.ts +2 -0
  37. package/dist/types/ui/geometry.d.ts +19 -0
  38. package/dist/types/ui/highlights.d.ts +28 -0
  39. package/dist/types/ui/host.d.ts +12 -0
  40. package/dist/types/ui/icons.d.ts +19 -0
  41. package/dist/types/ui/launcher.d.ts +7 -0
  42. package/dist/types/ui/layout.d.ts +62 -0
  43. package/dist/types/ui/mount.d.ts +19 -0
  44. package/dist/types/ui/pick.d.ts +21 -0
  45. package/dist/types/ui/select.d.ts +3 -0
  46. package/dist/types/ui/shortcut.d.ts +27 -0
  47. package/dist/types/ui/styles.d.ts +2 -0
  48. package/dist/types/ui/theme.d.ts +19 -0
  49. package/dist/types/ui/thread-view.d.ts +29 -0
  50. package/dist/types/util/encoding.d.ts +11 -0
  51. package/package.json +72 -4
@@ -0,0 +1,70 @@
1
+ export interface TextQuote {
2
+ exact: string;
3
+ prefix: string;
4
+ suffix: string;
5
+ }
6
+ /** Everything needed to find a comment's content again. See design §3. */
7
+ export interface Anchor {
8
+ /** data-pipeup-id of the nearest marked element, or of the element itself. */
9
+ id?: string;
10
+ /** CSS path from the marked element (or the page root) to the target element. '' means the base itself. */
11
+ path: string;
12
+ /** Commented text with surrounding context, for text comments. */
13
+ quote?: TextQuote;
14
+ /** Position inside the element as fractions of its box, for pins. */
15
+ point?: {
16
+ x: number;
17
+ y: number;
18
+ };
19
+ /** Where the reviewer was: slide ("1"-based), tab, route, plus anything the page reports. */
20
+ view?: Record<string, string>;
21
+ /** Fingerprint of the commented block's text when the comment was made. */
22
+ fingerprint: string;
23
+ /** A short plain-text excerpt of what the reviewer saw. */
24
+ snapshot: string;
25
+ }
26
+ export type OpKind = "create" | "reply" | "edit" | "delete" | "resolve" | "reopen";
27
+ export interface OpBody {
28
+ v: 1;
29
+ id: string;
30
+ kind: OpKind;
31
+ doc: string;
32
+ /** Thread id: the id of the thread's create op. */
33
+ thread: string;
34
+ /** reply: the comment answered. edit/delete: the comment changed. */
35
+ target?: string;
36
+ text?: string;
37
+ /** create only. */
38
+ anchor?: Anchor;
39
+ /** Author's public key. */
40
+ author: string;
41
+ /** Author's display name when the op was made; "" until they add one. */
42
+ name: string;
43
+ /** Lamport clock. */
44
+ clock: number;
45
+ /** Wall-clock ms, for display only. */
46
+ at: number;
47
+ }
48
+ export interface SignedOp {
49
+ body: OpBody;
50
+ sig: string;
51
+ }
52
+ export interface Comment {
53
+ id: string;
54
+ author: string;
55
+ /** The writer's name: the one on the comment, else the latest they used here; "" while they have none. */
56
+ name: string;
57
+ /** The comment's own op carried no name (so `name`, if any, was filled in from the writer's others). */
58
+ unnamed?: true;
59
+ text: string;
60
+ at: number;
61
+ edited: boolean;
62
+ deleted: boolean;
63
+ replies: Comment[];
64
+ }
65
+ export interface Thread {
66
+ id: string;
67
+ anchor: Anchor;
68
+ root: Comment;
69
+ resolved: boolean;
70
+ }
@@ -0,0 +1,13 @@
1
+ import type { SignedOp } from "../model/types";
2
+ /** The file a reviewer sends back with "Send feedback". */
3
+ export interface FeedbackFile {
4
+ pipeup: 1;
5
+ doc: string;
6
+ sealed: boolean;
7
+ iv?: string;
8
+ data?: string;
9
+ ops?: SignedOp[];
10
+ }
11
+ export declare function writeFeedbackFile(doc: string, ops: SignedOp[], key: CryptoKey | null): Promise<string>;
12
+ /** Returns the file's ops, untrusted: pass them to OpLog.add, which validates and verifies each one. */
13
+ export declare function readFeedbackFile(json: string, doc: string, key: CryptoKey | null): Promise<unknown[]>;
@@ -0,0 +1,15 @@
1
+ import type { SignedOp } from "../model/types";
2
+ import type { OpStore, Profile } from "./store";
3
+ /** Ops and profile in this browser's IndexedDB. The private key is stored as a non-extractable CryptoKey. */
4
+ export declare class IndexedDbStore implements OpStore {
5
+ private readonly db;
6
+ private constructor();
7
+ static open(name?: string): Promise<IndexedDbStore>;
8
+ close(): void;
9
+ load(doc: string): Promise<SignedOp[]>;
10
+ append(doc: string, ops: SignedOp[]): Promise<void>;
11
+ loadProfile(): Promise<Profile | null>;
12
+ saveProfile(profile: Profile): Promise<void>;
13
+ saveProfileIfAbsent(profile: Profile): Promise<Profile>;
14
+ private request;
15
+ }
@@ -0,0 +1,28 @@
1
+ import { type Identity } from "../crypto/identity";
2
+ import type { SignedOp } from "../model/types";
3
+ export interface Profile {
4
+ identity: Identity;
5
+ name: string;
6
+ }
7
+ /** Where a browser keeps ops and the reviewer's profile. Implementations must not leave the machine. */
8
+ export interface OpStore {
9
+ load(doc: string): Promise<SignedOp[]>;
10
+ append(doc: string, ops: SignedOp[]): Promise<void>;
11
+ loadProfile(): Promise<Profile | null>;
12
+ saveProfile(profile: Profile): Promise<void>;
13
+ /** Saves `profile` only if none is stored yet, atomically, and returns whichever profile is stored afterwards. */
14
+ saveProfileIfAbsent(profile: Profile): Promise<Profile>;
15
+ }
16
+ export declare class MemoryStore implements OpStore {
17
+ private readonly docs;
18
+ private profile;
19
+ load(doc: string): Promise<SignedOp[]>;
20
+ append(doc: string, ops: SignedOp[]): Promise<void>;
21
+ loadProfile(): Promise<Profile | null>;
22
+ saveProfile(profile: Profile): Promise<void>;
23
+ saveProfileIfAbsent(profile: Profile): Promise<Profile>;
24
+ }
25
+ /** A new reviewer has no name: they show as their animal until they add one. */
26
+ export declare const DEFAULT_PROFILE_NAME = "";
27
+ /** The reviewer's profile, created with a new identity the first time. */
28
+ export declare function loadOrCreateProfile(store: OpStore, name?: string): Promise<Profile>;
@@ -0,0 +1,18 @@
1
+ import { type Animal } from "../model/animals";
2
+ export declare const ANIMAL_ICONS: Record<Animal, string>;
3
+ /**
4
+ * The first letter or digit of a name, as a capital, always one character and the same in every locale
5
+ * ("ß" stays "ß"); "" when it has none (it might be all symbols or emoji).
6
+ */
7
+ export declare function initial(name: string): string;
8
+ /** What an avatar shows: the same key means the same picture. */
9
+ export declare const faceOf: (author: string, name: string) => string;
10
+ /**
11
+ * A squircle avatar: the writer's animal drawn in its colour on a tint of it, or their initial in the same colours once they have a
12
+ * name. Decorative: the name is always written beside it, so screen readers skip it.
13
+ */
14
+ export declare function avatar(author: string, name: string): HTMLElement;
15
+ /** Removes an avatar that a new one is easing in over, once it is covered; never leaves its spot empty. */
16
+ export declare function retire(el: HTMLElement, done?: () => void): void;
17
+ /** Eases a new avatar in once it is on the page (it starts faded and slightly small; .in brings it to rest). */
18
+ export declare function easeIn(el: HTMLElement): HTMLElement;
@@ -0,0 +1,15 @@
1
+ import { type PipeupDocument } from "../document";
2
+ import type { Ctx, UiState, View } from "./context";
3
+ export interface AppOptions {
4
+ doc: PipeupDocument;
5
+ root: Element;
6
+ onName(name: string): Promise<void>;
7
+ }
8
+ export interface App {
9
+ readonly ctx: Ctx;
10
+ destroy(): void;
11
+ }
12
+ /** Documents with free space beside them get a column; everything else gets bubbles and popovers. */
13
+ export declare function chooseMode(root: Element, showing?: boolean): UiState["mode"];
14
+ export declare function viewsFor(ctx: Ctx): View[];
15
+ export declare function startApp(o: AppOptions): App;
@@ -0,0 +1,10 @@
1
+ import type { Ctx, View } from "./context";
2
+ /** How long a preview waits after the pointer leaves, so the pointer can reach it and click. */
3
+ export declare const PREVIEW_GRACE_MS = 400;
4
+ /**
5
+ * Bubbles for element and pin comments, a hover preview, and (unless the document column is showing
6
+ * threads) a popover. Text comments have no bubble: their highlight is what you hover and click.
7
+ */
8
+ export declare function createBubbles(ctx: Ctx, opts: {
9
+ popovers: boolean;
10
+ }): View;
@@ -0,0 +1,15 @@
1
+ import type { Thread } from "../model/types";
2
+ import type { Ctx, View } from "./context";
3
+ /** Free width needed beside the content before comments get their own column. */
4
+ export declare const COLUMN_MIN = 300;
5
+ /** Width of the gutter that pages with data-pipeup-reserve keep for comments. */
6
+ export declare const GUTTER = 320;
7
+ /** Where the column goes (viewport x and width), or null when the page should use bubbles. */
8
+ export declare function columnSpot(root: Element, showing?: boolean): {
9
+ x: number;
10
+ width: number;
11
+ } | null;
12
+ /** The thread's content is gone (not just waiting for its fuzzy match). */
13
+ export declare function lost(ctx: Ctx, t: Thread): boolean;
14
+ /** Threads beside the document, level with what they're about; the open one aligns exactly. */
15
+ export declare function createColumn(ctx: Ctx): View;
@@ -0,0 +1,15 @@
1
+ import type { Ctx, View } from "./context";
2
+ import { type Look } from "./pick";
3
+ /** Comment mode's view; `back()` is Escape's step for it: clears a chosen block, false when none was chosen. */
4
+ export interface CommentMode extends View {
5
+ back(): boolean;
6
+ }
7
+ /**
8
+ * Comment mode: the page goes quiet, an outline glides to the obvious block under the pointer, and a click
9
+ * chooses it. Listeners are on window in the capture phase and stop propagation there, so the page's own
10
+ * handlers never run while Pipeup's other window listeners (selection, the menu) still do, whatever their order.
11
+ *
12
+ * Known limits: the page's CSS `:hover` still applies; page listeners on window in the capture phase that were
13
+ * added before comment mode started still hear events; Escape still reaches the page's own handlers.
14
+ */
15
+ export declare function createCommentMode(ctx: Ctx, look?: Look): CommentMode;
@@ -0,0 +1,15 @@
1
+ export interface ComposerOptions {
2
+ label: string;
3
+ /** Something to show before the line instead, such as the writer's avatar. */
4
+ before?: Node;
5
+ onSend(text: string): Promise<void> | void;
6
+ onCancel?(): void;
7
+ }
8
+ export interface Composer {
9
+ element: HTMLElement;
10
+ input: HTMLTextAreaElement;
11
+ focus(): void;
12
+ clear(): void;
13
+ }
14
+ /** A writing line: no placeholder, Enter sends, Shift+Enter breaks, Esc cancels, a paper plane appears with text. */
15
+ export declare function composer(o: ComposerOptions): Composer;
@@ -0,0 +1,93 @@
1
+ import type { Resolved } from "../anchor/resolve";
2
+ import type { PipeupDocument } from "../document";
3
+ import type { CopyAs, ExportItem } from "../export/format";
4
+ import type { Anchor, Thread } from "../model/types";
5
+ import type { DraftBox } from "./draft-view";
6
+ import type { ThreadActions } from "./thread-view";
7
+ /** A comment being written: what it's on, and where that is now. */
8
+ export interface Draft {
9
+ anchor: Anchor;
10
+ label: string;
11
+ /** Text being commented on (highlighted while drafting). */
12
+ ranges: Range[];
13
+ /** Where the draft's content is: its range for text, its element for blocks and pins. */
14
+ resolved: Resolved;
15
+ }
16
+ export interface UiState {
17
+ active: string | null;
18
+ hot: string | null;
19
+ showResolved: boolean;
20
+ /** Comments are out of sight: comment mode is off and All comments is closed. */
21
+ hidden: boolean;
22
+ mode: "column" | "bubbles";
23
+ draft: Draft | null;
24
+ /** Comment mode: the page is quiet and a click chooses a block to comment on. */
25
+ commenting: boolean;
26
+ /** All comments is open on the right: in the column layout it takes the column's place. */
27
+ listing: boolean;
28
+ /** A thread chosen from All comments on a narrow screen is open: comments show until it closes. */
29
+ reading: boolean;
30
+ }
31
+ /** Something that draws: rebuilt on render, repositioned every frame. */
32
+ export interface View {
33
+ render(threads: readonly Thread[]): void;
34
+ frame(): void;
35
+ destroy(): void;
36
+ }
37
+ export interface MenuActions {
38
+ copyAll(as: CopyAs): Promise<void>;
39
+ toggleResolved(): void;
40
+ rename(name: string): Promise<void>;
41
+ /** The reviewer's name; "" until they add one. */
42
+ name(): string;
43
+ /** Every thread in page order, with where each one is. */
44
+ all(): ExportItem[];
45
+ }
46
+ /** What every view shares: the document, state, and the few things views can ask the app to do. */
47
+ export interface Ctx {
48
+ readonly doc: PipeupDocument;
49
+ readonly root: Element;
50
+ readonly layer: HTMLElement;
51
+ readonly host: HTMLElement;
52
+ readonly state: UiState;
53
+ readonly resolved: ReadonlyMap<string, Resolved>;
54
+ /** Threads whose fuzzy match hasn't had its turn yet: not placed, but not lost either. */
55
+ readonly pending: ReadonlySet<string>;
56
+ readonly actions: ThreadActions;
57
+ readonly menu: MenuActions;
58
+ visible(t: Thread): boolean;
59
+ /** The shown text comment whose highlight is at (x, y), if any. */
60
+ quoteAt(x: number, y: number): Thread | null;
61
+ /**
62
+ * A highlight takes the pointer (previews on hover, opens on click) unless a comment with words in it is being
63
+ * written. One rule for hover and click, and for comment mode, which leaves such clicks to the highlight.
64
+ */
65
+ readsAt(x: number, y: number): Thread | null;
66
+ /** The event happened inside Pipeup's own UI. */
67
+ owns(e: Event): boolean;
68
+ /** A view handled this page event; the app won't treat it as a click elsewhere. */
69
+ claim(e: Event): void;
70
+ open(id: string | null): void;
71
+ /** Puts the cursor in thread `id`'s reply line, unless the reader is typing elsewhere. */
72
+ focusReply(id: string): void;
73
+ hot(id: string | null): void;
74
+ startDraft(d: Draft): void;
75
+ /** Moves the open draft to another block, keeping its words (starts one if none is open). */
76
+ moveDraft(d: Draft): void;
77
+ /** Nothing has been typed in the open draft. */
78
+ draftEmpty(): boolean;
79
+ /** Runs `fn`, then puts focus and the caret back in the open draft as they were. */
80
+ keepCaret(fn: () => void): void;
81
+ postDraft(text: string): Promise<void>;
82
+ cancelDraft(): void;
83
+ /** Turns comment mode on or off; comments show while it is on. */
84
+ setCommenting(on: boolean): void;
85
+ /** What a click on the page does: cancels an empty draft, else closes the open thread. */
86
+ dismiss(): void;
87
+ registerDraft(box: DraftBox | null): void;
88
+ toast(message: string): void;
89
+ /** Tells the reviewer what went wrong. */
90
+ report(e: unknown): void;
91
+ pulse(id: string): void;
92
+ render(): void;
93
+ }
@@ -0,0 +1,16 @@
1
+ type Child = Node | string | null | undefined | false;
2
+ /** Creates an element. Children are nodes or plain text — strings are never parsed as HTML. */
3
+ export declare function h<K extends keyof HTMLElementTagNameMap>(tag: K, props?: Record<string, string | boolean | undefined>, ...children: Child[]): HTMLElementTagNameMap[K];
4
+ /** Takes `el` out of (or back into) the keyboard's reach; focus never stays on something that goes inert. */
5
+ export declare function inert(el: HTMLElement, on: boolean): void;
6
+ export declare function clip(text: string, n: number): string;
7
+ /** Sizes a fixed overlay (outline, pulse, mark) to a box, with padding around it. */
8
+ export declare function fit(el: HTMLElement, r: {
9
+ left: number;
10
+ top: number;
11
+ width: number;
12
+ height: number;
13
+ }, pad?: number): void;
14
+ /** Text with its http(s) addresses as links. Built from nodes: nothing is ever parsed as HTML. */
15
+ export declare function linkify(text: string): (Node | string)[];
16
+ export {};
@@ -0,0 +1,35 @@
1
+ import type { Ctx, Draft } from "./context";
2
+ export interface DraftOptions {
3
+ /** What the comment is on: a quote or an element label. */
4
+ label: string;
5
+ /** The writer's public key, which picks their animal. */
6
+ me: string;
7
+ /** The writer's name; "" until they add one. */
8
+ name(): string;
9
+ /** Saves a new name (the same path as the menu's Your name). */
10
+ rename(name: string): Promise<void>;
11
+ onSend(text: string): Promise<void>;
12
+ onCancel(): void;
13
+ }
14
+ export interface DraftBox {
15
+ element: HTMLElement;
16
+ focus(): void;
17
+ setLabel(label: string): void;
18
+ /** The naming bar already says what the box is on: its label row takes no space. */
19
+ hideLabel(on: boolean): void;
20
+ /** Remembers the caret; the returned function focuses the box and puts it back. */
21
+ caret(): () => void;
22
+ isEmpty(): boolean;
23
+ }
24
+ /**
25
+ * The box for a new comment: what it's on, then the writing line with the writer's avatar beside it. Nobody
26
+ * has to give a name; a quiet line says who they are ("You're Otter · add your name") and opens a name
27
+ * field in its place.
28
+ */
29
+ export declare function renderDraft(o: DraftOptions): DraftBox;
30
+ /**
31
+ * The box for the app's current draft. While it holds words, a click elsewhere won't throw them away.
32
+ * The caller focuses it as soon as it is in the page: a deferred focus could land after the reader has
33
+ * already moved on, taking their typing or the new selection they just made.
34
+ */
35
+ export declare function draftBox(ctx: Ctx, d: Draft): DraftBox;
@@ -0,0 +1,2 @@
1
+ /** Copies on the user's action; falls back to a hidden field inside Pipeup's own layer. */
2
+ export declare function copyText(text: string, container: HTMLElement): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import type { Resolved } from "../anchor/resolve";
2
+ import type { Anchor } from "../model/types";
3
+ import type { AnchorPoint } from "./layout";
4
+ /** Where an element or pin comment's bubble sits (viewport coordinates). Text comments have no bubble. */
5
+ export declare function bubblePoint(a: Anchor, r: Resolved): {
6
+ x: number;
7
+ y: number;
8
+ } | null;
9
+ export declare function quoteRects(r: Resolved): DOMRect[];
10
+ /** The top edge a column thread or draft aligns with (viewport y), or null when the content is gone. */
11
+ export declare function anchorTop(a: Anchor, r: Resolved): number | null;
12
+ /** Where a preview or popover attaches: below the last quoted line, or beside the bubble or pin. */
13
+ export declare function popoverAnchor(a: Anchor, r: Resolved): AnchorPoint | null;
14
+ export declare function hit(x: number, y: number, rects: Iterable<{
15
+ left: number;
16
+ top: number;
17
+ right: number;
18
+ bottom: number;
19
+ }>): boolean;
@@ -0,0 +1,28 @@
1
+ export declare function highlightsSupported(): boolean;
2
+ export interface PageSheet {
3
+ /** Shows the picking cursor over the page while comment mode is on. */
4
+ picking(on: boolean): void;
5
+ /** Publishes --pipeup-gutter in px, for pages that reserve a gutter; null publishes nothing. */
6
+ gutter(px: number | null): void;
7
+ /** How strongly highlights are painted, 0 to 1. */
8
+ fade(k: number): void;
9
+ /**
10
+ * Makes room for the All comments panel: a right margin of `px` on the page, easing in and out unless
11
+ * `still`; 0 takes it away (the page eases back, then the rule goes).
12
+ */
13
+ room(px: number, still: boolean): void;
14
+ remove(): void;
15
+ }
16
+ /**
17
+ * Pipeup's only styles on the page itself: text highlights (painted by the browser, so the page's DOM is
18
+ * never touched), the picking cursor in comment mode, room for the open All comments panel (a right margin on
19
+ * the page) and, on pages that ask for it, the gutter width. They live in a constructed stylesheet
20
+ * adopted by the document — no <style> element is added and no inline style is written.
21
+ */
22
+ export declare function installPageSheet(doc?: Document, dark?: boolean): PageSheet;
23
+ export declare function paintHighlights(g: {
24
+ quote: Range[];
25
+ on: Range[];
26
+ done: Range[];
27
+ }): void;
28
+ export declare function clearHighlights(): void;
@@ -0,0 +1,12 @@
1
+ import { type Theme } from "./theme";
2
+ export interface Host {
3
+ element: HTMLElement;
4
+ shadow: ShadowRoot;
5
+ layer: HTMLElement;
6
+ destroy(): void;
7
+ }
8
+ /**
9
+ * Pipeup's only element in the page: a fixed, click-through <pipeup-root> under <html> with an open shadow
10
+ * root (open so tools like `pipeup check` can measure it). It never changes the page's layout.
11
+ */
12
+ export declare function createHost(theme: Theme, doc?: Document): Host;
@@ -0,0 +1,19 @@
1
+ declare const PATHS: {
2
+ readonly comment: readonly ["M20.5 11.5a8 8 0 0 1-11.7 7.1L4 19.8l1.2-4.4A8 8 0 1 1 20.5 11.5z", "M12.3 8.2v6.6M9 11.5h6.6"];
3
+ readonly send: readonly ["M21 12L3.5 4.5 6.2 12l-2.7 7.5zM6.2 12H13"];
4
+ readonly copy: readonly ["M10 8h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-8a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2z", "M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"];
5
+ readonly resolve: readonly ["M5 12.5l4.5 4.5L19 7.5"];
6
+ readonly reopen: readonly ["M3 12a9 9 0 1 0 3-6.7", "M3 4v5h5"];
7
+ readonly lines: readonly ["M4 7h16M4 12h10M4 17h7"];
8
+ readonly list: readonly ["M3 6.5a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0-3 0M3 12a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0-3 0M3 17.5a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0-3 0M9 6.5h11M9 12h11M9 17.5h7"];
9
+ readonly expand: readonly ["M14 4h6v6M20 4l-6 6M10 20H4v-6M4 20l6-6M20 14v6h-6M20 20l-6-6M4 10V4h6M4 4l6 6"];
10
+ readonly bubble: readonly ["M20.5 11.5a8 8 0 0 1-11.7 7.1L4 19.8l1.2-4.4A8 8 0 1 1 20.5 11.5z"];
11
+ readonly close: readonly ["M6 6l12 12M18 6L6 18"];
12
+ readonly edit: readonly ["M4 20h4L19 9l-4-4L4 16z"];
13
+ };
14
+ export type IconName = keyof typeof PATHS;
15
+ /** Outline icons drawn from constant path data; `stroke` thins or thickens the line (in 24-unit terms). */
16
+ export declare function icon(name: IconName, size?: number, stroke?: number): SVGSVGElement;
17
+ /** An outline drawing from constant path data (never from text people wrote). */
18
+ export declare function draw(paths: readonly string[], size: number): SVGSVGElement;
19
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { Ctx, View } from "./context";
2
+ /**
3
+ * The comment control: one round button in the corner (the comment icon, or the open count inside a comment
4
+ * bubble) that opens a menu, Comment nearest it. All comments is a panel on the right that stays open while
5
+ * choosing threads reveals them, so every counted thread can be reached.
6
+ */
7
+ export declare function createLauncher(ctx: Ctx): View;
@@ -0,0 +1,62 @@
1
+ export interface StackItem {
2
+ id: string;
3
+ /** Where the thread wants its top edge (page coordinates). */
4
+ want: number;
5
+ height: number;
6
+ }
7
+ export interface Box {
8
+ left: number;
9
+ top: number;
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export interface Viewport {
14
+ width: number;
15
+ height: number;
16
+ }
17
+ export interface AnchorPoint {
18
+ x: number;
19
+ y: number;
20
+ /** Text comments open below their line; element comments open beside. */
21
+ below: boolean;
22
+ }
23
+ /** Threads sit level with what they're about; overlaps push down; the open thread aligns exactly. */
24
+ export declare function stackColumn(items: StackItem[], activeId: string | null, gap?: number): Map<string, number>;
25
+ /**
26
+ * All comments' width, and a popover's (threads, drafts, the side popover). The only place they are set: the
27
+ * host writes them onto Pipeup's layer as --pu-panel and --pu-pop for the stylesheet.
28
+ */
29
+ export declare const PANEL = 320;
30
+ export declare const POPOVER = 300;
31
+ /** On screens this narrow or less, All comments takes the whole width (it gets .full). */
32
+ export declare const NARROW = 480;
33
+ export declare const narrow: () => boolean;
34
+ /** The viewport left for popovers: All comments, when open, takes its width from the right. */
35
+ export declare function room(listing: boolean): Viewport;
36
+ export declare function placePopover(at: AnchorPoint, size: {
37
+ width: number;
38
+ height: number;
39
+ }, vp: Viewport, margin?: number): {
40
+ left: number;
41
+ top: number;
42
+ };
43
+ /**
44
+ * An element draft: just below its block, above it when there is no room below, never over the block or the
45
+ * naming bar (which sits above the block, or below it when there is no room above).
46
+ */
47
+ export declare function placeDraft(block: Box, size: {
48
+ width: number;
49
+ height: number;
50
+ }, vp: Viewport, margin?: number, bar?: number): {
51
+ left: number;
52
+ top: number;
53
+ above: boolean;
54
+ };
55
+ /** The selection bar: centred above the target, or below it when there's no room; never covering it. */
56
+ export declare function placeBar(target: Box, size: {
57
+ width: number;
58
+ height: number;
59
+ }, vp: Viewport): {
60
+ left: number;
61
+ top: number;
62
+ };
@@ -0,0 +1,19 @@
1
+ import { PipeupDocument } from "../document";
2
+ import { type OpStore } from "../storage/store";
3
+ export interface MountOptions {
4
+ /** The area people can comment on. Default: document.body. */
5
+ root?: Element;
6
+ /** The reviewer's name. Without one they show as their animal until they add a name. */
7
+ name?: string;
8
+ /** Where comments are kept. Default: this browser's IndexedDB. */
9
+ store?: OpStore;
10
+ }
11
+ export interface PipeupInstance {
12
+ readonly document: PipeupDocument;
13
+ flush(): Promise<void>;
14
+ unmount(): void;
15
+ }
16
+ /** Adds Pipeup to the page. Calling it again returns the same instance. */
17
+ export declare function mount(options?: MountOptions): Promise<PipeupInstance>;
18
+ /** Pages that carry data-pipeup-doc mount themselves, unless data-pipeup-auto="off". */
19
+ export declare function autoMount(): void;
@@ -0,0 +1,21 @@
1
+ /** What picking needs to know about the page's layout (faked in unit tests). */
2
+ export interface Look {
3
+ box(el: Element): {
4
+ left: number;
5
+ top: number;
6
+ width: number;
7
+ height: number;
8
+ };
9
+ /** The element draws something of its own — a background, border or shadow — so it reads as a block. */
10
+ drawn(el: Element): boolean;
11
+ viewport(): {
12
+ width: number;
13
+ height: number;
14
+ };
15
+ }
16
+ /** The obvious block under `from`: what the outline glides to in comment mode. */
17
+ export declare function pickBlock(from: Element, root: Element, look: Look): Element | null;
18
+ /** The next block out from `el` with a different box (wrappers the same size are skipped). */
19
+ export declare function parentBlock(el: Element, root: Element, look: Look): Element | null;
20
+ /** The real page: boxes from layout, "drawn" from computed backgrounds, borders and shadows. */
21
+ export declare function pageLook(win?: Window): Look;
@@ -0,0 +1,3 @@
1
+ import type { Ctx, View } from "./context";
2
+ /** In comment mode, selecting text offers commenting: one comment icon, centred above the selection, never over it. */
3
+ export declare function createSelection(ctx: Ctx): View;
@@ -0,0 +1,27 @@
1
+ /** The comment-mode shortcut: change the key, or its labels, here and nowhere else. */
2
+ export declare const SHORTCUT: {
3
+ /** The physical key (`KeyboardEvent.code`): Option changes the typed character on a Mac. */
4
+ readonly code: "KeyC";
5
+ /** The modifiers that must be down, and only those (Shift, Alt/Option, Command, Ctrl). */
6
+ readonly mods: {
7
+ readonly shift: true;
8
+ readonly alt: true;
9
+ readonly meta: false;
10
+ readonly ctrl: false;
11
+ };
12
+ readonly mac: {
13
+ readonly label: "⇧⌥C";
14
+ readonly aria: "Shift+Alt+C";
15
+ };
16
+ readonly other: {
17
+ readonly label: "Shift+Alt+C";
18
+ readonly aria: "Shift+Alt+C";
19
+ };
20
+ };
21
+ export declare const SHORTCUT_LABEL: string;
22
+ export declare const SHORTCUT_ARIA: string;
23
+ /**
24
+ * Shift+Option+C (Shift+Alt+C): never a key repeat, Command or Ctrl, or AltGr (which types characters).
25
+ * Callers must not claim it inside text fields: on a Mac it types a character there.
26
+ */
27
+ export declare function isShortcut(e: KeyboardEvent): boolean;