helldots 0.3.0 → 0.5.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 +102 -14
- package/dist/helldots.esm.js +6 -1016
- package/dist/helldots.esm.js.map +4 -4
- package/dist/helldots.umd.js +14 -1024
- package/dist/index.d.ts +153 -12
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -48,9 +48,27 @@ export interface CommentContext {
|
|
|
48
48
|
language: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Identifier of a comment or a reply.
|
|
53
|
+
*
|
|
54
|
+
* New ids are 21-character nanoid strings. The `number` arm is not legacy
|
|
55
|
+
* cruft to be removed later: comments created before that change are still
|
|
56
|
+
* sitting in hosts' localStorage and in their own back ends, and they keep
|
|
57
|
+
* resolving. Compare ids with `String(a) === String(b)` rather than `===`
|
|
58
|
+
* when either side may have crossed a JSON or URL boundary.
|
|
59
|
+
*/
|
|
60
|
+
export type CommentId = string | number;
|
|
61
|
+
|
|
51
62
|
export interface SerializedComment {
|
|
52
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Version of this serialized shape. Stamped as 1 by serializeComments;
|
|
65
|
+
* optional because payloads persisted before it existed have none.
|
|
66
|
+
*/
|
|
67
|
+
schemaVersion?: number;
|
|
68
|
+
id: CommentId;
|
|
53
69
|
text: string;
|
|
70
|
+
/** ISO timestamp of the last edit; null when never edited. */
|
|
71
|
+
editedAt?: string | null;
|
|
54
72
|
anchor: CommentAnchor | null;
|
|
55
73
|
/** location.pathname where the comment was created. */
|
|
56
74
|
page: string;
|
|
@@ -82,26 +100,113 @@ export interface SerializedComment {
|
|
|
82
100
|
reactions?: Record<string, string[]>;
|
|
83
101
|
}
|
|
84
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Everything that can change, as one discriminated union. Switch on `type`
|
|
105
|
+
* and TypeScript narrows the rest of the fields for you.
|
|
106
|
+
*
|
|
107
|
+
* The nine specific callbacks below carry exactly the same events at
|
|
108
|
+
* exactly the same moments; subscribe either way, or both.
|
|
109
|
+
*/
|
|
110
|
+
export type ChangeEvent =
|
|
111
|
+
| { type: "comment:created"; comment: SerializedComment }
|
|
112
|
+
| { type: "comment:edited"; comment: SerializedComment }
|
|
113
|
+
| { type: "comment:deleted"; id: CommentId }
|
|
114
|
+
| { type: "comment:status-changed"; comment: SerializedComment }
|
|
115
|
+
/** Type, priority or tags changed. */
|
|
116
|
+
| { type: "comment:updated"; comment: SerializedComment }
|
|
117
|
+
| { type: "comment:anchor-lost"; comment: SerializedComment }
|
|
118
|
+
| {
|
|
119
|
+
type: "reply:added";
|
|
120
|
+
comment: SerializedComment;
|
|
121
|
+
reply: CommentReply;
|
|
122
|
+
}
|
|
123
|
+
| {
|
|
124
|
+
type: "reply:deleted";
|
|
125
|
+
comment: SerializedComment;
|
|
126
|
+
reply: CommentReply;
|
|
127
|
+
}
|
|
128
|
+
| {
|
|
129
|
+
type: "reply:edited";
|
|
130
|
+
comment: SerializedComment;
|
|
131
|
+
reply: CommentReply;
|
|
132
|
+
};
|
|
133
|
+
|
|
85
134
|
export interface CommentOverlayOptions {
|
|
86
135
|
shortcutKey?: string;
|
|
87
136
|
shortcutModifier?: "alt" | "ctrl" | "shift";
|
|
88
137
|
autoInit?: boolean;
|
|
89
|
-
/**
|
|
90
|
-
|
|
138
|
+
/**
|
|
139
|
+
* UI language. "en" and "es" ship today; any other value falls back to
|
|
140
|
+
* English (and a locale missing individual keys falls back per key).
|
|
141
|
+
* Typed as string so a host can pass a runtime-detected code without a
|
|
142
|
+
* cast — an unknown code degrades, it never breaks.
|
|
143
|
+
*/
|
|
144
|
+
locale?: string;
|
|
91
145
|
/** Auto save/restore comments. Default: "none" (host app persists via callbacks). */
|
|
92
146
|
persistence?: "localStorage" | "none";
|
|
93
147
|
/** Capture a viewport screenshot and environment snapshot on every new comment. Default: true. */
|
|
94
148
|
autoScreenshot?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Fetch stylesheets the renderer cannot read, so their web fonts survive
|
|
151
|
+
* into screenshots. Default: false.
|
|
152
|
+
*
|
|
153
|
+
* A cross-origin `<link>` (Google Fonts and friends) throws `SecurityError`
|
|
154
|
+
* on `cssRules`, so its `@font-face` never reaches the capture and the text
|
|
155
|
+
* is rendered in a fallback face. The fallback's metrics differ, which
|
|
156
|
+
* shifts glyphs sideways — a drag selection tight around a few letters then
|
|
157
|
+
* comes back holding the wrong ones. Enabling this re-fetches those sheets
|
|
158
|
+
* (the same URLs the page already loaded, cached per session) so the
|
|
159
|
+
* capture matches the page.
|
|
160
|
+
*
|
|
161
|
+
* Off by default because a comment widget making third-party requests
|
|
162
|
+
* should be the host's decision. Leave it off and captures of such a page
|
|
163
|
+
* stay misaligned; a host can also fix it at the source by self-hosting the
|
|
164
|
+
* font or adding `crossorigin` to the `<link>`.
|
|
165
|
+
*/
|
|
166
|
+
embedCrossOriginFonts?: boolean;
|
|
95
167
|
/** Identity used as the author of new comments and replies. */
|
|
96
168
|
user?: { name: string };
|
|
169
|
+
/**
|
|
170
|
+
* Query parameter carrying a comment id in "Copy link" URLs, and read back
|
|
171
|
+
* on startup to open that comment. Default: "helldotsComment". Override it
|
|
172
|
+
* when the host already routes on that name.
|
|
173
|
+
*/
|
|
174
|
+
linkParam?: string;
|
|
175
|
+
/**
|
|
176
|
+
* Called instead of a full-page load when the widget navigates to another
|
|
177
|
+
* page (the inbox's "view on its page" jump). Hand it your SPA router's
|
|
178
|
+
* push so the app's state survives; call `notifyNavigation()` after the
|
|
179
|
+
* route renders.
|
|
180
|
+
*/
|
|
181
|
+
navigate?: (page: string) => void;
|
|
182
|
+
/**
|
|
183
|
+
* Run `notifyNavigation()` automatically on popstate (back/forward).
|
|
184
|
+
* Opt-in and popstate-only: pushState routing still needs an explicit
|
|
185
|
+
* `notifyNavigation()` call from the router's hook. Default: false.
|
|
186
|
+
*/
|
|
187
|
+
autoDetectNavigation?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Single subscription point: fires for every change, alongside whichever
|
|
190
|
+
* specific callback below carries the same event. Handy when a host syncs
|
|
191
|
+
* everything to one endpoint instead of wiring nine functions. A handler
|
|
192
|
+
* that throws is caught and warned about — it never rolls back the
|
|
193
|
+
* mutation that emitted it.
|
|
194
|
+
*/
|
|
195
|
+
onChange?: (event: ChangeEvent) => void;
|
|
97
196
|
/** Fired after a new comment is saved. */
|
|
98
197
|
onCommentCreated?: (comment: SerializedComment) => void;
|
|
99
198
|
/** Fired after a reply is added to any comment. */
|
|
100
199
|
onReplyAdded?: (comment: SerializedComment, reply: CommentReply) => void;
|
|
200
|
+
/** Fired after deleteReply removes a reply. */
|
|
201
|
+
onReplyDeleted?: (comment: SerializedComment, reply: CommentReply) => void;
|
|
202
|
+
/** Fired after editComment rewrites a comment's text. */
|
|
203
|
+
onCommentEdited?: (comment: SerializedComment) => void;
|
|
204
|
+
/** Fired after editReply rewrites a reply's text. */
|
|
205
|
+
onReplyEdited?: (comment: SerializedComment, reply: CommentReply) => void;
|
|
101
206
|
/** Fired for each comment that could not be re-anchored by loadComments. */
|
|
102
207
|
onAnchorLost?: (comment: SerializedComment) => void;
|
|
103
208
|
/** Fired after deleteComment removes a comment. */
|
|
104
|
-
onCommentDeleted?: (id:
|
|
209
|
+
onCommentDeleted?: (id: CommentId) => void;
|
|
105
210
|
/** Fired after setCommentStatus changes a comment's lifecycle state. */
|
|
106
211
|
onCommentStatusChanged?: (comment: SerializedComment) => void;
|
|
107
212
|
/** Fired after type, priority or tags change on any comment. */
|
|
@@ -109,16 +214,20 @@ export interface CommentOverlayOptions {
|
|
|
109
214
|
}
|
|
110
215
|
|
|
111
216
|
export interface CommentReply {
|
|
112
|
-
id:
|
|
217
|
+
id: CommentId;
|
|
113
218
|
text: string;
|
|
114
219
|
author: string;
|
|
115
220
|
timestamp: string;
|
|
116
221
|
screenshots?: string[];
|
|
222
|
+
/** ISO timestamp of the last edit; null when never edited. */
|
|
223
|
+
editedAt?: string | null;
|
|
117
224
|
}
|
|
118
225
|
|
|
119
226
|
export interface Comment {
|
|
120
|
-
id:
|
|
227
|
+
id: CommentId;
|
|
121
228
|
text: string;
|
|
229
|
+
/** ISO timestamp of the last edit; null when never edited. */
|
|
230
|
+
editedAt?: string | null;
|
|
122
231
|
/** Live anchor element; null while the comment is orphaned or inactive. */
|
|
123
232
|
container: HTMLElement | null;
|
|
124
233
|
relativeX: number;
|
|
@@ -166,18 +275,50 @@ export declare class CommentOverlay {
|
|
|
166
275
|
constructor(options?: Omit<CommentOverlayOptions, "autoInit">);
|
|
167
276
|
|
|
168
277
|
toggleCommentMode(): void;
|
|
169
|
-
|
|
278
|
+
/**
|
|
279
|
+
* `screenshots` are data-URLs attached to the reply. Takes the live
|
|
280
|
+
* comment or its id; null when an id does not resolve.
|
|
281
|
+
*/
|
|
282
|
+
addReply(
|
|
283
|
+
comment: Comment | CommentId,
|
|
284
|
+
text: string,
|
|
285
|
+
screenshots?: string[]
|
|
286
|
+
): CommentReply | null;
|
|
287
|
+
deleteReply(commentId: CommentId, replyId: CommentId): boolean;
|
|
288
|
+
/** Rewrites a comment's text. False when the id is unknown, the text is blank, or nothing changed. */
|
|
289
|
+
editComment(id: CommentId, text: string): boolean;
|
|
290
|
+
/** Rewrites a reply's text. Same contract as editComment. */
|
|
291
|
+
editReply(commentId: CommentId, replyId: CommentId, text: string): boolean;
|
|
292
|
+
/** The shareable URL for a comment, or null when the id is unknown. */
|
|
293
|
+
commentLink(id: CommentId): string | null;
|
|
170
294
|
serializeComments(): SerializedComment[];
|
|
171
295
|
loadComments(data: SerializedComment[]): {
|
|
172
296
|
anchored: number;
|
|
173
297
|
orphaned: number;
|
|
174
298
|
inactive: number;
|
|
175
299
|
};
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
300
|
+
/**
|
|
301
|
+
* Re-syncs the widget after a client-side navigation: reclassifies every
|
|
302
|
+
* comment against the new pathname, re-resolves anchors against the new
|
|
303
|
+
* DOM, rebuilds markers and moves the inbox onto the new page. Also the
|
|
304
|
+
* "re-anchor now" primitive for same-path re-renders.
|
|
305
|
+
*/
|
|
306
|
+
notifyNavigation(): {
|
|
307
|
+
anchored: number;
|
|
308
|
+
orphaned: number;
|
|
309
|
+
inactive: number;
|
|
310
|
+
};
|
|
311
|
+
deleteComment(id: CommentId): boolean;
|
|
312
|
+
/**
|
|
313
|
+
* Removes every comment at once (markers, memory, and their persisted
|
|
314
|
+
* entries in localStorage mode). A bulk reset for reconciling against a
|
|
315
|
+
* backend before loadComments — fires no per-comment callbacks.
|
|
316
|
+
*/
|
|
317
|
+
clearComments(): void;
|
|
318
|
+
setCommentStatus(id: CommentId, status: CommentStatus): boolean;
|
|
319
|
+
setCommentType(id: CommentId, type: CommentType | null): boolean;
|
|
320
|
+
setCommentPriority(id: CommentId, priority: CommentPriority | null): boolean;
|
|
321
|
+
setCommentTags(id: CommentId, tags: string[]): boolean;
|
|
181
322
|
cleanup(): void;
|
|
182
323
|
}
|
|
183
324
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helldots",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Drop-in comment overlay for web apps — click anywhere to leave a comment anchored to that element, with an automatic screenshot and environment capture",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comments",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"node": ">=18"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
+
"dev": "npx --yes serve -l 4173 .",
|
|
46
47
|
"test": "vitest run",
|
|
47
48
|
"test:coverage": "vitest run --coverage",
|
|
48
49
|
"lint": "eslint .",
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"eslint-config-prettier": "^10.1.8",
|
|
66
67
|
"globals": "^17.7.0",
|
|
67
68
|
"jsdom": "^29.1.1",
|
|
69
|
+
"nanoid": "^6.0.1",
|
|
68
70
|
"prettier": "^3.9.4",
|
|
69
71
|
"typescript": "^6.0.3",
|
|
70
72
|
"vitest": "^4.1.9"
|