helldots 0.4.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 +5 -1451
- package/dist/helldots.esm.js.map +4 -4
- package/dist/helldots.umd.js +13 -1459
- package/dist/index.d.ts +110 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,12 +73,34 @@ export function Comments({ user }) {
|
|
|
73
73
|
}
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### Single-page apps
|
|
77
|
+
|
|
78
|
+
A client-side router swaps the DOM without a page load, so tell the widget
|
|
79
|
+
when a navigation happened and let its own cross-page jumps use your router:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
const overlay = createCommentOverlay({
|
|
83
|
+
user,
|
|
84
|
+
persistence: "localStorage",
|
|
85
|
+
navigate: (page) => router.push(page), // "view on its page" without a reload
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// After every route render (React Router, Vue Router, …)
|
|
89
|
+
router.afterEach(() => overlay.notifyNavigation());
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`notifyNavigation()` reclassifies every comment against the new URL,
|
|
93
|
+
re-resolves anchors against the new DOM and rebuilds the markers. Calling it
|
|
94
|
+
after a same-path re-render is also the way to re-anchor when your app
|
|
95
|
+
replaced the route's DOM. `autoDetectNavigation: true` additionally covers
|
|
96
|
+
back/forward (popstate) automatically.
|
|
97
|
+
|
|
76
98
|
## What gets captured
|
|
77
99
|
|
|
78
100
|
When someone leaves a comment, HellDots records more than the text:
|
|
79
101
|
|
|
80
102
|
**A screenshot of the page as they saw it.** Taken automatically, JPEG at half
|
|
81
|
-
scale (~30–100 KB). The widget's own UI is
|
|
103
|
+
scale (~30–100 KB). The widget's own UI is excluded from the capture, so the
|
|
82
104
|
toolbar never ends up inside the image. Dragging a region additionally attaches
|
|
83
105
|
a full-resolution PNG crop of exactly what was selected.
|
|
84
106
|
|
|
@@ -94,6 +116,31 @@ orphaned rather than silently dropped.
|
|
|
94
116
|
Set `autoScreenshot: false` to skip the capture — the render costs a moment
|
|
95
117
|
on every comment, and some apps would rather not pay it.
|
|
96
118
|
|
|
119
|
+
### Web fonts in screenshots
|
|
120
|
+
|
|
121
|
+
A screenshot is not a screen grab: the browser exposes no way to rasterize
|
|
122
|
+
the painted page from JavaScript, so the capture is a re-render of the DOM.
|
|
123
|
+
Anything the re-render cannot reach is missing from it — web fonts included.
|
|
124
|
+
|
|
125
|
+
A font loaded through a cross-origin `<link>` (Google Fonts and friends) is
|
|
126
|
+
one of those. Reading `cssRules` on such a stylesheet throws `SecurityError`,
|
|
127
|
+
so its `@font-face` never reaches the capture and the text comes out in a
|
|
128
|
+
fallback face. That is not only cosmetic: the fallback's metrics differ, so
|
|
129
|
+
glyphs sit at different positions than on screen, and a drag selection tight
|
|
130
|
+
around a few letters can come back holding the wrong ones.
|
|
131
|
+
|
|
132
|
+
Three ways out, cheapest first:
|
|
133
|
+
|
|
134
|
+
- **Self-host the font**, or add `crossorigin` to the `<link>`. The
|
|
135
|
+
stylesheet becomes readable and the capture matches the page, with no
|
|
136
|
+
extra requests at capture time.
|
|
137
|
+
- **`embedCrossOriginFonts: true`.** HellDots re-fetches those stylesheets
|
|
138
|
+
(the same URLs the page already loaded, cached per session) and hands them
|
|
139
|
+
to the renderer. Off by default: a comment widget making third-party
|
|
140
|
+
requests on your users' behalf should be your call, not ours.
|
|
141
|
+
- **Leave it.** Captures of such a page stay misaligned where text is
|
|
142
|
+
concerned; everything else about them is correct.
|
|
143
|
+
|
|
97
144
|
## Triage
|
|
98
145
|
|
|
99
146
|
Comments carry an optional type, priority and free-form tags. All three start
|
|
@@ -145,22 +192,48 @@ OS: iOS 17.2
|
|
|
145
192
|
|
|
146
193
|
## Options
|
|
147
194
|
|
|
148
|
-
| Option
|
|
149
|
-
|
|
|
150
|
-
| `user`
|
|
151
|
-
| `persistence`
|
|
152
|
-
| `autoScreenshot`
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
195
|
+
| Option | Type | Default | |
|
|
196
|
+
| ----------------------- | -------------------------------- | ------------------- | ----------------------------------------------------------------- |
|
|
197
|
+
| `user` | `{ name: string }` | `"Anonymous"` | Author of new comments and replies |
|
|
198
|
+
| `persistence` | `"localStorage"` \| `"none"` | `"none"` | Auto save/restore, or handle it yourself via callbacks |
|
|
199
|
+
| `autoScreenshot` | `boolean` | `true` | Capture a screenshot and environment snapshot per comment |
|
|
200
|
+
| `embedCrossOriginFonts` | `boolean` | `false` | Fetch unreadable stylesheets so their web fonts reach the capture |
|
|
201
|
+
| `locale` | `string` | browser language | `"en"` and `"es"` ship; anything else falls back per key |
|
|
202
|
+
| `linkParam` | `string` | `"helldotsComment"` | Query param used by "Copy link" URLs |
|
|
203
|
+
| `navigate` | `(page: string) => void` | full page load | SPA router hook for the widget's cross-page jumps |
|
|
204
|
+
| `autoDetectNavigation` | `boolean` | `false` | Run `notifyNavigation()` on popstate (back/forward) |
|
|
205
|
+
| `shortcutKey` | `string` | `"c"` | Key that toggles comment mode |
|
|
206
|
+
| `shortcutModifier` | `"alt"` \| `"ctrl"` \| `"shift"` | `"alt"` | Modifier for that key |
|
|
207
|
+
| `autoInit` | `boolean` | `true` | When `false`, returns an initializer to call yourself |
|
|
157
208
|
|
|
158
209
|
### Callbacks
|
|
159
210
|
|
|
211
|
+
Every change is also available as one stream, which is usually what you want
|
|
212
|
+
when the whole thing syncs to a single endpoint:
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
createCommentOverlay({
|
|
216
|
+
onChange: (event) => {
|
|
217
|
+
// "comment:created" | "comment:edited" | "comment:deleted"
|
|
218
|
+
// "comment:status-changed" | "comment:updated" | "comment:anchor-lost"
|
|
219
|
+
// "reply:added" | "reply:deleted" | "reply:edited"
|
|
220
|
+
api.post("/helldots-events", event);
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`ChangeEvent` is a discriminated union: switch on `event.type` and
|
|
226
|
+
TypeScript narrows the payload. The specific callbacks below carry the same
|
|
227
|
+
events at the same moments — subscribe either way, or both. A handler that
|
|
228
|
+
throws is caught and warned about, never rolling back the change.
|
|
229
|
+
|
|
160
230
|
| Callback | Fires when |
|
|
161
231
|
| --------------------------------- | -------------------------------------------------- |
|
|
162
232
|
| `onCommentCreated(comment)` | A new comment is saved |
|
|
163
233
|
| `onReplyAdded(comment, reply)` | A reply is added to any comment |
|
|
234
|
+
| `onReplyDeleted(comment, reply)` | A reply is removed |
|
|
235
|
+
| `onCommentEdited(comment)` | A comment's text is rewritten |
|
|
236
|
+
| `onReplyEdited(comment, reply)` | A reply's text is rewritten |
|
|
164
237
|
| `onCommentStatusChanged(comment)` | Status moves between open / in progress / resolved |
|
|
165
238
|
| `onCommentUpdated(comment)` | Type, priority or tags change |
|
|
166
239
|
| `onCommentDeleted(id)` | A comment is removed |
|
|
@@ -174,9 +247,15 @@ const overlay = createCommentOverlay(options);
|
|
|
174
247
|
overlay.comments; // Comment[]
|
|
175
248
|
overlay.commentMode; // boolean
|
|
176
249
|
overlay.toggleCommentMode();
|
|
177
|
-
overlay.addReply(
|
|
250
|
+
overlay.addReply(commentOrId, text, screenshots?); // → CommentReply | null
|
|
251
|
+
overlay.deleteReply(commentId, replyId); // → boolean
|
|
252
|
+
overlay.editComment(id, text); // → boolean
|
|
253
|
+
overlay.editReply(commentId, replyId, text); // → boolean
|
|
254
|
+
overlay.commentLink(id); // → string | null (shareable URL)
|
|
178
255
|
overlay.serializeComments(); // → SerializedComment[]
|
|
179
256
|
overlay.loadComments(data); // → { anchored, orphaned, inactive }
|
|
257
|
+
overlay.notifyNavigation(); // re-sync after a client-side navigation
|
|
258
|
+
overlay.clearComments(); // bulk reset, fires no callbacks
|
|
180
259
|
overlay.deleteComment(id); // → boolean
|
|
181
260
|
overlay.setCommentStatus(id, status); // → boolean
|
|
182
261
|
overlay.setCommentType(id, type); // → boolean
|
|
@@ -186,7 +265,9 @@ overlay.cleanup(); // remove the widget entirely
|
|
|
186
265
|
```
|
|
187
266
|
|
|
188
267
|
The setters return `false` for an unknown id or an invalid value, and make no
|
|
189
|
-
change when they do.
|
|
268
|
+
change when they do. To reconcile against a backend after remote deletions,
|
|
269
|
+
call `clearComments()` and then `loadComments(freshData)` — `loadComments`
|
|
270
|
+
alone replaces by id but never removes.
|
|
190
271
|
|
|
191
272
|
TypeScript definitions ship with the package — no `@types` install needed.
|
|
192
273
|
|
|
@@ -196,6 +277,13 @@ With `persistence: "localStorage"`, every comment (screenshot included) lives
|
|
|
196
277
|
under a single key shared across all pages of your app. Browsers cap that at
|
|
197
278
|
roughly 5 MB, which is on the order of a hundred comments with screenshots.
|
|
198
279
|
|
|
280
|
+
The mode assumes one active tab per page: writes from another tab are
|
|
281
|
+
preserved on the next sync, but two tabs editing the same comment
|
|
282
|
+
concurrently resolve last-write-wins, and a comment deleted in one tab can
|
|
283
|
+
reappear if another tab still holding it in memory saves afterwards. Hosts
|
|
284
|
+
that need real multi-tab editing should persist through the callbacks
|
|
285
|
+
instead.
|
|
286
|
+
|
|
199
287
|
When the quota is reached, HellDots sheds the _automatic_ screenshots of the
|
|
200
288
|
oldest comments and retries, so the comments themselves survive. Screenshots a
|
|
201
289
|
user deliberately attached are never discarded. If you expect heavy use, wire
|
|
@@ -209,8 +297,8 @@ page's CSS cannot leak into it and its styles cannot leak out.
|
|
|
209
297
|
## ESM only
|
|
210
298
|
|
|
211
299
|
This package ships ES modules only. `import` works everywhere — bundlers, Vite,
|
|
212
|
-
Next.js, native `<script type="module">`. There is no CommonJS
|
|
213
|
-
`require("helldots")` will not work.
|
|
300
|
+
Next.js, Node ≥ 18, native `<script type="module">`. There is no CommonJS
|
|
301
|
+
build, so `require("helldots")` will not work.
|
|
214
302
|
|
|
215
303
|
For a plain `<script>` tag with no bundler, a self-contained UMD build is on
|
|
216
304
|
the CDN:
|