@unhingged/vizu-core 0.1.23 → 0.2.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/dist/auto.cjs +127 -323
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +124 -320
- package/dist/auto.js.map +1 -1
- package/dist/index.cjs +117 -325
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -113
- package/dist/index.d.ts +50 -113
- package/dist/index.js +114 -319
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +205 -18
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +122 -2
- package/dist/internal.d.ts +122 -2
- package/dist/internal.js +199 -15
- package/dist/internal.js.map +1 -1
- package/dist/vizu.min.js +47 -47
- package/dist/vizu.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9,9 +9,9 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
var __copyProps = (to, from, except, desc) => {
|
|
11
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let
|
|
13
|
-
if (!__hasOwnProp.call(to,
|
|
14
|
-
__defProp(to,
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
@@ -21,9 +21,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
CloudStorageAdapter: () => CloudStorageAdapter,
|
|
24
|
-
InMemoryStorageAdapter: () => InMemoryStorageAdapter,
|
|
25
|
-
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
26
|
-
NullStorageAdapter: () => NullStorageAdapter,
|
|
27
24
|
SCHEMA_VERSION: () => SCHEMA_VERSION,
|
|
28
25
|
Vizu: () => Vizu,
|
|
29
26
|
findByFingerprint: () => findByFingerprint,
|
|
@@ -168,204 +165,6 @@ function needsPersist(raws) {
|
|
|
168
165
|
return false;
|
|
169
166
|
}
|
|
170
167
|
|
|
171
|
-
// src/storage.ts
|
|
172
|
-
var key = (ns) => `vizu:comments:${ns}`;
|
|
173
|
-
var LocalStorageAdapter = class {
|
|
174
|
-
async load(namespace) {
|
|
175
|
-
return readArray(namespace);
|
|
176
|
-
}
|
|
177
|
-
async addComment(namespace, comment) {
|
|
178
|
-
const list = readArray(namespace);
|
|
179
|
-
list.push(comment);
|
|
180
|
-
writeArray(namespace, list);
|
|
181
|
-
}
|
|
182
|
-
async updateComment(namespace, id, patch) {
|
|
183
|
-
const list = readArray(namespace);
|
|
184
|
-
const idx = list.findIndex((c) => c.id === id);
|
|
185
|
-
if (idx === -1) return null;
|
|
186
|
-
const next = { ...list[idx], ...patch, id: list[idx].id };
|
|
187
|
-
list[idx] = next;
|
|
188
|
-
writeArray(namespace, list);
|
|
189
|
-
return next;
|
|
190
|
-
}
|
|
191
|
-
async removeComment(namespace, id) {
|
|
192
|
-
const list = readArray(namespace);
|
|
193
|
-
writeArray(namespace, list.filter((c) => c.id !== id));
|
|
194
|
-
}
|
|
195
|
-
async setAll(namespace, comments) {
|
|
196
|
-
writeArray(namespace, comments);
|
|
197
|
-
}
|
|
198
|
-
async clear(namespace) {
|
|
199
|
-
if (typeof localStorage === "undefined") return;
|
|
200
|
-
localStorage.removeItem(key(namespace));
|
|
201
|
-
}
|
|
202
|
-
async addReply(namespace, commentId, reply) {
|
|
203
|
-
const list = readArray(namespace);
|
|
204
|
-
const idx = list.findIndex((c) => c.id === commentId);
|
|
205
|
-
if (idx === -1) return null;
|
|
206
|
-
const next = { ...list[idx], replies: [...list[idx].replies ?? [], reply] };
|
|
207
|
-
list[idx] = next;
|
|
208
|
-
writeArray(namespace, list);
|
|
209
|
-
return next;
|
|
210
|
-
}
|
|
211
|
-
async removeReply(namespace, commentId, replyId) {
|
|
212
|
-
const list = readArray(namespace);
|
|
213
|
-
const idx = list.findIndex((c) => c.id === commentId);
|
|
214
|
-
if (idx === -1) return null;
|
|
215
|
-
const next = {
|
|
216
|
-
...list[idx],
|
|
217
|
-
replies: (list[idx].replies ?? []).filter((r) => r.id !== replyId)
|
|
218
|
-
};
|
|
219
|
-
list[idx] = next;
|
|
220
|
-
writeArray(namespace, list);
|
|
221
|
-
return next;
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
function readArray(namespace) {
|
|
225
|
-
if (typeof localStorage === "undefined") return [];
|
|
226
|
-
const raw = localStorage.getItem(key(namespace));
|
|
227
|
-
if (!raw) return [];
|
|
228
|
-
try {
|
|
229
|
-
const parsed = JSON.parse(raw);
|
|
230
|
-
return Array.isArray(parsed) ? migrateComments(parsed) : [];
|
|
231
|
-
} catch {
|
|
232
|
-
return [];
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function writeArray(namespace, comments) {
|
|
236
|
-
if (typeof localStorage === "undefined") return;
|
|
237
|
-
localStorage.setItem(key(namespace), JSON.stringify(comments));
|
|
238
|
-
}
|
|
239
|
-
var InMemoryStorageAdapter = class {
|
|
240
|
-
constructor() {
|
|
241
|
-
this.store = /* @__PURE__ */ new Map();
|
|
242
|
-
}
|
|
243
|
-
async load(namespace) {
|
|
244
|
-
return [...this.store.get(namespace) || []];
|
|
245
|
-
}
|
|
246
|
-
async addComment(namespace, comment) {
|
|
247
|
-
const list = this.store.get(namespace) ?? [];
|
|
248
|
-
this.store.set(namespace, [...list, comment]);
|
|
249
|
-
}
|
|
250
|
-
async updateComment(namespace, id, patch) {
|
|
251
|
-
const list = this.store.get(namespace) ?? [];
|
|
252
|
-
const idx = list.findIndex((c) => c.id === id);
|
|
253
|
-
if (idx === -1) return null;
|
|
254
|
-
const next = { ...list[idx], ...patch, id: list[idx].id };
|
|
255
|
-
const copy = [...list];
|
|
256
|
-
copy[idx] = next;
|
|
257
|
-
this.store.set(namespace, copy);
|
|
258
|
-
return next;
|
|
259
|
-
}
|
|
260
|
-
async removeComment(namespace, id) {
|
|
261
|
-
const list = this.store.get(namespace) ?? [];
|
|
262
|
-
this.store.set(
|
|
263
|
-
namespace,
|
|
264
|
-
list.filter((c) => c.id !== id)
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
async setAll(namespace, comments) {
|
|
268
|
-
this.store.set(namespace, [...comments]);
|
|
269
|
-
}
|
|
270
|
-
async clear(namespace) {
|
|
271
|
-
this.store.delete(namespace);
|
|
272
|
-
}
|
|
273
|
-
async addReply(namespace, commentId, reply) {
|
|
274
|
-
const list = this.store.get(namespace) ?? [];
|
|
275
|
-
const idx = list.findIndex((c) => c.id === commentId);
|
|
276
|
-
if (idx === -1) return null;
|
|
277
|
-
const next = { ...list[idx], replies: [...list[idx].replies ?? [], reply] };
|
|
278
|
-
const copy = [...list];
|
|
279
|
-
copy[idx] = next;
|
|
280
|
-
this.store.set(namespace, copy);
|
|
281
|
-
return next;
|
|
282
|
-
}
|
|
283
|
-
async removeReply(namespace, commentId, replyId) {
|
|
284
|
-
const list = this.store.get(namespace) ?? [];
|
|
285
|
-
const idx = list.findIndex((c) => c.id === commentId);
|
|
286
|
-
if (idx === -1) return null;
|
|
287
|
-
const next = {
|
|
288
|
-
...list[idx],
|
|
289
|
-
replies: (list[idx].replies ?? []).filter((r) => r.id !== replyId)
|
|
290
|
-
};
|
|
291
|
-
const copy = [...list];
|
|
292
|
-
copy[idx] = next;
|
|
293
|
-
this.store.set(namespace, copy);
|
|
294
|
-
return next;
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
var NullStorageAdapter = class {
|
|
298
|
-
async load() {
|
|
299
|
-
return [];
|
|
300
|
-
}
|
|
301
|
-
async addComment() {
|
|
302
|
-
}
|
|
303
|
-
async updateComment() {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
async removeComment() {
|
|
307
|
-
}
|
|
308
|
-
async setAll() {
|
|
309
|
-
}
|
|
310
|
-
async clear() {
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
function isV1Adapter(x) {
|
|
314
|
-
if (!x || typeof x !== "object") return false;
|
|
315
|
-
const o = x;
|
|
316
|
-
return typeof o.load === "function" && typeof o.save === "function" && typeof o.clear === "function" && typeof o.addComment !== "function";
|
|
317
|
-
}
|
|
318
|
-
function wrapV1Adapter(v1) {
|
|
319
|
-
if (!isV1Adapter(v1)) return v1;
|
|
320
|
-
let warned = false;
|
|
321
|
-
const warnOnce = () => {
|
|
322
|
-
if (warned) return;
|
|
323
|
-
warned = true;
|
|
324
|
-
if (typeof console !== "undefined") {
|
|
325
|
-
console.warn(
|
|
326
|
-
"[vizu] StorageAdapter v1 detected. Per-comment ops are emulated via full-array rewrites \u2014 upgrade your adapter to the v2 interface (addComment / updateComment / removeComment / setAll / load / clear) when you can."
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
return {
|
|
331
|
-
async load(namespace) {
|
|
332
|
-
warnOnce();
|
|
333
|
-
return v1.load(namespace);
|
|
334
|
-
},
|
|
335
|
-
async addComment(namespace, comment) {
|
|
336
|
-
warnOnce();
|
|
337
|
-
const list = await v1.load(namespace);
|
|
338
|
-
list.push(comment);
|
|
339
|
-
await v1.save(namespace, list);
|
|
340
|
-
},
|
|
341
|
-
async updateComment(namespace, id, patch) {
|
|
342
|
-
warnOnce();
|
|
343
|
-
const list = await v1.load(namespace);
|
|
344
|
-
const idx = list.findIndex((c) => c.id === id);
|
|
345
|
-
if (idx === -1) return null;
|
|
346
|
-
const next = { ...list[idx], ...patch, id: list[idx].id };
|
|
347
|
-
list[idx] = next;
|
|
348
|
-
await v1.save(namespace, list);
|
|
349
|
-
return next;
|
|
350
|
-
},
|
|
351
|
-
async removeComment(namespace, id) {
|
|
352
|
-
warnOnce();
|
|
353
|
-
const list = await v1.load(namespace);
|
|
354
|
-
await v1.save(
|
|
355
|
-
namespace,
|
|
356
|
-
list.filter((c) => c.id !== id)
|
|
357
|
-
);
|
|
358
|
-
},
|
|
359
|
-
async setAll(namespace, comments) {
|
|
360
|
-
warnOnce();
|
|
361
|
-
await v1.save(namespace, comments);
|
|
362
|
-
},
|
|
363
|
-
async clear(namespace) {
|
|
364
|
-
await v1.clear(namespace);
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
168
|
// src/cloud.ts
|
|
370
169
|
var DEFAULT_API_URL = "https://vizu.unhingged.com";
|
|
371
170
|
var POPUP_WIDTH = 480;
|
|
@@ -1422,8 +1221,8 @@ var Popover = class {
|
|
|
1422
1221
|
const removeBtn = target.closest('[data-vz="remove-anchor"]');
|
|
1423
1222
|
if (removeBtn) {
|
|
1424
1223
|
e.stopPropagation();
|
|
1425
|
-
const
|
|
1426
|
-
if (
|
|
1224
|
+
const key = removeBtn.getAttribute("data-fp-key");
|
|
1225
|
+
if (key) this.removeAnchor(key);
|
|
1427
1226
|
return;
|
|
1428
1227
|
}
|
|
1429
1228
|
const refEl = target.closest('[data-vz="ref"]');
|
|
@@ -1518,8 +1317,8 @@ var Popover = class {
|
|
|
1518
1317
|
}
|
|
1519
1318
|
/** Add another anchor to the in-progress comment (Shift+Click flow). */
|
|
1520
1319
|
addAnchor(fp, target) {
|
|
1521
|
-
const
|
|
1522
|
-
if (this.anchorFingerprints.some((x) => fingerprintKey(x) ===
|
|
1320
|
+
const key = fingerprintKey(fp);
|
|
1321
|
+
if (this.anchorFingerprints.some((x) => fingerprintKey(x) === key)) return false;
|
|
1523
1322
|
this.anchorFingerprints.push(fp);
|
|
1524
1323
|
if (target) this.anchorTargets.push(target);
|
|
1525
1324
|
this.callbacks.onAnchorsChanged([...this.anchorFingerprints]);
|
|
@@ -1528,9 +1327,9 @@ var Popover = class {
|
|
|
1528
1327
|
return true;
|
|
1529
1328
|
}
|
|
1530
1329
|
/** Remove an anchor by fingerprint key. Never removes the last anchor. */
|
|
1531
|
-
removeAnchor(
|
|
1330
|
+
removeAnchor(key) {
|
|
1532
1331
|
if (this.anchorFingerprints.length <= 1) return false;
|
|
1533
|
-
const idx = this.anchorFingerprints.findIndex((x) => fingerprintKey(x) ===
|
|
1332
|
+
const idx = this.anchorFingerprints.findIndex((x) => fingerprintKey(x) === key);
|
|
1534
1333
|
if (idx < 0) return false;
|
|
1535
1334
|
this.anchorFingerprints.splice(idx, 1);
|
|
1536
1335
|
this.anchorTargets.splice(idx, 1);
|
|
@@ -1640,9 +1439,9 @@ var Popover = class {
|
|
|
1640
1439
|
return;
|
|
1641
1440
|
}
|
|
1642
1441
|
const chips = this.anchorFingerprints.map((fp) => {
|
|
1643
|
-
const
|
|
1442
|
+
const key = fingerprintKey(fp);
|
|
1644
1443
|
const label = fingerprintLabel(fp);
|
|
1645
|
-
return `<span class="vz-anchor-chip" data-fp-key="${escapeHtml(
|
|
1444
|
+
return `<span class="vz-anchor-chip" data-fp-key="${escapeHtml(key)}">${escapeHtml(label)}<button class="vz-anchor-chip-remove" data-vz="remove-anchor" data-fp-key="${escapeHtml(key)}" aria-label="Remove anchor">\xD7</button></span>`;
|
|
1646
1445
|
}).join("");
|
|
1647
1446
|
wrap.innerHTML = `
|
|
1648
1447
|
<div class="vz-anchor-hint">Anchored to ${this.anchorFingerprints.length} elements:</div>
|
|
@@ -2369,10 +2168,10 @@ var Sidebar = class {
|
|
|
2369
2168
|
const fps = c.fingerprints || [];
|
|
2370
2169
|
const primary = fps[0];
|
|
2371
2170
|
if (!primary) continue;
|
|
2372
|
-
const
|
|
2373
|
-
const g = groups.get(
|
|
2171
|
+
const key = fingerprintKey(primary);
|
|
2172
|
+
const g = groups.get(key);
|
|
2374
2173
|
if (g) g.comments.push(c);
|
|
2375
|
-
else groups.set(
|
|
2174
|
+
else groups.set(key, { fp: primary, comments: [c] });
|
|
2376
2175
|
}
|
|
2377
2176
|
const groupHtml = [];
|
|
2378
2177
|
let gi = 0;
|
|
@@ -2433,8 +2232,8 @@ var Sidebar = class {
|
|
|
2433
2232
|
}
|
|
2434
2233
|
const chips = this.el.querySelectorAll('[data-vz="jump-fp"]');
|
|
2435
2234
|
for (const chip of chips) {
|
|
2436
|
-
const
|
|
2437
|
-
const fp = findFingerprintByKey(visible,
|
|
2235
|
+
const key = chip.getAttribute("data-fp-key");
|
|
2236
|
+
const fp = findFingerprintByKey(visible, key || "");
|
|
2438
2237
|
chip.__fp = fp;
|
|
2439
2238
|
}
|
|
2440
2239
|
}
|
|
@@ -2522,10 +2321,10 @@ function renderOrphansHtml(orphans) {
|
|
|
2522
2321
|
</div>
|
|
2523
2322
|
`;
|
|
2524
2323
|
}
|
|
2525
|
-
function findFingerprintByKey(comments,
|
|
2324
|
+
function findFingerprintByKey(comments, key) {
|
|
2526
2325
|
for (const c of comments) {
|
|
2527
2326
|
for (const fp of c.fingerprints || []) {
|
|
2528
|
-
if (fingerprintKey(fp) ===
|
|
2327
|
+
if (fingerprintKey(fp) === key) return fp;
|
|
2529
2328
|
}
|
|
2530
2329
|
}
|
|
2531
2330
|
return null;
|
|
@@ -3620,30 +3419,17 @@ var EventBus = class {
|
|
|
3620
3419
|
// src/index.ts
|
|
3621
3420
|
var DEFAULTS = {
|
|
3622
3421
|
shortcut: "mod+shift+e",
|
|
3623
|
-
namespace: "default",
|
|
3624
3422
|
accent: "#FF6647"
|
|
3625
3423
|
};
|
|
3626
|
-
function resolveStorage(storage, cloud, defaultMode, onAuthChanged) {
|
|
3627
|
-
if (cloud) {
|
|
3628
|
-
if (storage && typeof console !== "undefined") {
|
|
3629
|
-
console.warn("[vizu] Both `cloud` and `storage` set; `cloud` wins. Drop `storage` to silence this.");
|
|
3630
|
-
}
|
|
3631
|
-
return new CloudStorageAdapter({
|
|
3632
|
-
workspace: cloud.workspace,
|
|
3633
|
-
apiUrl: cloud.apiUrl,
|
|
3634
|
-
autoSignIn: cloud.autoSignIn,
|
|
3635
|
-
onAuthChanged
|
|
3636
|
-
});
|
|
3637
|
-
}
|
|
3638
|
-
if (!storage) return defaultMode === "local" ? new LocalStorageAdapter() : new InMemoryStorageAdapter();
|
|
3639
|
-
if (storage === "local") return new LocalStorageAdapter();
|
|
3640
|
-
if (storage === "memory") return new InMemoryStorageAdapter();
|
|
3641
|
-
if (storage === "none") return new NullStorageAdapter();
|
|
3642
|
-
if (isV1Adapter(storage)) return wrapV1Adapter(storage);
|
|
3643
|
-
return storage;
|
|
3644
|
-
}
|
|
3645
3424
|
var Vizu = class {
|
|
3646
|
-
constructor(options
|
|
3425
|
+
constructor(options, storageOverride) {
|
|
3426
|
+
/**
|
|
3427
|
+
* The cloud adapter when this instance runs in the normal cloud mode;
|
|
3428
|
+
* null only under the internal storage override (tests / storybook).
|
|
3429
|
+
* Auth-gated behavior (mount gate, write gate, sign-in kicks) branches
|
|
3430
|
+
* on this instead of `instanceof` checks against `this.storage`.
|
|
3431
|
+
*/
|
|
3432
|
+
this.cloud = null;
|
|
3647
3433
|
this.root = null;
|
|
3648
3434
|
this.highlighter = null;
|
|
3649
3435
|
this.popover = null;
|
|
@@ -3705,8 +3491,8 @@ var Vizu = class {
|
|
|
3705
3491
|
this.pendingTargets = [];
|
|
3706
3492
|
/**
|
|
3707
3493
|
* Subscribe to remote-change notifications from the active storage
|
|
3708
|
-
* adapter
|
|
3709
|
-
*
|
|
3494
|
+
* adapter, when it implements `subscribe` (none do today — kept as
|
|
3495
|
+
* the hook realtime lands on). Torn down on `destroy()`.
|
|
3710
3496
|
*/
|
|
3711
3497
|
this.unsubscribeStorage = null;
|
|
3712
3498
|
this.onKeyDown = (e) => {
|
|
@@ -3749,23 +3535,34 @@ var Vizu = class {
|
|
|
3749
3535
|
this.popover?.reposition();
|
|
3750
3536
|
});
|
|
3751
3537
|
};
|
|
3538
|
+
if (!storageOverride && !options?.workspace) {
|
|
3539
|
+
throw new Error(
|
|
3540
|
+
"[vizu] `workspace` is required \u2014 create one at https://vizu.unhingged.com and pass new Vizu({ workspace: 'your-slug' })."
|
|
3541
|
+
);
|
|
3542
|
+
}
|
|
3752
3543
|
this.opts = { ...options };
|
|
3753
3544
|
this.opts.shortcut = options.shortcut ?? DEFAULTS.shortcut;
|
|
3754
|
-
this.opts.namespace = options.namespace ?? DEFAULTS.namespace;
|
|
3755
3545
|
this.opts.accent = options.accent ?? DEFAULTS.accent;
|
|
3546
|
+
this.ns = options.workspace ?? "default";
|
|
3756
3547
|
this.parsedShortcut = parseShortcut(this.opts.shortcut);
|
|
3757
3548
|
this.user = options.user ?? null;
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3549
|
+
if (storageOverride) {
|
|
3550
|
+
this.storage = storageOverride;
|
|
3551
|
+
this.cloud = null;
|
|
3552
|
+
} else {
|
|
3553
|
+
this.cloud = new CloudStorageAdapter({
|
|
3554
|
+
workspace: options.workspace,
|
|
3555
|
+
apiUrl: options.apiUrl,
|
|
3556
|
+
autoSignIn: options.autoSignIn,
|
|
3557
|
+
onAuthChanged: (info) => {
|
|
3558
|
+
if (info.user) this.setUser(info.user);
|
|
3559
|
+
if (this.hasLoadedComments) {
|
|
3560
|
+
void this.loadComments();
|
|
3561
|
+
}
|
|
3766
3562
|
}
|
|
3767
|
-
}
|
|
3768
|
-
|
|
3563
|
+
});
|
|
3564
|
+
this.storage = this.cloud;
|
|
3565
|
+
}
|
|
3769
3566
|
if (options.actions) this.actions = [...options.actions];
|
|
3770
3567
|
if (options.onCommentAdded) this.bus.on("comment:added", (p) => options.onCommentAdded(p.comment));
|
|
3771
3568
|
if (options.onCommentRemoved) this.bus.on("comment:removed", (p) => options.onCommentRemoved(p.id));
|
|
@@ -3807,8 +3604,8 @@ var Vizu = class {
|
|
|
3807
3604
|
* Resolution order:
|
|
3808
3605
|
* 1. `options.mentionable` if provided — host-supplied static or
|
|
3809
3606
|
* custom resolver. Wins over everything else.
|
|
3810
|
-
* 2.
|
|
3811
|
-
* 3. Empty array (
|
|
3607
|
+
* 2. The workspace's mentionable-members API.
|
|
3608
|
+
* 3. Empty array (internal storage override, no override).
|
|
3812
3609
|
*/
|
|
3813
3610
|
async searchMentionable() {
|
|
3814
3611
|
if (this.opts.mentionable) {
|
|
@@ -3821,38 +3618,36 @@ var Vizu = class {
|
|
|
3821
3618
|
return [];
|
|
3822
3619
|
}
|
|
3823
3620
|
}
|
|
3824
|
-
if (this.
|
|
3825
|
-
return this.storage.searchMentionable();
|
|
3826
|
-
}
|
|
3621
|
+
if (this.cloud) return this.cloud.searchMentionable();
|
|
3827
3622
|
return [];
|
|
3828
3623
|
}
|
|
3829
3624
|
/**
|
|
3830
3625
|
* Whether the full Vizu UI (pill, highlighter, markers, popover) can
|
|
3831
|
-
* be mounted right now
|
|
3832
|
-
*
|
|
3833
|
-
* auth
|
|
3834
|
-
*
|
|
3835
|
-
*
|
|
3626
|
+
* be mounted right now: not until the user is signed in to the
|
|
3627
|
+
* workspace. Re-checked from `enable()` and from `setUser()` so that
|
|
3628
|
+
* the moment auth resolves, the UI appears without the user needing
|
|
3629
|
+
* to press the shortcut again. (Under the internal storage override
|
|
3630
|
+
* there is no auth concept and the UI mounts immediately.)
|
|
3836
3631
|
*/
|
|
3837
3632
|
shouldMount() {
|
|
3838
|
-
if (!
|
|
3633
|
+
if (!this.cloud) return true;
|
|
3839
3634
|
return this.user !== null;
|
|
3840
3635
|
}
|
|
3841
3636
|
/**
|
|
3842
|
-
*
|
|
3843
|
-
*
|
|
3844
|
-
*
|
|
3845
|
-
*
|
|
3846
|
-
*
|
|
3847
|
-
*
|
|
3637
|
+
* Write gate. Every entry point that creates or modifies a comment
|
|
3638
|
+
* first checks that we have a known user. Without one, the write
|
|
3639
|
+
* would land as Anonymous (or 401 on the backend) — neither is the
|
|
3640
|
+
* right UX. So we re-trigger the sign-in popup and refuse the action.
|
|
3641
|
+
* The user signs in, this.user gets set via onAuthChanged, then they
|
|
3642
|
+
* can retry the click themselves.
|
|
3848
3643
|
*
|
|
3849
3644
|
* Returns true when the caller may proceed.
|
|
3850
3645
|
*/
|
|
3851
3646
|
requireUserOrAuth() {
|
|
3852
|
-
if (!
|
|
3647
|
+
if (!this.cloud) return true;
|
|
3853
3648
|
if (this.user) return true;
|
|
3854
|
-
if (this.
|
|
3855
|
-
void this.
|
|
3649
|
+
if (this.cloud.isAccessDenied()) return false;
|
|
3650
|
+
void this.cloud.preflightAuth().catch(() => {
|
|
3856
3651
|
});
|
|
3857
3652
|
return false;
|
|
3858
3653
|
}
|
|
@@ -3879,19 +3674,19 @@ var Vizu = class {
|
|
|
3879
3674
|
return this.enabled;
|
|
3880
3675
|
}
|
|
3881
3676
|
/**
|
|
3882
|
-
* Kick the
|
|
3677
|
+
* Kick the sign-in flow. Call this alongside `enable()` from an
|
|
3883
3678
|
* explicit user gesture — a host-rendered launcher button, a menu item —
|
|
3884
|
-
* so
|
|
3885
|
-
*
|
|
3886
|
-
*
|
|
3887
|
-
*
|
|
3679
|
+
* so users get the sign-in popup immediately instead of only after
|
|
3680
|
+
* clicking an element. The keyboard shortcut goes through this same
|
|
3681
|
+
* path. No-op when already signed in, when the workspace has already
|
|
3682
|
+
* denied access this session, or under the internal storage override.
|
|
3888
3683
|
*/
|
|
3889
3684
|
requestAuth() {
|
|
3890
|
-
if (!
|
|
3685
|
+
if (!this.cloud) return;
|
|
3891
3686
|
if (this.user) return;
|
|
3892
|
-
if (this.
|
|
3893
|
-
void this.
|
|
3894
|
-
if (err?.code === "auth_canceled" || err?.code === "popup_blocked") return;
|
|
3687
|
+
if (this.cloud.isAccessDenied()) return;
|
|
3688
|
+
void this.cloud.preflightAuth().catch((err) => {
|
|
3689
|
+
if (err?.code === "auth_canceled" || err?.code === "popup_blocked" || err?.code === "auth_required") return;
|
|
3895
3690
|
if (typeof console !== "undefined") {
|
|
3896
3691
|
console.warn("[vizu] preflight auth failed:", err);
|
|
3897
3692
|
}
|
|
@@ -3932,7 +3727,7 @@ var Vizu = class {
|
|
|
3932
3727
|
}
|
|
3933
3728
|
async setComments(comments, opts) {
|
|
3934
3729
|
this.comments = migrateComments(comments);
|
|
3935
|
-
if (opts?.persist !== false) await this.storage.setAll(this.
|
|
3730
|
+
if (opts?.persist !== false) await this.storage.setAll(this.ns, this.comments);
|
|
3936
3731
|
this.bus.emit("comments:set", { comments: this.getComments() });
|
|
3937
3732
|
this.refreshUi();
|
|
3938
3733
|
}
|
|
@@ -3947,7 +3742,7 @@ var Vizu = class {
|
|
|
3947
3742
|
const { id: _i, schemaVersion: _s, ...safe } = patch;
|
|
3948
3743
|
const next = { ...this.comments[idx], ...safe };
|
|
3949
3744
|
this.comments[idx] = next;
|
|
3950
|
-
const persisted = await this.storage.updateComment(this.
|
|
3745
|
+
const persisted = await this.storage.updateComment(this.ns, id, safe);
|
|
3951
3746
|
this.bus.emit("comment:updated", { comment: persisted ?? next });
|
|
3952
3747
|
this.refreshUi();
|
|
3953
3748
|
return next;
|
|
@@ -3989,7 +3784,7 @@ var Vizu = class {
|
|
|
3989
3784
|
const idx = this.comments.findIndex((x) => x.id === c.id);
|
|
3990
3785
|
if (idx !== -1) this.comments[idx] = { ...this.comments[idx], ...patch };
|
|
3991
3786
|
try {
|
|
3992
|
-
await this.storage.updateComment(this.
|
|
3787
|
+
await this.storage.updateComment(this.ns, c.id, patch);
|
|
3993
3788
|
} catch (err) {
|
|
3994
3789
|
if (typeof console !== "undefined") {
|
|
3995
3790
|
console.warn("[vizu] self-heal write failed for", c.id, err);
|
|
@@ -4011,8 +3806,8 @@ var Vizu = class {
|
|
|
4011
3806
|
}
|
|
4012
3807
|
/**
|
|
4013
3808
|
* True iff the active storage adapter implements `uploadAttachment`.
|
|
4014
|
-
* The popover hides its upload UI when this returns false (
|
|
4015
|
-
*
|
|
3809
|
+
* The popover hides its upload UI when this returns false (only the
|
|
3810
|
+
* internal offline adapters lack uploads).
|
|
4016
3811
|
*/
|
|
4017
3812
|
canUploadAttachments() {
|
|
4018
3813
|
return typeof this.storage.uploadAttachment === "function";
|
|
@@ -4025,10 +3820,10 @@ var Vizu = class {
|
|
|
4025
3820
|
async uploadAttachment(file) {
|
|
4026
3821
|
if (!this.storage.uploadAttachment) {
|
|
4027
3822
|
throw new Error(
|
|
4028
|
-
"[vizu] active storage adapter does not support uploadAttachment
|
|
3823
|
+
"[vizu] active storage adapter does not support uploadAttachment"
|
|
4029
3824
|
);
|
|
4030
3825
|
}
|
|
4031
|
-
return this.storage.uploadAttachment(this.
|
|
3826
|
+
return this.storage.uploadAttachment(this.ns, file);
|
|
4032
3827
|
}
|
|
4033
3828
|
/**
|
|
4034
3829
|
* Append a reply to a comment. Returns the parent comment or null if
|
|
@@ -4056,9 +3851,9 @@ var Vizu = class {
|
|
|
4056
3851
|
this.comments[idx] = updated;
|
|
4057
3852
|
try {
|
|
4058
3853
|
if (this.storage.addReply) {
|
|
4059
|
-
await this.storage.addReply(this.
|
|
3854
|
+
await this.storage.addReply(this.ns, commentId, reply);
|
|
4060
3855
|
} else {
|
|
4061
|
-
await this.storage.updateComment(this.
|
|
3856
|
+
await this.storage.updateComment(this.ns, commentId, { replies: updated.replies });
|
|
4062
3857
|
}
|
|
4063
3858
|
} catch (err) {
|
|
4064
3859
|
const cur = this.comments[idx];
|
|
@@ -4098,9 +3893,9 @@ var Vizu = class {
|
|
|
4098
3893
|
const updated = { ...this.comments[idx], replies: filtered };
|
|
4099
3894
|
this.comments[idx] = updated;
|
|
4100
3895
|
if (this.storage.removeReply) {
|
|
4101
|
-
await this.storage.removeReply(this.
|
|
3896
|
+
await this.storage.removeReply(this.ns, commentId, replyId);
|
|
4102
3897
|
} else {
|
|
4103
|
-
await this.storage.updateComment(this.
|
|
3898
|
+
await this.storage.updateComment(this.ns, commentId, { replies: filtered });
|
|
4104
3899
|
}
|
|
4105
3900
|
this.bus.emit("comment:updated", { comment: updated });
|
|
4106
3901
|
this.refreshUi();
|
|
@@ -4113,7 +3908,7 @@ var Vizu = class {
|
|
|
4113
3908
|
async clearAll() {
|
|
4114
3909
|
const previous = this.comments;
|
|
4115
3910
|
this.comments = [];
|
|
4116
|
-
await this.storage.clear(this.
|
|
3911
|
+
await this.storage.clear(this.ns);
|
|
4117
3912
|
this.bus.emit("comments:cleared", { previous });
|
|
4118
3913
|
this.refreshUi();
|
|
4119
3914
|
this.popover?.close();
|
|
@@ -4154,10 +3949,10 @@ var Vizu = class {
|
|
|
4154
3949
|
}
|
|
4155
3950
|
/* ===== Internal ===== */
|
|
4156
3951
|
async loadComments() {
|
|
4157
|
-
const raw = await this.storage.load(this.
|
|
3952
|
+
const raw = await this.storage.load(this.ns);
|
|
4158
3953
|
this.comments = migrateComments(raw);
|
|
4159
3954
|
if (needsPersist(raw)) {
|
|
4160
|
-
void this.storage.setAll(this.
|
|
3955
|
+
void this.storage.setAll(this.ns, this.comments).catch(() => {
|
|
4161
3956
|
});
|
|
4162
3957
|
}
|
|
4163
3958
|
this.bus.emit("comments:loaded", { comments: this.getComments() });
|
|
@@ -4166,7 +3961,7 @@ var Vizu = class {
|
|
|
4166
3961
|
subscribeStorage() {
|
|
4167
3962
|
if (this.unsubscribeStorage) return;
|
|
4168
3963
|
if (!this.storage.subscribe) return;
|
|
4169
|
-
this.unsubscribeStorage = this.storage.subscribe(this.
|
|
3964
|
+
this.unsubscribeStorage = this.storage.subscribe(this.ns, (e) => {
|
|
4170
3965
|
if (e.type === "added") {
|
|
4171
3966
|
const fresh = migrateComment(e.comment);
|
|
4172
3967
|
if (!this.comments.some((c) => c.id === fresh.id)) {
|
|
@@ -4318,9 +4113,9 @@ var Vizu = class {
|
|
|
4318
4113
|
this.bus.emit("element:deselected", {});
|
|
4319
4114
|
}
|
|
4320
4115
|
addToSelection(fp, el) {
|
|
4321
|
-
const
|
|
4322
|
-
if (this.pendingFingerprints.some((x) => fingerprintKey(x) ===
|
|
4323
|
-
this.pendingFingerprints = this.pendingFingerprints.filter((x) => fingerprintKey(x) !==
|
|
4116
|
+
const key = fingerprintKey(fp);
|
|
4117
|
+
if (this.pendingFingerprints.some((x) => fingerprintKey(x) === key)) {
|
|
4118
|
+
this.pendingFingerprints = this.pendingFingerprints.filter((x) => fingerprintKey(x) !== key);
|
|
4324
4119
|
this.pendingTargets = this.pendingTargets.filter((t) => t !== el);
|
|
4325
4120
|
} else {
|
|
4326
4121
|
this.pendingFingerprints.push(fp);
|
|
@@ -4344,9 +4139,9 @@ var Vizu = class {
|
|
|
4344
4139
|
return (c.status ?? "open") === this.statusFilter;
|
|
4345
4140
|
}
|
|
4346
4141
|
commentsForElement(fp) {
|
|
4347
|
-
const
|
|
4142
|
+
const key = fingerprintKey(fp);
|
|
4348
4143
|
return this.comments.filter(
|
|
4349
|
-
(c) => (c.fingerprints || []).some((f) => fingerprintKey(f) ===
|
|
4144
|
+
(c) => (c.fingerprints || []).some((f) => fingerprintKey(f) === key)
|
|
4350
4145
|
);
|
|
4351
4146
|
}
|
|
4352
4147
|
async addCommentFromPopover(text, fps, references, mentions, attachments) {
|
|
@@ -4370,7 +4165,7 @@ var Vizu = class {
|
|
|
4370
4165
|
mentions: mentions ?? []
|
|
4371
4166
|
};
|
|
4372
4167
|
this.comments.push(c);
|
|
4373
|
-
await this.storage.addComment(this.
|
|
4168
|
+
await this.storage.addComment(this.ns, c);
|
|
4374
4169
|
this.bus.emit("comment:added", { comment: c });
|
|
4375
4170
|
this.popover?.update(this.commentsForElement(fps[0]));
|
|
4376
4171
|
this.refreshUi();
|
|
@@ -4381,7 +4176,7 @@ var Vizu = class {
|
|
|
4381
4176
|
const comment = this.comments[idx];
|
|
4382
4177
|
this.comments = this.comments.filter((c) => c.id !== id);
|
|
4383
4178
|
try {
|
|
4384
|
-
await this.storage.removeComment(this.
|
|
4179
|
+
await this.storage.removeComment(this.ns, id);
|
|
4385
4180
|
} catch (err) {
|
|
4386
4181
|
this.comments.splice(idx, 0, comment);
|
|
4387
4182
|
const status = err?.status;
|
|
@@ -4479,8 +4274,8 @@ var Vizu = class {
|
|
|
4479
4274
|
this.spotlights.delete(entry);
|
|
4480
4275
|
}, 1800);
|
|
4481
4276
|
}
|
|
4482
|
-
const
|
|
4483
|
-
const outline = this.outlines.get(
|
|
4277
|
+
const key = fingerprintKey(fp);
|
|
4278
|
+
const outline = this.outlines.get(key)?.outline;
|
|
4484
4279
|
if (outline) {
|
|
4485
4280
|
setTimeout(() => {
|
|
4486
4281
|
outline.classList.remove("is-pulsing");
|
|
@@ -4503,8 +4298,8 @@ var Vizu = class {
|
|
|
4503
4298
|
const bestConf = (a, b) => a === void 0 ? b : rankConf(b) > rankConf(a) ? b : a;
|
|
4504
4299
|
for (const c of this.comments) {
|
|
4505
4300
|
for (const fp of c.fingerprints || []) {
|
|
4506
|
-
const
|
|
4507
|
-
let bucket = byElement.get(
|
|
4301
|
+
const key = fingerprintKey(fp);
|
|
4302
|
+
let bucket = byElement.get(key);
|
|
4508
4303
|
if (!bucket) {
|
|
4509
4304
|
const match = findByFingerprint(fp);
|
|
4510
4305
|
bucket = {
|
|
@@ -4513,7 +4308,7 @@ var Vizu = class {
|
|
|
4513
4308
|
confidence: match.confidence,
|
|
4514
4309
|
commentIds: []
|
|
4515
4310
|
};
|
|
4516
|
-
byElement.set(
|
|
4311
|
+
byElement.set(key, bucket);
|
|
4517
4312
|
}
|
|
4518
4313
|
bucket.commentIds.push(c.id);
|
|
4519
4314
|
perComment.set(c.id, bestConf(perComment.get(c.id), bucket.confidence));
|
|
@@ -4532,7 +4327,7 @@ var Vizu = class {
|
|
|
4532
4327
|
if (this.resolvedConfidence.get(c.id) !== "drifted") continue;
|
|
4533
4328
|
void this.selfHealComment(c, byElement);
|
|
4534
4329
|
}
|
|
4535
|
-
for (const [
|
|
4330
|
+
for (const [key, info] of byElement) {
|
|
4536
4331
|
if (info.confidence === "orphaned" || !info.target) continue;
|
|
4537
4332
|
const idsForFilter = info.commentIds.filter((id) => this.commentMatchesStatusFilter(id));
|
|
4538
4333
|
if (idsForFilter.length === 0) continue;
|
|
@@ -4540,11 +4335,11 @@ var Vizu = class {
|
|
|
4540
4335
|
outlineEl.className = "vz-comment-outline";
|
|
4541
4336
|
if (info.confidence === "drifted") outlineEl.classList.add("vz-comment-outline-drifted");
|
|
4542
4337
|
this.root.appendChild(outlineEl);
|
|
4543
|
-
this.outlines.set(
|
|
4338
|
+
this.outlines.set(key, { outline: outlineEl, target: info.target, fp: info.fp });
|
|
4544
4339
|
const marker = document.createElement("button");
|
|
4545
4340
|
marker.className = "vz-marker";
|
|
4546
4341
|
if (info.confidence === "drifted") marker.classList.add("vz-marker-drifted");
|
|
4547
|
-
marker.setAttribute("data-element-key",
|
|
4342
|
+
marker.setAttribute("data-element-key", key);
|
|
4548
4343
|
marker.setAttribute("data-confidence", info.confidence);
|
|
4549
4344
|
const total = idsForFilter.length;
|
|
4550
4345
|
if (total > 1) {
|
|
@@ -4563,9 +4358,9 @@ var Vizu = class {
|
|
|
4563
4358
|
this.openPopoverFor([info.target], [info.fp], elComments);
|
|
4564
4359
|
}
|
|
4565
4360
|
});
|
|
4566
|
-
marker.addEventListener("mouseenter", () => this.pulseRelatedElements(idsForFilter,
|
|
4361
|
+
marker.addEventListener("mouseenter", () => this.pulseRelatedElements(idsForFilter, key));
|
|
4567
4362
|
this.root.appendChild(marker);
|
|
4568
|
-
this.markers.set(
|
|
4363
|
+
this.markers.set(key, { marker, target: info.target, fp: info.fp, commentIds: idsForFilter });
|
|
4569
4364
|
if (info.target) {
|
|
4570
4365
|
this.positionMarker(marker, info.target);
|
|
4571
4366
|
this.positionOutline(outlineEl, info.target);
|
|
@@ -4588,8 +4383,8 @@ var Vizu = class {
|
|
|
4588
4383
|
if (k !== selfKey) relatedKeys.add(k);
|
|
4589
4384
|
}
|
|
4590
4385
|
}
|
|
4591
|
-
for (const
|
|
4592
|
-
const outline = this.outlines.get(
|
|
4386
|
+
for (const key of relatedKeys) {
|
|
4387
|
+
const outline = this.outlines.get(key)?.outline;
|
|
4593
4388
|
if (!outline) continue;
|
|
4594
4389
|
outline.classList.remove("is-pulsing");
|
|
4595
4390
|
void outline.offsetWidth;
|
|
@@ -4623,22 +4418,22 @@ var Vizu = class {
|
|
|
4623
4418
|
syncActiveAnchorOutlines(fps) {
|
|
4624
4419
|
if (!this.root) return;
|
|
4625
4420
|
const wanted = new Set(fps.map((fp) => fingerprintKey(fp)));
|
|
4626
|
-
for (const [
|
|
4627
|
-
if (!wanted.has(
|
|
4421
|
+
for (const [key, info] of this.activeAnchorOutlines) {
|
|
4422
|
+
if (!wanted.has(key)) {
|
|
4628
4423
|
info.el.remove();
|
|
4629
|
-
this.activeAnchorOutlines.delete(
|
|
4424
|
+
this.activeAnchorOutlines.delete(key);
|
|
4630
4425
|
}
|
|
4631
4426
|
}
|
|
4632
4427
|
for (const fp of fps) {
|
|
4633
|
-
const
|
|
4634
|
-
if (this.activeAnchorOutlines.has(
|
|
4428
|
+
const key = fingerprintKey(fp);
|
|
4429
|
+
if (this.activeAnchorOutlines.has(key)) continue;
|
|
4635
4430
|
const outline = document.createElement("div");
|
|
4636
4431
|
outline.className = "vz-comment-outline is-selected-pending";
|
|
4637
4432
|
this.root.appendChild(outline);
|
|
4638
4433
|
const target = findElementByFingerprint(fp);
|
|
4639
4434
|
if (target) this.positionOutline(outline, target);
|
|
4640
4435
|
else outline.style.display = "none";
|
|
4641
|
-
this.activeAnchorOutlines.set(
|
|
4436
|
+
this.activeAnchorOutlines.set(key, { el: outline, fp });
|
|
4642
4437
|
}
|
|
4643
4438
|
}
|
|
4644
4439
|
clearActiveAnchorOutlines() {
|
|
@@ -4686,9 +4481,6 @@ function fallbackCopy(text) {
|
|
|
4686
4481
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4687
4482
|
0 && (module.exports = {
|
|
4688
4483
|
CloudStorageAdapter,
|
|
4689
|
-
InMemoryStorageAdapter,
|
|
4690
|
-
LocalStorageAdapter,
|
|
4691
|
-
NullStorageAdapter,
|
|
4692
4484
|
SCHEMA_VERSION,
|
|
4693
4485
|
Vizu,
|
|
4694
4486
|
findByFingerprint,
|