visual-comments 0.1.0 → 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/README.md +19 -2
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +477 -0
- package/dist/core/presence.d.ts +13 -0
- package/dist/core/presence.js +25 -0
- package/dist/core/threads.svelte.d.ts +36 -2
- package/dist/core/threads.svelte.js +120 -3
- package/dist/index.d.ts +1 -1
- package/dist/server/env.d.ts +78 -0
- package/dist/server/env.js +94 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +10 -3
- package/dist/server/variables.d.ts +28 -0
- package/dist/server/variables.js +73 -0
- package/dist/shared/protocol.d.ts +4 -0
- package/dist/shared/protocol.js +4 -0
- package/dist/shared/types.d.ts +64 -0
- package/dist/sveltekit/CommentTools.svelte +98 -0
- package/dist/sveltekit/CommentTools.svelte.d.ts +6 -0
- package/dist/sveltekit/Comments.svelte +104 -0
- package/dist/sveltekit/Comments.svelte.d.ts +25 -0
- package/dist/sveltekit/Toolbar.svelte +64 -0
- package/dist/sveltekit/Toolbar.svelte.d.ts +13 -0
- package/dist/sveltekit/ToolbarButton.svelte +66 -0
- package/dist/sveltekit/ToolbarButton.svelte.d.ts +12 -0
- package/dist/sveltekit/context.d.ts +16 -0
- package/dist/sveltekit/context.js +9 -0
- package/dist/sveltekit/index.d.ts +6 -0
- package/dist/sveltekit/index.js +6 -0
- package/dist/ui/Account.svelte +45 -7
- package/dist/ui/App.svelte +12 -3
- package/dist/ui/Avatar.svelte +28 -6
- package/dist/ui/Avatar.svelte.d.ts +4 -5
- package/dist/ui/Connect.svelte +128 -0
- package/dist/ui/Connect.svelte.d.ts +6 -0
- package/dist/ui/Menu.svelte +5 -1
- package/dist/ui/Menu.svelte.d.ts +2 -0
- package/dist/ui/Overlay.svelte +15 -1
- package/dist/ui/Panel.svelte +141 -127
- package/dist/ui/Pin.svelte +22 -2
- package/dist/ui/Pin.svelte.d.ts +2 -0
- package/dist/ui/Presence.svelte +43 -0
- package/dist/ui/Presence.svelte.d.ts +8 -0
- package/dist/ui/ThreadCard.svelte +26 -2
- package/dist/ui/generated/styles.css +1 -1
- package/dist/ui/generated/styles.js +2 -2
- package/dist/ui/styles.css +21 -0
- package/dist/ui/text.d.ts +3 -1
- package/dist/ui/text.js +4 -0
- package/dist/visual-comments.svelte.d.ts +53 -4
- package/dist/visual-comments.svelte.js +154 -29
- package/dist/vite/index.d.ts +20 -0
- package/dist/vite/index.js +78 -0
- package/dist-widget/visual-comments.js +17 -10
- package/package.json +88 -66
|
@@ -17,8 +17,10 @@ export class VisualComments {
|
|
|
17
17
|
api;
|
|
18
18
|
store;
|
|
19
19
|
options = $state.raw();
|
|
20
|
-
/** Comment mode:
|
|
20
|
+
/** Comment mode: clicking a surface starts a thread. Pins always show. */
|
|
21
21
|
active = $state(false);
|
|
22
|
+
/** Hide the list while keeping comment mode active. */
|
|
23
|
+
panelCollapsed = $state(false);
|
|
22
24
|
/** The thread whose conversation is open. */
|
|
23
25
|
openId = $state();
|
|
24
26
|
draft = $state();
|
|
@@ -37,6 +39,12 @@ export class VisualComments {
|
|
|
37
39
|
cursor = $state.raw();
|
|
38
40
|
/** Opening a thread scrolls its element into view once it is found. */
|
|
39
41
|
revealing = $state();
|
|
42
|
+
/** An agent's request to join, from `#vc-connect=<id>`, while the dialog to approve it is shown. */
|
|
43
|
+
connecting = $state();
|
|
44
|
+
/** The reply field of the open thread is being written in. */
|
|
45
|
+
typing = $state(false);
|
|
46
|
+
/** Where the host says the viewer is, for others to see. */
|
|
47
|
+
whereabouts = $state.raw({});
|
|
40
48
|
doc;
|
|
41
49
|
host;
|
|
42
50
|
app;
|
|
@@ -51,11 +59,13 @@ export class VisualComments {
|
|
|
51
59
|
cursorSheet = new CSSStyleSheet();
|
|
52
60
|
focusDraftHandler;
|
|
53
61
|
linked;
|
|
62
|
+
typingTimer;
|
|
63
|
+
presenceTimer;
|
|
54
64
|
constructor(options, doc = document) {
|
|
55
65
|
this.options = options;
|
|
56
66
|
this.doc = doc;
|
|
57
67
|
this.api = new Api(options.endpoint ?? '/_comments');
|
|
58
|
-
this.store = new Threads(this.api);
|
|
68
|
+
this.store = new Threads(this.api, options.viewer);
|
|
59
69
|
this.host = doc.createElement('visual-comments');
|
|
60
70
|
this.host.setAttribute('data-vc-ignore', '');
|
|
61
71
|
this.host.style.cssText = 'position:fixed;inset:0;z-index:2147483600;pointer-events:none;display:block;';
|
|
@@ -69,6 +79,19 @@ export class VisualComments {
|
|
|
69
79
|
get open() {
|
|
70
80
|
return this.store.threads.find((thread) => thread.id === this.openId);
|
|
71
81
|
}
|
|
82
|
+
/** Everyone present in the space: people's tabs, this one included, and agents' tasks. Empty when signed out. */
|
|
83
|
+
get participants() { return this.store.participants; }
|
|
84
|
+
/** This tab's connection to the server's event stream, as in `Participant.id`, while it is open. */
|
|
85
|
+
get connection() { return this.store.connection; }
|
|
86
|
+
/** What this tab tells others: the host's location, the open thread, and whether its reply is being written. */
|
|
87
|
+
get ownPresence() {
|
|
88
|
+
return {
|
|
89
|
+
state: this.typing && this.openId !== undefined ? 'typing' : 'viewing',
|
|
90
|
+
page: this.whereabouts.page ?? (this.page || undefined),
|
|
91
|
+
surface: this.whereabouts.surface,
|
|
92
|
+
thread: this.openId
|
|
93
|
+
};
|
|
94
|
+
}
|
|
72
95
|
/** Threads shown as pins: on this page, unresolved unless resolved ones are shown or it is open. */
|
|
73
96
|
get pinned() {
|
|
74
97
|
return this.store.threads.filter((thread) => thread.page === this.page
|
|
@@ -106,6 +129,8 @@ export class VisualComments {
|
|
|
106
129
|
}
|
|
107
130
|
toggle(on = !this.active) {
|
|
108
131
|
this.active = on;
|
|
132
|
+
if (!on)
|
|
133
|
+
this.panelCollapsed = false;
|
|
109
134
|
this.cursor = undefined;
|
|
110
135
|
this.aim = undefined;
|
|
111
136
|
if (!on) {
|
|
@@ -116,6 +141,8 @@ export class VisualComments {
|
|
|
116
141
|
this.updateAim();
|
|
117
142
|
}
|
|
118
143
|
openThread(id) {
|
|
144
|
+
if (id !== this.openId)
|
|
145
|
+
this.stopTyping();
|
|
119
146
|
this.openId = id;
|
|
120
147
|
const thread = this.open;
|
|
121
148
|
if (thread?.unread)
|
|
@@ -130,7 +157,11 @@ export class VisualComments {
|
|
|
130
157
|
if (this.options.show?.(thread))
|
|
131
158
|
return;
|
|
132
159
|
if (thread.page !== this.page) {
|
|
133
|
-
|
|
160
|
+
const url = this.linkOf(thread);
|
|
161
|
+
if (this.options.navigate)
|
|
162
|
+
this.options.navigate(url, thread);
|
|
163
|
+
else
|
|
164
|
+
location.assign(url);
|
|
134
165
|
return;
|
|
135
166
|
}
|
|
136
167
|
const surface = this.surface(thread.surface);
|
|
@@ -152,6 +183,33 @@ export class VisualComments {
|
|
|
152
183
|
reveal(element);
|
|
153
184
|
this.revealing = undefined;
|
|
154
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Tells others where the viewer is: the page, by default the page key, and
|
|
188
|
+
* the surface, e.g. the frame on screen. Each call replaces the last.
|
|
189
|
+
*/
|
|
190
|
+
setPresence(update) {
|
|
191
|
+
const current = untrack(() => this.whereabouts);
|
|
192
|
+
if (current.page === update.page && current.surface === update.surface)
|
|
193
|
+
return;
|
|
194
|
+
this.whereabouts = { page: update.page, surface: update.surface };
|
|
195
|
+
}
|
|
196
|
+
/** Called by the UI as the viewer writes in the open thread; they stop typing 5 seconds after the last key. */
|
|
197
|
+
noteTyping() {
|
|
198
|
+
if (this.openId === undefined)
|
|
199
|
+
return;
|
|
200
|
+
this.typing = true;
|
|
201
|
+
clearTimeout(this.typingTimer);
|
|
202
|
+
this.typingTimer = setTimeout(() => this.stopTyping(), 5000);
|
|
203
|
+
}
|
|
204
|
+
stopTyping() {
|
|
205
|
+
clearTimeout(this.typingTimer);
|
|
206
|
+
this.typing = false;
|
|
207
|
+
}
|
|
208
|
+
/** Closes the dialog for an agent's request and takes it out of the address. */
|
|
209
|
+
endConnect() {
|
|
210
|
+
this.connecting = undefined;
|
|
211
|
+
dropFromHash('vc-connect');
|
|
212
|
+
}
|
|
155
213
|
/** Leaves for the site's sign-in, and comes back to this address. */
|
|
156
214
|
signIn() {
|
|
157
215
|
const auth = this.options.auth ?? '/auth';
|
|
@@ -181,7 +239,8 @@ export class VisualComments {
|
|
|
181
239
|
subscribe(listener) {
|
|
182
240
|
return $effect.root(() => {
|
|
183
241
|
$effect(() => {
|
|
184
|
-
void [this.active, this.openId, this.unreadCount, this.viewer, this.store.threads.length, this.loading
|
|
242
|
+
void [this.active, this.openId, this.unreadCount, this.viewer, this.store.threads.length, this.loading,
|
|
243
|
+
this.store.participants, this.store.connection];
|
|
185
244
|
listener(this);
|
|
186
245
|
});
|
|
187
246
|
});
|
|
@@ -191,6 +250,8 @@ export class VisualComments {
|
|
|
191
250
|
stop();
|
|
192
251
|
for (const stop of this.frames.values())
|
|
193
252
|
stop();
|
|
253
|
+
clearTimeout(this.typingTimer);
|
|
254
|
+
clearTimeout(this.presenceTimer);
|
|
194
255
|
this.frames.clear();
|
|
195
256
|
for (const app of this.slots.values())
|
|
196
257
|
void unmount(app);
|
|
@@ -218,12 +279,13 @@ export class VisualComments {
|
|
|
218
279
|
doc.documentElement.appendChild(this.host);
|
|
219
280
|
this.app = mount(App, { target: shadow, props: { vc: this } });
|
|
220
281
|
this.cleanup.push(this.store.start());
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
this.linked = Number(
|
|
224
|
-
|
|
225
|
-
history.replaceState(history.state, '', `${location.pathname}${location.search}${rest ? `#${rest}` : ''}`);
|
|
282
|
+
const linked = hashPart('vc');
|
|
283
|
+
if (linked && /^\d+$/.test(linked)) {
|
|
284
|
+
this.linked = Number(linked);
|
|
285
|
+
dropFromHash('vc');
|
|
226
286
|
}
|
|
287
|
+
// The hash stays until the request is answered, so it survives signing in first.
|
|
288
|
+
this.connecting = hashPart('vc-connect') || undefined;
|
|
227
289
|
this.cleanup.push($effect.root(() => {
|
|
228
290
|
// Open a linked thread once threads have loaded.
|
|
229
291
|
$effect(() => {
|
|
@@ -238,6 +300,19 @@ export class VisualComments {
|
|
|
238
300
|
this.focus(thread);
|
|
239
301
|
}
|
|
240
302
|
});
|
|
303
|
+
// Others see what this tab is doing: where it is, the open thread, and whether its reply is being written.
|
|
304
|
+
$effect(() => {
|
|
305
|
+
const presence = this.ownPresence;
|
|
306
|
+
clearTimeout(this.presenceTimer);
|
|
307
|
+
this.presenceTimer = setTimeout(() => this.store.setPresence(presence), 150);
|
|
308
|
+
});
|
|
309
|
+
// The server lets typing lapse unless it hears it again.
|
|
310
|
+
$effect(() => {
|
|
311
|
+
if (!this.typing)
|
|
312
|
+
return;
|
|
313
|
+
const timer = setInterval(() => this.store.setPresence(untrack(() => this.ownPresence), true), 3000);
|
|
314
|
+
return () => clearInterval(timer);
|
|
315
|
+
});
|
|
241
316
|
// Buttons the host marked with data-vc-toggle say whether comment mode is on and if anything is unread.
|
|
242
317
|
$effect(() => {
|
|
243
318
|
const unread = this.unreadCount > 0 && !this.active;
|
|
@@ -264,9 +339,34 @@ export class VisualComments {
|
|
|
264
339
|
on(view, 'click', (event) => this.onClick(event), capture);
|
|
265
340
|
for (const type of ['dblclick', 'auxclick', 'contextmenu'])
|
|
266
341
|
on(view, type, (event) => this.onOtherClick(event), capture);
|
|
342
|
+
// Escape closes the comments' own things before the page's (an open dialog) sees it; in the
|
|
343
|
+
// comment UI, after the UI's own handlers.
|
|
344
|
+
on(view, 'keydown', (event) => { if (!this.fromUs(event))
|
|
345
|
+
this.onEscape(event); }, capture);
|
|
267
346
|
on(view, 'keydown', (event) => this.onKey(event));
|
|
268
347
|
on(view, 'keyup', (event) => this.onKey(event));
|
|
348
|
+
// What happens in the comment UI is not the page's: a modal's focus trap (bits-ui, focus-trap, …)
|
|
349
|
+
// would take focus back from it, and a press or key in it would close the modal or work its
|
|
350
|
+
// shortcuts. Focus is stopped before any trap sees it, the rest where it leaves the UI.
|
|
351
|
+
on(view, 'focusin', (event) => { if (this.fromUs(event))
|
|
352
|
+
event.stopImmediatePropagation(); }, capture);
|
|
353
|
+
for (const type of ['keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'click', 'touchstart', 'touchend', 'focusout']) {
|
|
354
|
+
const keep = (event) => {
|
|
355
|
+
if (event instanceof KeyboardEvent) {
|
|
356
|
+
this.onEscape(event);
|
|
357
|
+
this.onKey(event);
|
|
358
|
+
}
|
|
359
|
+
event.stopPropagation();
|
|
360
|
+
};
|
|
361
|
+
this.host.addEventListener(type, keep);
|
|
362
|
+
this.cleanup.push(() => this.host.removeEventListener(type, keep));
|
|
363
|
+
}
|
|
269
364
|
on(view, 'resize', () => this.schedule());
|
|
365
|
+
on(view, 'hashchange', () => {
|
|
366
|
+
const id = hashPart('vc-connect');
|
|
367
|
+
if (id)
|
|
368
|
+
this.connecting = id;
|
|
369
|
+
});
|
|
270
370
|
on(doc, 'scroll', () => this.schedule(), { capture: true, passive: true });
|
|
271
371
|
const leave = () => this.move(undefined);
|
|
272
372
|
doc.documentElement.addEventListener('pointerleave', leave);
|
|
@@ -299,7 +399,10 @@ export class VisualComments {
|
|
|
299
399
|
if (!view || this.frames.has(frame))
|
|
300
400
|
return;
|
|
301
401
|
const schedule = () => this.schedule();
|
|
302
|
-
const key = (event) =>
|
|
402
|
+
const key = (event) => {
|
|
403
|
+
this.onEscape(event);
|
|
404
|
+
this.onKey(event);
|
|
405
|
+
};
|
|
303
406
|
const observer = new MutationObserver(schedule);
|
|
304
407
|
observer.observe(frame.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
305
408
|
frame.addEventListener('scroll', schedule, { capture: true, passive: true });
|
|
@@ -329,9 +432,12 @@ export class VisualComments {
|
|
|
329
432
|
const doc = this.doc;
|
|
330
433
|
this.surfaces = discover(doc, { ours: this.ours, documentOf: this.options.documentOf });
|
|
331
434
|
this.page = pageOf(this.options.page);
|
|
332
|
-
// A new comment on a surface that has gone, like a frame the viewer left, goes with it
|
|
435
|
+
// A new comment on a surface that has gone, like a frame the viewer left, goes with it; an
|
|
436
|
+
// empty one also with its element, like a dialog's that closed.
|
|
333
437
|
if (this.draft && !this.surface(this.draft.surface))
|
|
334
438
|
this.draft = undefined;
|
|
439
|
+
if (this.draft?.target && !this.draft.target.element.isConnected && !this.draftHasContent)
|
|
440
|
+
this.draft = undefined;
|
|
335
441
|
const docs = new Set(this.surfaces.map((surface) => surface.doc).filter((d) => Boolean(d) && d !== doc));
|
|
336
442
|
for (const frame of docs)
|
|
337
443
|
this.watchFrame(frame);
|
|
@@ -360,6 +466,10 @@ export class VisualComments {
|
|
|
360
466
|
continue;
|
|
361
467
|
const shadow = element.attachShadow({ mode: 'open' });
|
|
362
468
|
addStyles(shadow);
|
|
469
|
+
// In the host's toolbar the button takes the toolbar's colour and type, not the comments' ink.
|
|
470
|
+
const own = this.doc.createElement('style');
|
|
471
|
+
own.textContent = ':host{color:inherit;font-family:inherit;font-size:inherit}';
|
|
472
|
+
shadow.appendChild(own);
|
|
363
473
|
this.slots.set(element, mount(Account, { target: shadow, props: { vc: this, slot: true } }));
|
|
364
474
|
}
|
|
365
475
|
for (const [element, app] of this.slots) {
|
|
@@ -503,31 +613,46 @@ export class VisualComments {
|
|
|
503
613
|
this.updateAim();
|
|
504
614
|
return;
|
|
505
615
|
}
|
|
506
|
-
if (event.type !== 'keydown' || event.defaultPrevented)
|
|
507
|
-
return;
|
|
508
|
-
const target = event.composedPath()[0];
|
|
509
|
-
if (editable(target))
|
|
616
|
+
if (event.type !== 'keydown' || event.defaultPrevented || !this.ownsKey(event))
|
|
510
617
|
return;
|
|
511
|
-
if (event.key === 'Escape') {
|
|
512
|
-
if (this.host.shadowRoot?.querySelector(':popover-open') || this.doc.querySelector(':popover-open'))
|
|
513
|
-
return;
|
|
514
|
-
if (this.draft)
|
|
515
|
-
this.draft = undefined;
|
|
516
|
-
else if (this.openId !== undefined)
|
|
517
|
-
this.openThread(undefined);
|
|
518
|
-
else if (this.active)
|
|
519
|
-
this.toggle(false);
|
|
520
|
-
else
|
|
521
|
-
return;
|
|
522
|
-
event.preventDefault();
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
618
|
const shortcut = this.options.shortcut ?? 'c';
|
|
526
619
|
if (shortcut && event.key.toLowerCase() === shortcut && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
527
620
|
event.preventDefault();
|
|
528
621
|
this.toggle();
|
|
529
622
|
}
|
|
530
623
|
}
|
|
624
|
+
/** Escape drops the new comment, else closes the open thread, else leaves comment mode. */
|
|
625
|
+
onEscape(event) {
|
|
626
|
+
if (event.type !== 'keydown' || event.key !== 'Escape' || event.defaultPrevented || !this.ownsKey(event))
|
|
627
|
+
return;
|
|
628
|
+
if (this.host.shadowRoot?.querySelector(':popover-open') || this.doc.querySelector(':popover-open'))
|
|
629
|
+
return;
|
|
630
|
+
if (this.draft)
|
|
631
|
+
this.draft = undefined;
|
|
632
|
+
else if (this.openId !== undefined)
|
|
633
|
+
this.openThread(undefined);
|
|
634
|
+
else if (this.active)
|
|
635
|
+
this.toggle(false);
|
|
636
|
+
else
|
|
637
|
+
return;
|
|
638
|
+
stop(event);
|
|
639
|
+
}
|
|
640
|
+
/** Whether a key is the comments' to act on: not typed into a field, nor into the dialog to approve an agent. */
|
|
641
|
+
ownsKey(event) {
|
|
642
|
+
return !this.host.shadowRoot?.querySelector('dialog:modal') && !editable(event.composedPath()[0]);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/** A `name=value` part of the address's fragment, whose parts are joined by `&`. */
|
|
646
|
+
function hashPart(name) {
|
|
647
|
+
const part = location.hash.slice(1).split('&').find((p) => p.startsWith(`${name}=`));
|
|
648
|
+
return part?.slice(name.length + 1);
|
|
649
|
+
}
|
|
650
|
+
function dropFromHash(name) {
|
|
651
|
+
const parts = location.hash.slice(1).split('&');
|
|
652
|
+
const rest = parts.filter((part) => part && !part.startsWith(`${name}=`)).join('&');
|
|
653
|
+
if (rest === parts.join('&'))
|
|
654
|
+
return;
|
|
655
|
+
history.replaceState(history.state, '', `${location.pathname}${location.search}${rest ? `#${rest}` : ''}`);
|
|
531
656
|
}
|
|
532
657
|
function stop(event) {
|
|
533
658
|
event.preventDefault();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export interface VisualCommentsPluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Whether the comments are in the build. Default: under the dev server, and
|
|
5
|
+
* in a build only with `VISUAL_COMMENTS_BUILD=true` in its environment.
|
|
6
|
+
*/
|
|
7
|
+
include?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Puts the comments in development and in the builds that ask for them, and
|
|
11
|
+
* leaves them out of every other build:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* // vite.config.ts
|
|
15
|
+
* import { visualComments } from 'visual-comments/vite';
|
|
16
|
+
*
|
|
17
|
+
* export default defineConfig({ plugins: [visualComments(), sveltekit()] });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function visualComments(options?: VisualCommentsPluginOptions): Plugin;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// A Vite plugin that leaves the comments out of a build altogether: for builds
|
|
2
|
+
// that must not carry a review tool, such as production's, whatever the
|
|
3
|
+
// variables say when they run. The site's code stays as it is: `<Comments>`
|
|
4
|
+
// of `visual-comments/sveltekit` renders only what it wraps, `<Toolbar>` only
|
|
5
|
+
// the app's own tools (or nothing), and `commentsFromEnv` of
|
|
6
|
+
// `visual-comments/server` is always off, so the comments' client, the proxy
|
|
7
|
+
// and their sign-in are not in the build. The variables are still declared,
|
|
8
|
+
// so the same `src/env.ts` serves both.
|
|
9
|
+
/** This package's `dist/`, as Vite writes module ids. */
|
|
10
|
+
const dist = (() => {
|
|
11
|
+
const path = decodeURIComponent(new URL('../', import.meta.url).pathname);
|
|
12
|
+
return /^\/[A-Za-z]:\//.test(path) ? path.slice(1) : path;
|
|
13
|
+
})();
|
|
14
|
+
const COMMENTS = `<script>
|
|
15
|
+
// Left out of this build (visual-comments/vite): renders what it wraps.
|
|
16
|
+
let { comments = $bindable(), children } = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
`;
|
|
21
|
+
const COMMENT_TOOLS = `<script>
|
|
22
|
+
// Left out of this build (visual-comments/vite).
|
|
23
|
+
let { divider } = $props();
|
|
24
|
+
</script>
|
|
25
|
+
`;
|
|
26
|
+
const ENV = `// Left out of this build (visual-comments/vite): comments are off, whatever the variables say.
|
|
27
|
+
export { commentsVariables } from './variables.js';
|
|
28
|
+
|
|
29
|
+
let warned = false;
|
|
30
|
+
|
|
31
|
+
export function commentsFromEnv(env) {
|
|
32
|
+
if (!warned && env?.VISUAL_COMMENTS_ENABLED === 'true') {
|
|
33
|
+
warned = true;
|
|
34
|
+
console.warn('[visual-comments] VISUAL_COMMENTS_ENABLED is true, but this build leaves the comments out (visual-comments/vite).');
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
enabled: false,
|
|
38
|
+
client: null,
|
|
39
|
+
load: async () => null,
|
|
40
|
+
handle: async () => undefined,
|
|
41
|
+
sveltekit: () => ({ event, resolve }) => Promise.resolve(resolve(event))
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
const STUBS = {
|
|
46
|
+
[`${dist}sveltekit/Comments.svelte`]: COMMENTS,
|
|
47
|
+
[`${dist}sveltekit/CommentTools.svelte`]: COMMENT_TOOLS,
|
|
48
|
+
[`${dist}server/env.js`]: ENV
|
|
49
|
+
};
|
|
50
|
+
function environment(name) {
|
|
51
|
+
return globalThis.process?.env?.[name];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Puts the comments in development and in the builds that ask for them, and
|
|
55
|
+
* leaves them out of every other build:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* // vite.config.ts
|
|
59
|
+
* import { visualComments } from 'visual-comments/vite';
|
|
60
|
+
*
|
|
61
|
+
* export default defineConfig({ plugins: [visualComments(), sveltekit()] });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function visualComments(options = {}) {
|
|
65
|
+
let include = true;
|
|
66
|
+
return {
|
|
67
|
+
name: 'visual-comments',
|
|
68
|
+
enforce: 'pre',
|
|
69
|
+
config(_, { command }) {
|
|
70
|
+
include = options.include ?? (command === 'serve' || environment('VISUAL_COMMENTS_BUILD') === 'true');
|
|
71
|
+
// Bundled into the server too, where it would otherwise be loaded from node_modules as it is.
|
|
72
|
+
return include ? undefined : { ssr: { noExternal: ['visual-comments'] } };
|
|
73
|
+
},
|
|
74
|
+
load(id) {
|
|
75
|
+
return include ? undefined : STUBS[id];
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|