web-comments 0.0.1 → 1.0.0
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 +263 -6
- package/dist/auth/bearer-token.js +43 -0
- package/dist/auth/cloudflare-access.js +90 -0
- package/dist/auth/dev-user.js +21 -0
- package/dist/auth/session-endpoint.js +47 -0
- package/dist/chunks/chunk-644PTNSO.js +101 -0
- package/dist/chunks/chunk-7K7K7M5O.js +101 -0
- package/dist/chunks/chunk-RRSB326L.js +720 -0
- package/dist/chunks/chunk-T4WN3STA.js +239 -0
- package/dist/chunks/chunk-WSFCRVEQ.js +9 -0
- package/dist/chunks/chunk-WY5E2C47.js +99 -0
- package/dist/connectors/linear.js +318 -0
- package/dist/connectors/mcp.js +890 -0
- package/dist/index.js +52 -0
- package/dist/react.js +19 -0
- package/dist/server.js +11 -0
- package/dist/store/d1.js +16 -0
- package/dist/store/drizzle/mysql.js +17 -0
- package/dist/store/drizzle/pg.js +17 -0
- package/dist/store/drizzle/sqlite.js +17 -0
- package/dist/store/drizzle.js +10 -0
- package/dist/store/memory.js +109 -0
- package/dist/store/r2.js +25 -0
- package/dist/store/s3.js +43 -0
- package/dist/types/auth/bearer-token.d.ts +13 -0
- package/dist/types/auth/cloudflare-access.d.ts +33 -0
- package/dist/types/auth/dev-user.d.ts +6 -0
- package/dist/types/auth/session-endpoint.d.ts +25 -0
- package/dist/types/connectors/linear/index.d.ts +46 -0
- package/dist/types/connectors/mcp/index.d.ts +21 -0
- package/dist/types/connectors/mcp/oauth.d.ts +50 -0
- package/dist/types/connectors/mcp/pages.d.ts +14 -0
- package/dist/types/react.d.ts +8 -0
- package/dist/types/server/auth.d.ts +16 -0
- package/dist/types/server/connector.d.ts +102 -0
- package/dist/types/server/events.d.ts +31 -0
- package/dist/types/server/index.d.ts +50 -0
- package/dist/types/server/service.d.ts +53 -0
- package/dist/types/server/store.d.ts +85 -0
- package/dist/types/server/validate.d.ts +20 -0
- package/dist/types/shared/types.d.ts +117 -0
- package/dist/types/store/d1.d.ts +9 -0
- package/dist/types/store/drizzle/index.d.ts +22 -0
- package/dist/types/store/drizzle/mysql.d.ts +700 -0
- package/dist/types/store/drizzle/pg.d.ts +659 -0
- package/dist/types/store/drizzle/sqlite.d.ts +700 -0
- package/dist/types/store/memory.d.ts +10 -0
- package/dist/types/store/r2.d.ts +20 -0
- package/dist/types/store/s3.d.ts +21 -0
- package/dist/types/widget/anchor.d.ts +9 -0
- package/dist/types/widget/api.d.ts +43 -0
- package/dist/types/widget/app.d.ts +29 -0
- package/dist/types/widget/capture.d.ts +33 -0
- package/dist/types/widget/format.d.ts +15 -0
- package/dist/types/widget/index.d.ts +41 -0
- package/dist/types/widget/styles.d.ts +6 -0
- package/dist/types/widget/thread.d.ts +32 -0
- package/dist/types/widget/tracker.d.ts +68 -0
- package/dist/widget.global.js +52 -0
- package/package.json +144 -6
- package/types/widget.global.d.ts +5 -0
- package/index.d.ts +0 -2
- package/index.js +0 -6
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AssetStore } from "../server/store";
|
|
2
|
+
/** The slice of a Workers R2 binding this uses. */
|
|
3
|
+
export interface R2BucketLike {
|
|
4
|
+
put(key: string, value: ArrayBuffer, options?: {
|
|
5
|
+
httpMetadata?: {
|
|
6
|
+
contentType?: string;
|
|
7
|
+
};
|
|
8
|
+
}): Promise<unknown>;
|
|
9
|
+
get(key: string): Promise<{
|
|
10
|
+
body: ReadableStream;
|
|
11
|
+
httpMetadata?: {
|
|
12
|
+
contentType?: string;
|
|
13
|
+
};
|
|
14
|
+
} | null>;
|
|
15
|
+
delete(key: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/** Screenshots in a Cloudflare R2 bucket, through its Workers binding: `assets: (env) => r2Assets(env.SCREENSHOTS)`. */
|
|
18
|
+
export declare function r2Assets(bucket: R2BucketLike, options?: {
|
|
19
|
+
prefix?: string;
|
|
20
|
+
}): AssetStore;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AssetStore } from "../server/store";
|
|
2
|
+
export interface S3AssetsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The S3 API endpoint, without the bucket: "https://s3.us-east-1.amazonaws.com",
|
|
5
|
+
* "https://<account>.r2.cloudflarestorage.com", a MinIO URL, ...
|
|
6
|
+
*/
|
|
7
|
+
endpoint: string;
|
|
8
|
+
bucket: string;
|
|
9
|
+
accessKeyId: string;
|
|
10
|
+
secretAccessKey: string;
|
|
11
|
+
/** Default "auto", which R2 expects; AWS needs the bucket's region. */
|
|
12
|
+
region?: string;
|
|
13
|
+
/** Put every key under this prefix, e.g. "web-comments/". */
|
|
14
|
+
prefix?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Screenshots in any S3-compatible bucket (AWS S3, R2's S3 API, MinIO, ...),
|
|
18
|
+
* signed with SigV4 over fetch, so it runs on Workers, Node and Bun alike.
|
|
19
|
+
* Path-style addressing: {endpoint}/{bucket}/{key}.
|
|
20
|
+
*/
|
|
21
|
+
export declare function s3Assets(options: S3AssetsOptions): AssetStore;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Anchor } from "../shared/types";
|
|
2
|
+
export declare function textOf(element: Element): string;
|
|
3
|
+
/** The page element under a point, looking past the widget's own layer. */
|
|
4
|
+
export declare function elementAt(x: number, y: number, host: Element): Element | null;
|
|
5
|
+
/** Describes where a click landed so the comment can find its element again later. */
|
|
6
|
+
export declare function createAnchor(element: Element, clientX: number, clientY: number): Anchor;
|
|
7
|
+
/** Finds an anchor's element on the page as it is now, or null when it's gone. */
|
|
8
|
+
export declare function resolveAnchor(anchor: Anchor): Element | null;
|
|
9
|
+
export declare function selectorFor(element: Element): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Anchor, Identity, Thread, ThreadContext, ThreadStatus } from "../shared/types";
|
|
2
|
+
import type { Shots } from "./capture";
|
|
3
|
+
export interface ApiOptions {
|
|
4
|
+
endpoint: string;
|
|
5
|
+
headers?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
6
|
+
credentials: RequestCredentials;
|
|
7
|
+
}
|
|
8
|
+
/** The API answered that nobody is signed in (or the gate in front of it did). */
|
|
9
|
+
export declare class SignedOut extends Error {
|
|
10
|
+
readonly loginUrl?: string | undefined;
|
|
11
|
+
constructor(loginUrl?: string | undefined);
|
|
12
|
+
}
|
|
13
|
+
/** Signed in, but not allowed to use comments here. */
|
|
14
|
+
export declare class Forbidden extends Error {
|
|
15
|
+
}
|
|
16
|
+
export declare class ApiError extends Error {
|
|
17
|
+
readonly status: number;
|
|
18
|
+
constructor(message: string, status: number);
|
|
19
|
+
}
|
|
20
|
+
export declare function createApi(options: ApiOptions): {
|
|
21
|
+
/** A path the API returned (a screenshot), as a URL the browser can load: it's relative to the API's host. */
|
|
22
|
+
resolve: (path: string) => string;
|
|
23
|
+
me: () => Promise<Identity>;
|
|
24
|
+
threads: (page: string) => Promise<Thread[]>;
|
|
25
|
+
thread: (threadId: string) => Promise<Thread>;
|
|
26
|
+
/** Opens a thread, sending its screenshots along in the same request when there are any. */
|
|
27
|
+
createThread: (input: {
|
|
28
|
+
page: string;
|
|
29
|
+
url: string;
|
|
30
|
+
anchor: Anchor;
|
|
31
|
+
context?: ThreadContext;
|
|
32
|
+
body: string;
|
|
33
|
+
}, shots?: Shots) => Promise<Thread>;
|
|
34
|
+
reply: (threadId: string, body: string) => Promise<Thread>;
|
|
35
|
+
/** Moves a thread's pin to a new spot on the page. Only its author may. */
|
|
36
|
+
moveThread: (threadId: string, anchor: Anchor) => Promise<Thread>;
|
|
37
|
+
setStatus: (threadId: string, status: ThreadStatus) => Promise<Thread>;
|
|
38
|
+
deleteComment: (commentId: string) => Promise<{
|
|
39
|
+
deleted: "comment" | "thread";
|
|
40
|
+
threadId: string;
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
export type Api = ReturnType<typeof createApi>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Api } from "./api";
|
|
2
|
+
import { type Tracker } from "./tracker";
|
|
3
|
+
export interface Settings {
|
|
4
|
+
page: () => string;
|
|
5
|
+
shortcut: string | false;
|
|
6
|
+
pollInterval: number;
|
|
7
|
+
linkParam: string;
|
|
8
|
+
/** "push" moves the page over to make room for the toolbar; "overlay" draws it on top. */
|
|
9
|
+
dock: "push" | "overlay";
|
|
10
|
+
}
|
|
11
|
+
/** What mountComments hands back, wired up once the app is running. */
|
|
12
|
+
export interface Controls {
|
|
13
|
+
open?: (threadId: string) => void;
|
|
14
|
+
setPlacing?: (placing: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
interface RootProps {
|
|
17
|
+
api: Api;
|
|
18
|
+
host: HTMLElement;
|
|
19
|
+
tracker: Tracker;
|
|
20
|
+
settings: Settings;
|
|
21
|
+
controls: Controls;
|
|
22
|
+
colorScheme: "light" | "dark" | undefined;
|
|
23
|
+
zIndex: number;
|
|
24
|
+
}
|
|
25
|
+
/** Spectrum's Provider, with overlays (menus, tooltips, dialogs) kept inside the shadow root. */
|
|
26
|
+
export declare function Root({ colorScheme, zIndex, ...props }: RootProps): import("react").JSX.Element;
|
|
27
|
+
type AppProps = Omit<RootProps, "colorScheme" | "zIndex">;
|
|
28
|
+
export declare function App({ api, host, tracker, settings, controls }: AppProps): import("react").JSX.Element | null;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ScreenshotKind, ThreadContext } from "../shared/types";
|
|
2
|
+
export interface Shot {
|
|
3
|
+
blob: Blob;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export type Shots = Partial<Record<ScreenshotKind, Shot>>;
|
|
8
|
+
/** What the commenter is looking at: the element and the page around it. */
|
|
9
|
+
export declare function contextOf(element: Element): ThreadContext;
|
|
10
|
+
/**
|
|
11
|
+
* Two screenshots from one render of the viewport, drawn from the DOM and
|
|
12
|
+
* leaving the widget out: the viewport as it was, and the element in its
|
|
13
|
+
* surroundings with an outline round it. None comes back when the page can't
|
|
14
|
+
* be drawn (a render that times out); a comment never waits on them for long.
|
|
15
|
+
*/
|
|
16
|
+
export declare function capture(element: Element, host: Element): Promise<Shots>;
|
|
17
|
+
/** The part of the viewport to show for an element: it, a margin, at least a minimum size, inside the viewport. */
|
|
18
|
+
export declare function cropAround(rect: {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}, viewport: {
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
}): {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
/** Waits for screenshots, but never longer than `ms`: a comment goes out without them rather than late. */
|
|
33
|
+
export declare function within(shots: Promise<Shots>, ms: number): Promise<Shots>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function colorFor(id: string): string;
|
|
2
|
+
/** An image for Spectrum's Avatar: the person's picture, or their initials on a color. */
|
|
3
|
+
export declare function avatarSrc(person: {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
avatarUrl?: string;
|
|
7
|
+
}): string;
|
|
8
|
+
/** "Ada Lovelace" → "AL", "林黛玉" → "林" */
|
|
9
|
+
export declare function initials(name: string): string;
|
|
10
|
+
export declare function timeAgo(iso: string, now: number): string;
|
|
11
|
+
/** Splits text into plain strings and links (with where each starts), for rendering without innerHTML. */
|
|
12
|
+
export declare function linkify(text: string): Array<string | {
|
|
13
|
+
href: string;
|
|
14
|
+
start: number;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Settings } from "./app";
|
|
2
|
+
export type * from "../shared/types";
|
|
3
|
+
export interface MountOptions {
|
|
4
|
+
/** Where the comments API is mounted. Default "/__comments". */
|
|
5
|
+
endpoint?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Which page a comment belongs to. Default: the path. Return a route
|
|
8
|
+
* pattern (e.g. "/:org/settings") to share comments across ids.
|
|
9
|
+
*/
|
|
10
|
+
page?: () => string;
|
|
11
|
+
/** Extra request headers, e.g. a bearer token the host app holds. */
|
|
12
|
+
headers?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
13
|
+
/** Default "include", so cookie-based gates such as Cloudflare Access work cross-origin too. */
|
|
14
|
+
credentials?: RequestCredentials;
|
|
15
|
+
/**
|
|
16
|
+
* The toolbar is docked on the right. "push" (default) moves the page over
|
|
17
|
+
* to make room for it; "overlay" draws it on top of the page.
|
|
18
|
+
*/
|
|
19
|
+
dock?: Settings["dock"];
|
|
20
|
+
/** Key that toggles comment mode. Default "c"; false turns it off. */
|
|
21
|
+
shortcut?: string | false;
|
|
22
|
+
theme?: "auto" | "light" | "dark";
|
|
23
|
+
/** Default: just under the largest z-index. */
|
|
24
|
+
zIndex?: number;
|
|
25
|
+
/** How often to look for new comments, in ms. Default 15000. */
|
|
26
|
+
pollInterval?: number;
|
|
27
|
+
/** Query parameter that links to a thread. Default "comment". */
|
|
28
|
+
linkParam?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface CommentsHandle {
|
|
31
|
+
/** Opens a thread on this page by id. */
|
|
32
|
+
open(threadId: string): void;
|
|
33
|
+
/** Turns comment mode on or off. */
|
|
34
|
+
setCommentMode(on: boolean): void;
|
|
35
|
+
unmount(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Puts the comments toolbar on the page for people the API accepts; everyone
|
|
39
|
+
* else sees nothing. Call unmount() to take it off again.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mountComments(options?: MountOptions): CommentsHandle;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Everything that styles the widget, injected into its shadow root. */
|
|
2
|
+
export declare const SHADOW_CSS: string;
|
|
3
|
+
/** @font-face and @property rules, which only work in the document. */
|
|
4
|
+
export declare const DOCUMENT_CSS: string;
|
|
5
|
+
/** The widget's own base rules, and keyframes its styles refer to by name. */
|
|
6
|
+
export declare const BASE_CSS = "\nimg { -webkit-user-drag: none; user-select: none; }\n/* The toolbar wears construction stripes in the accent colour: a tool for the people building the page, not part of it. */\n.wc-rail {\n background-image: repeating-linear-gradient(\n 45deg,\n light-dark(rgb(59 99 251 / 0.05), rgb(93 137 255 / 0.06)) 0 10px,\n light-dark(rgb(59 99 251 / 0.11), rgb(93 137 255 / 0.12)) 10px 20px\n );\n}\n@keyframes wc-pop-in {\n from { opacity: 0; transform: scale(0.5); animation-timing-function: cubic-bezier(0, 0, 0.4, 1); }\n 60% { opacity: 1; transform: scale(1.12); animation-timing-function: cubic-bezier(0.45, 0, 0.4, 1); }\n to { opacity: 1; transform: scale(1); }\n}\n@keyframes wc-grow-in {\n from { opacity: 0; transform: scale(0.96); }\n to { opacity: 1; transform: scale(1); }\n}\n";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Author, Identity, Thread } from "../shared/types";
|
|
2
|
+
import type { Api } from "./api";
|
|
3
|
+
export declare function PersonAvatar({ person, size, }: {
|
|
4
|
+
person: Pick<Author, "id" | "name" | "avatarUrl">;
|
|
5
|
+
size?: 20 | 24 | 28 | 32;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
interface ComposerProps {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string;
|
|
10
|
+
onChange: (value: string) => void;
|
|
11
|
+
onSubmit: () => Promise<void>;
|
|
12
|
+
/** Escape calls this: drops a draft, or closes the thread. */
|
|
13
|
+
onCancel?: () => void;
|
|
14
|
+
autoFocus?: boolean;
|
|
15
|
+
/** Floats on its own over the page (a new comment) rather than inside a card. */
|
|
16
|
+
floating?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function Composer(props: ComposerProps): import("react").JSX.Element;
|
|
19
|
+
interface ThreadViewProps {
|
|
20
|
+
thread: Thread;
|
|
21
|
+
me: Identity;
|
|
22
|
+
api: Api;
|
|
23
|
+
now: number;
|
|
24
|
+
linkFor: (thread: Thread) => string;
|
|
25
|
+
onChange: (thread: Thread) => void;
|
|
26
|
+
onDeleted: (threadId: string) => void;
|
|
27
|
+
onClose?: () => void;
|
|
28
|
+
/** Shown inside the panel rather than as a card by its pin. */
|
|
29
|
+
inline?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function ThreadView(props: ThreadViewProps): import("react").JSX.Element;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Anchor } from "../shared/types";
|
|
2
|
+
export interface Box {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
/** The element a comment is on, for cards that sit under it. */
|
|
8
|
+
rect?: {
|
|
9
|
+
left: number;
|
|
10
|
+
top: number;
|
|
11
|
+
right: number;
|
|
12
|
+
bottom: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
type Place = (node: HTMLElement, box: Box) => void;
|
|
16
|
+
interface Item {
|
|
17
|
+
node: HTMLElement;
|
|
18
|
+
box: () => Box | null;
|
|
19
|
+
place: Place;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Moves pins and cards with their elements every frame, through the
|
|
23
|
+
* `translate` property so a hover `scale` grows the pin in place instead of
|
|
24
|
+
* scaling its offset from the corner of the viewport. Positions are written
|
|
25
|
+
* straight to the DOM so scrolling never re-renders the widget.
|
|
26
|
+
*/
|
|
27
|
+
export declare class Tracker {
|
|
28
|
+
private items;
|
|
29
|
+
private ticks;
|
|
30
|
+
private frame;
|
|
31
|
+
set(key: string, item: Item): void;
|
|
32
|
+
delete(key: string): void;
|
|
33
|
+
/** Runs a callback on every frame, for cheap checks such as "did the page change?". */
|
|
34
|
+
onTick(callback: () => void): () => void;
|
|
35
|
+
start(): void;
|
|
36
|
+
stop(): void;
|
|
37
|
+
private place;
|
|
38
|
+
}
|
|
39
|
+
export declare const placePin: Place;
|
|
40
|
+
export declare const placeOutline: Place;
|
|
41
|
+
/**
|
|
42
|
+
* Places a card under the element its comment is on, left edges aligned, or
|
|
43
|
+
* above it when there's no room below. An element too tall for either gets
|
|
44
|
+
* the card beside the pin. Everything stays left of `rightEdge()` (the docked
|
|
45
|
+
* toolbar, and the panel when it's open). The transform origin follows the
|
|
46
|
+
* pin, so an entering card grows out of it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function placeCardNear(rightEdge: () => number): Place;
|
|
49
|
+
/** Remembers which element each thread is on, looking again when the page replaces it. */
|
|
50
|
+
export declare class AnchorCache {
|
|
51
|
+
private entries;
|
|
52
|
+
element(id: string, anchor: Anchor): Element | null;
|
|
53
|
+
/** Where a thread's pin goes right now, or null when its element is gone or hidden. */
|
|
54
|
+
point(id: string, anchor: Anchor): Box | null;
|
|
55
|
+
/** The pin's point plus its element's box, for the thread's card. */
|
|
56
|
+
card(id: string, anchor: Anchor): Box | null;
|
|
57
|
+
/** The box of a thread's element, for an outline around it. */
|
|
58
|
+
outline(id: string, anchor: Anchor): Box | null;
|
|
59
|
+
/** Looks for a thread's element afresh, e.g. after it was moved. */
|
|
60
|
+
forget(id: string): void;
|
|
61
|
+
clear(): void;
|
|
62
|
+
}
|
|
63
|
+
export declare function pointOn(element: Element, anchor: Pick<Anchor, "x" | "y">): Box | null;
|
|
64
|
+
/** An element's box, for an outline drawn around it. */
|
|
65
|
+
export declare function outlineOf(element: Element | null | undefined): Box | null;
|
|
66
|
+
/** Where a comment's card goes: the pin's point, plus the element's box to sit under. */
|
|
67
|
+
export declare function cardBoxOf(element: Element | null | undefined, anchor: Pick<Anchor, "x" | "y">): Box | null;
|
|
68
|
+
export {};
|