@dfosco/storyboard-core 3.6.0 → 3.7.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/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +12274 -11387
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +1 -1
- package/src/CanvasZoomControl.svelte +8 -8
- package/src/CommentsMenuButton.svelte +7 -21
- package/src/CoreUIBar.svelte +19 -3
- package/src/CreateMenuButton.svelte +8 -12
- package/src/InspectorPanel.svelte +12 -15
- package/src/SidePanel.svelte +14 -14
- package/src/assets/fonts/IoskeleyMono-Bold.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-Italic.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-Medium.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-Regular.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-SemiBold.woff2 +0 -0
- package/src/comments/ui/AuthModal.svelte +45 -12
- package/src/comments/ui/authModal.js +6 -1
- package/src/comments/ui/comment-layout.css +15 -15
- package/src/comments/ui/commentWindow.js +6 -1
- package/src/comments/ui/comments.css +57 -57
- package/src/comments/ui/commentsDrawer.js +2 -0
- package/src/comments/ui/composer.js +7 -2
- package/src/comments/ui/mount.js +252 -33
- package/src/comments/ui/mount.test.js +138 -0
- package/src/core-ui-colors.css +28 -28
- package/src/inspector/mouseMode.js +2 -2
- package/src/lib/components/ui/button/button.svelte +9 -9
- package/src/lib/components/ui/panel/panel-content.svelte +2 -2
- package/src/lib/components/ui/select/select-trigger.svelte +1 -1
- package/src/lib/components/ui/toggle/toggle.svelte +1 -1
- package/src/lib/components/ui/toggle-group/toggle-group.svelte +2 -2
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +13 -13
- package/src/modes.css +21 -21
- package/src/mountStoryboardCore.js +4 -4
- package/src/sidepanel.css +11 -11
- package/src/styles/tailwind.css +89 -1
- package/src/svelte-plugin-ui/components/ModeSwitch.svelte +3 -3
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +31 -11
- package/src/svelte-plugin-ui/styles/base.css +41 -41
- package/src/workshop/features/createFlow/CreateFlowForm.svelte +187 -25
- package/src/workshop/features/createFlow/server.js +437 -40
- package/src/workshop/features/createPage/CreatePageForm.svelte +249 -0
- package/src/workshop/features/createPage/index.js +11 -0
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +77 -24
- package/src/workshop/features/createPrototype/server.js +14 -16
- package/src/workshop/features/registry-server.js +1 -0
- package/src/workshop/features/registry.js +2 -0
- package/src/workshop/features/templateIndex.js +155 -0
- package/toolbar.config.json +2 -1
|
@@ -387,12 +387,9 @@
|
|
|
387
387
|
requestAnimationFrame(() => {
|
|
388
388
|
const el = sourceContainer.querySelector('.highlighted-line')
|
|
389
389
|
if (el) {
|
|
390
|
-
//
|
|
391
|
-
const targetTop = el.offsetTop -
|
|
392
|
-
sourceContainer.
|
|
393
|
-
requestAnimationFrame(() => {
|
|
394
|
-
el.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
395
|
-
})
|
|
390
|
+
// Align the highlighted line to the top of the code viewport.
|
|
391
|
+
const targetTop = Math.max(el.offsetTop - 24, 0)
|
|
392
|
+
sourceContainer.scrollTo({ top: targetTop, behavior: 'smooth' })
|
|
396
393
|
} else {
|
|
397
394
|
sourceContainer.scrollTop = 0
|
|
398
395
|
}
|
|
@@ -531,7 +528,7 @@
|
|
|
531
528
|
</p>
|
|
532
529
|
<button
|
|
533
530
|
class="mt-2 px-4 py-1.5 text-xs font-medium rounded-md border-none cursor-pointer transition-colors"
|
|
534
|
-
style:background="var(--color-purple, #7655a4)"
|
|
531
|
+
style:background="var(--sb--color-purple, #7655a4)"
|
|
535
532
|
style:color="#fff"
|
|
536
533
|
onclick={startInspecting}
|
|
537
534
|
>
|
|
@@ -552,7 +549,7 @@
|
|
|
552
549
|
class="mt-2 px-4 py-1.5 text-xs font-medium rounded-md border cursor-pointer transition-colors"
|
|
553
550
|
style:background="transparent"
|
|
554
551
|
style:color="var(--fgColor-muted)"
|
|
555
|
-
style:border-color="var(--borderColor-default, var(--color-border, #d1d9e0))"
|
|
552
|
+
style:border-color="var(--borderColor-default, var(--sb--color-border, #d1d9e0))"
|
|
556
553
|
onclick={stopInspecting}
|
|
557
554
|
>
|
|
558
555
|
Cancel
|
|
@@ -564,7 +561,7 @@
|
|
|
564
561
|
<div class="flex flex-col flex-1 min-h-0 p-3 pt-0 gap-3">
|
|
565
562
|
<!-- Component name -->
|
|
566
563
|
<div>
|
|
567
|
-
<h3 class="text-base font-bold m-0 inspector-mono" style:color="var(--color-purple, #7655a4)">
|
|
564
|
+
<h3 class="text-base font-bold m-0 inspector-mono" style:color="var(--sb--color-purple, #7655a4)">
|
|
568
565
|
{componentInfo.name}
|
|
569
566
|
</h3>
|
|
570
567
|
</div>
|
|
@@ -621,7 +618,7 @@
|
|
|
621
618
|
<!-- Re-select button -->
|
|
622
619
|
<button
|
|
623
620
|
class="flex items-center justify-center gap-1.5 w-full px-3 py-1.5 text-xs font-medium rounded-md border-none cursor-pointer transition-colors shrink-0"
|
|
624
|
-
style:background="var(--color-purple, #7655a4)"
|
|
621
|
+
style:background="var(--sb--color-purple, #7655a4)"
|
|
625
622
|
style:color="#fff"
|
|
626
623
|
onclick={startInspecting}
|
|
627
624
|
>
|
|
@@ -642,7 +639,7 @@
|
|
|
642
639
|
width: 8px;
|
|
643
640
|
height: 8px;
|
|
644
641
|
border-radius: 50%;
|
|
645
|
-
background: var(--color-purple, #7655a4);
|
|
642
|
+
background: var(--sb--color-purple, #7655a4);
|
|
646
643
|
animation: inspector-pulse 1.5s ease-in-out infinite;
|
|
647
644
|
flex-shrink: 0;
|
|
648
645
|
}
|
|
@@ -681,8 +678,8 @@
|
|
|
681
678
|
}
|
|
682
679
|
|
|
683
680
|
.source-pre :global(.highlighted-line) {
|
|
684
|
-
background: color-mix(in srgb, var(--color-purple, #7655a4) 20%, transparent);
|
|
685
|
-
border-left: 2px solid var(--color-purple, #7655a4);
|
|
681
|
+
background: color-mix(in srgb, var(--sb--color-purple, #7655a4) 20%, transparent);
|
|
682
|
+
border-left: 2px solid var(--sb--color-purple, #7655a4);
|
|
686
683
|
padding-left: 10px;
|
|
687
684
|
}
|
|
688
685
|
|
|
@@ -734,8 +731,8 @@
|
|
|
734
731
|
}
|
|
735
732
|
|
|
736
733
|
.code-wrapper :global(.highlighted-line) {
|
|
737
|
-
background: color-mix(in srgb, var(--color-purple, #7655a4) 20%, transparent);
|
|
738
|
-
border-left: 2px solid var(--color-purple, #7655a4);
|
|
734
|
+
background: color-mix(in srgb, var(--sb--color-purple, #7655a4) 20%, transparent);
|
|
735
|
+
border-left: 2px solid var(--sb--color-purple, #7655a4);
|
|
739
736
|
padding-left: 10px;
|
|
740
737
|
}
|
|
741
738
|
|
package/src/SidePanel.svelte
CHANGED
|
@@ -52,14 +52,14 @@
|
|
|
52
52
|
// Sync panel width to CSS custom property
|
|
53
53
|
$effect(() => {
|
|
54
54
|
if (typeof document !== 'undefined') {
|
|
55
|
-
document.documentElement.style.setProperty('--sidepanel-width', `${panelWidth}px`)
|
|
55
|
+
document.documentElement.style.setProperty('--sb--sidepanel-width', `${panelWidth}px`)
|
|
56
56
|
}
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
// Sync panel height to CSS custom property
|
|
60
60
|
$effect(() => {
|
|
61
61
|
if (typeof document !== 'undefined') {
|
|
62
|
-
document.documentElement.style.setProperty('--sidepanel-height', `${panelHeight}px`)
|
|
62
|
+
document.documentElement.style.setProperty('--sb--sidepanel-height', `${panelHeight}px`)
|
|
63
63
|
}
|
|
64
64
|
})
|
|
65
65
|
|
|
@@ -272,15 +272,15 @@
|
|
|
272
272
|
top: 0;
|
|
273
273
|
right: 0;
|
|
274
274
|
bottom: 0;
|
|
275
|
-
width: var(--sidepanel-width, 420px);
|
|
275
|
+
width: var(--sb--sidepanel-width, 420px);
|
|
276
276
|
z-index: 9998;
|
|
277
277
|
display: flex;
|
|
278
278
|
flex-direction: column;
|
|
279
|
-
background-color: var(--bgColor-default, var(--color-background, #ffffff));
|
|
280
|
-
border-left: 1px solid var(--borderColor-default, var(--color-border, #d0d7de));
|
|
279
|
+
background-color: var(--bgColor-default, var(--sb--color-background, #ffffff));
|
|
280
|
+
border-left: 1px solid var(--borderColor-default, var(--sb--color-border, #d0d7de));
|
|
281
281
|
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
|
|
282
282
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
283
|
-
color: var(--fgColor-default, var(--color-foreground, #1f2328));
|
|
283
|
+
color: var(--fgColor-default, var(--sb--color-foreground, #1f2328));
|
|
284
284
|
animation: sb-sidepanel-slide-in 0.25s ease;
|
|
285
285
|
}
|
|
286
286
|
|
|
@@ -291,9 +291,9 @@
|
|
|
291
291
|
bottom: 0;
|
|
292
292
|
left: 0;
|
|
293
293
|
width: 100% !important;
|
|
294
|
-
height: var(--sidepanel-height, 300px);
|
|
294
|
+
height: var(--sb--sidepanel-height, 300px);
|
|
295
295
|
border-left: none;
|
|
296
|
-
border-top: 1px solid var(--borderColor-default, var(--color-border, #d0d7de));
|
|
296
|
+
border-top: 1px solid var(--borderColor-default, var(--sb--color-border, #d0d7de));
|
|
297
297
|
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
|
|
298
298
|
animation: sb-sidepanel-slide-up 0.25s ease;
|
|
299
299
|
}
|
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
left: 0;
|
|
321
321
|
right: 0;
|
|
322
322
|
height: 3px;
|
|
323
|
-
/* background: var(--mode-color, var(--borderColor-default, var(--color-border, #d0d7de))); */
|
|
323
|
+
/* background: var(--sb--mode-color, var(--borderColor-default, var(--sb--color-border, #d0d7de))); */
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
/* Drag handle — side mode (left edge, vertical) */
|
|
@@ -378,7 +378,7 @@
|
|
|
378
378
|
font-weight: 600;
|
|
379
379
|
text-transform: uppercase;
|
|
380
380
|
letter-spacing: 0.05em;
|
|
381
|
-
color: var(--fgColor-muted, var(--color-muted-foreground, #656d76));
|
|
381
|
+
color: var(--fgColor-muted, var(--sb--color-muted-foreground, #656d76));
|
|
382
382
|
padding-left: 4px;
|
|
383
383
|
}
|
|
384
384
|
|
|
@@ -392,7 +392,7 @@
|
|
|
392
392
|
appearance: none;
|
|
393
393
|
border: none;
|
|
394
394
|
background: transparent;
|
|
395
|
-
color: var(--fgColor-muted, var(--color-muted-foreground, #656d76));
|
|
395
|
+
color: var(--fgColor-muted, var(--sb--color-muted-foreground, #656d76));
|
|
396
396
|
cursor: pointer;
|
|
397
397
|
padding: 6px;
|
|
398
398
|
border-radius: 6px;
|
|
@@ -404,7 +404,7 @@
|
|
|
404
404
|
|
|
405
405
|
.sb-sidepanel-action-btn:hover {
|
|
406
406
|
background: var(--bgColor-neutral-muted, rgba(110, 118, 129, 0.1));
|
|
407
|
-
color: var(--fgColor-default, var(--color-foreground, #1f2328));
|
|
407
|
+
color: var(--fgColor-default, var(--sb--color-foreground, #1f2328));
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
/* Body */
|
|
@@ -425,8 +425,8 @@
|
|
|
425
425
|
.sb-sidepanel-spinner {
|
|
426
426
|
width: 20px;
|
|
427
427
|
height: 20px;
|
|
428
|
-
border: 2px solid var(--borderColor-default, var(--color-border, #d0d7de));
|
|
429
|
-
border-top-color: var(--fgColor-muted, var(--color-muted-foreground, #656d76));
|
|
428
|
+
border: 2px solid var(--borderColor-default, var(--sb--color-border, #d0d7de));
|
|
429
|
+
border-top-color: var(--fgColor-muted, var(--sb--color-muted-foreground, #656d76));
|
|
430
430
|
border-radius: 50%;
|
|
431
431
|
animation: sb-spin 0.6s linear infinite;
|
|
432
432
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<script lang="ts">
|
|
7
7
|
import { onMount } from 'svelte'
|
|
8
8
|
import { setToken, validateToken } from '../auth.js'
|
|
9
|
+
import { getCommentsConfig } from '../config.js'
|
|
9
10
|
import { Button } from '../../lib/components/ui/button/index.js'
|
|
10
11
|
import { Input } from '../../lib/components/ui/input/index.js'
|
|
11
12
|
import { Label } from '../../lib/components/ui/label/index.js'
|
|
@@ -15,17 +16,38 @@
|
|
|
15
16
|
interface Props {
|
|
16
17
|
onDone?: (user: { login: string; avatarUrl: string }) => void
|
|
17
18
|
onClose?: () => void
|
|
19
|
+
initialError?: string | null
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
let { onDone, onClose }: Props = $props()
|
|
22
|
+
let { onDone, onClose, initialError = null }: Props = $props()
|
|
21
23
|
|
|
22
24
|
let token = $state('')
|
|
23
25
|
let submitting = $state(false)
|
|
24
26
|
let error: string | null = $state(null)
|
|
25
27
|
let user: { login: string; avatarUrl: string } | null = $state(null)
|
|
26
|
-
let inputEl: HTMLInputElement |
|
|
28
|
+
let inputEl: HTMLInputElement | null = $state(null)
|
|
29
|
+
const commentsConfig = getCommentsConfig()
|
|
30
|
+
const repoOwner = commentsConfig?.repo?.owner || 'github'
|
|
31
|
+
const repoName = commentsConfig?.repo?.name || 'storyboard'
|
|
32
|
+
const repoSlug = `${repoOwner}/${repoName}`
|
|
33
|
+
const tokenTemplateName = 'Storyboard Comments'
|
|
34
|
+
const tokenTemplateDescription =
|
|
35
|
+
`Token for enabling comments on ${repoSlug} prototype. Configure as:\n\n` +
|
|
36
|
+
`Owner: ${repoOwner}\n` +
|
|
37
|
+
'Expiration: 366 days (recommended)\n' +
|
|
38
|
+
`Repository access: Only select repositories > ${repoSlug}\n` +
|
|
39
|
+
'Permissions: Repositories > Discussions > Access: Read and Write'
|
|
40
|
+
const tokenCreateUrl =
|
|
41
|
+
`https://github.com/settings/personal-access-tokens/new?` +
|
|
42
|
+
new URLSearchParams({
|
|
43
|
+
name: tokenTemplateName,
|
|
44
|
+
description: tokenTemplateDescription,
|
|
45
|
+
}).toString()
|
|
27
46
|
|
|
28
|
-
onMount(() => {
|
|
47
|
+
onMount(() => {
|
|
48
|
+
error = initialError
|
|
49
|
+
inputEl?.focus()
|
|
50
|
+
})
|
|
29
51
|
|
|
30
52
|
async function submit() {
|
|
31
53
|
const val = token.trim()
|
|
@@ -43,30 +65,41 @@
|
|
|
43
65
|
}
|
|
44
66
|
</script>
|
|
45
67
|
|
|
46
|
-
<div class="bg-popover text-popover-foreground border border-border rounded-lg shadow-lg overflow-hidden max-w-[
|
|
68
|
+
<div class="bg-popover text-popover-foreground border border-border rounded-lg shadow-lg overflow-hidden max-w-[600px] w-full font-sans">
|
|
47
69
|
<div class="flex items-center justify-between px-4 py-3 border-b border-border">
|
|
48
|
-
<h2 class="text-
|
|
70
|
+
<h2 class="text-medium font-semibold">Sign in for comments</h2>
|
|
49
71
|
<Button variant="ghost" size="icon" onclick={onClose} aria-label="Close" class="h-7 w-7 text-muted-foreground">×</Button>
|
|
50
72
|
</div>
|
|
51
73
|
<div class="p-4 space-y-3">
|
|
74
|
+
{#if error}<Alert.Root variant="destructive"><Alert.Description>{error}</Alert.Description></Alert.Root>{/if}
|
|
75
|
+
<p class="text-sm text-muted-foreground leading-relaxed">
|
|
76
|
+
Leave comments for other users to see and respond, and react to! Storyboard comments use Discussions as a back-end and require a GitHub PAT to be enabled.
|
|
77
|
+
</p>
|
|
52
78
|
<p class="text-sm text-muted-foreground leading-relaxed">
|
|
53
|
-
Create a <a class="text-primary underline" href=
|
|
79
|
+
Create a <a class="text-primary underline" href={tokenCreateUrl} target="_blank" rel="noopener">GitHub Fine-Grained Personal Access Token</a> with the settings below to get started:
|
|
54
80
|
</p>
|
|
81
|
+
<div class="px-3 py-2 bg-muted border border-border rounded text-xs text-muted-foreground leading-relaxed">
|
|
82
|
+
<div class="mb-1"><strong class="text-foreground">Fine-grained Personal Access Token</strong></div>
|
|
83
|
+
<div>Owner: <code class="px-1 bg-background rounded font-mono text-foreground">{repoOwner}</code></div>
|
|
84
|
+
<div>Expiration: <code class="px-1 bg-background rounded font-mono text-foreground">366 days</code> (recommended)</div>
|
|
85
|
+
<div>Repository access: <code class="px-1 bg-background rounded font-mono text-foreground">Only select repositories > {repoSlug}</code></div>
|
|
86
|
+
<div>Permissions: <code class="px-1 bg-background rounded font-mono text-foreground">Repositories > Discussions > Access: Read and Write</code></div>
|
|
87
|
+
</div>
|
|
55
88
|
<div class="space-y-1">
|
|
56
89
|
<Label for="sb-auth-token-input">Personal Access Token</Label>
|
|
57
|
-
<Input id="sb-auth-token-input" type="password" placeholder="github_pat_\u2026 or ghp_\u2026" autocomplete="off" spellcheck="false" class="font-mono" bind:value={token} bind:
|
|
58
|
-
</div>
|
|
59
|
-
<div class="px-3 py-2 bg-muted border border-border rounded text-xs text-muted-foreground leading-relaxed">
|
|
60
|
-
<div class="mb-1"><strong class="text-foreground">Fine-grained</strong> (recommended): <code class="px-1 bg-background rounded font-mono text-foreground">Discussions: Read and write</code></div>
|
|
61
|
-
<div><strong class="text-foreground">Classic</strong>: <code class="px-1 bg-background rounded font-mono text-foreground">repo</code></div>
|
|
90
|
+
<Input id="sb-auth-token-input" type="password" placeholder="github_pat_\u2026 or ghp_\u2026" autocomplete="off" spellcheck="false" class="font-mono" bind:value={token} bind:ref={inputEl} onkeydown={handleKeydown} />
|
|
62
91
|
</div>
|
|
63
|
-
{#if error}<Alert.Root variant="destructive"><Alert.Description>{error}</Alert.Description></Alert.Root>{/if}
|
|
64
92
|
{#if user}
|
|
65
93
|
<div class="flex items-center py-1 gap-3">
|
|
66
94
|
<Avatar.Root class="h-10 w-10"><Avatar.Image src={user.avatarUrl} alt={user.login} /><Avatar.Fallback>{user.login[0]?.toUpperCase()}</Avatar.Fallback></Avatar.Root>
|
|
67
95
|
<div class="text-sm"><span class="text-foreground">{user.login}</span><span class="block text-xs text-success mt-0.5">✓ Signed in</span></div>
|
|
68
96
|
</div>
|
|
69
97
|
{/if}
|
|
98
|
+
<Alert.Root variant="warning" class="bg-amber-100 border-amber-300">
|
|
99
|
+
<Alert.Description class="text-amber-700">
|
|
100
|
+
⚠️ Comments are an experimental feature and may be unstable.
|
|
101
|
+
</Alert.Description>
|
|
102
|
+
</Alert.Root>
|
|
70
103
|
</div>
|
|
71
104
|
<div class="flex items-center justify-end px-4 py-3 border-t border-border gap-2">
|
|
72
105
|
<Button variant="outline" size="sm" onclick={onClose}>Cancel</Button>
|
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
import { mount, unmount } from 'svelte'
|
|
8
8
|
import AuthModal from './AuthModal.svelte'
|
|
9
9
|
import { getCachedUser, clearToken } from '../auth.js'
|
|
10
|
+
import './comment-layout.css'
|
|
10
11
|
|
|
11
12
|
const MODAL_ID = 'sb-auth-modal'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Open the auth modal. Returns a promise that resolves with the user info
|
|
15
16
|
* on successful sign-in, or null if cancelled.
|
|
17
|
+
* @param {{ initialError?: string|null }} [options]
|
|
16
18
|
* @returns {Promise<{ login: string, avatarUrl: string }|null>}
|
|
17
19
|
*/
|
|
18
|
-
export function openAuthModal() {
|
|
20
|
+
export function openAuthModal(options = {}) {
|
|
21
|
+
const { initialError = null } = options
|
|
22
|
+
|
|
19
23
|
return new Promise((resolve) => {
|
|
20
24
|
const existing = document.getElementById(MODAL_ID)
|
|
21
25
|
if (existing) existing.remove()
|
|
@@ -55,6 +59,7 @@ export function openAuthModal() {
|
|
|
55
59
|
instance = mount(AuthModal, {
|
|
56
60
|
target: backdrop,
|
|
57
61
|
props: {
|
|
62
|
+
initialError,
|
|
58
63
|
onDone: (user) => {
|
|
59
64
|
cleanup()
|
|
60
65
|
resolve(user)
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
/* Comment pin */
|
|
29
29
|
.sb-comment-pin {
|
|
30
30
|
position: absolute;
|
|
31
|
-
border: 3px solid hsl(var(--pin-hue, 140), 50%, 38%);
|
|
31
|
+
border: 3px solid hsl(var(--sb--pin-hue, 140), 50%, 38%);
|
|
32
32
|
border-radius: 50%;
|
|
33
33
|
z-index: 100000;
|
|
34
34
|
width: 32px; height: 32px;
|
|
@@ -38,21 +38,21 @@
|
|
|
38
38
|
pointer-events: auto;
|
|
39
39
|
overflow: hidden;
|
|
40
40
|
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
|
41
|
-
background: var(--color-popover, #fff);
|
|
41
|
+
background: var(--sb--color-popover, #fff);
|
|
42
42
|
}
|
|
43
43
|
.sb-comment-pin:active { cursor: grabbing; }
|
|
44
44
|
.sb-pin-img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 50%; }
|
|
45
45
|
.sb-comment-pin[data-resolved="true"] {
|
|
46
|
-
border-color: var(--color-muted-foreground, #848d97);
|
|
46
|
+
border-color: var(--sb--color-muted-foreground, #848d97);
|
|
47
47
|
opacity: 0.5;
|
|
48
48
|
}
|
|
49
49
|
.sb-comment-pin-pending {
|
|
50
|
-
border-color: var(--color-muted-foreground, #848d97) !important;
|
|
50
|
+
border-color: var(--sb--color-muted-foreground, #848d97) !important;
|
|
51
51
|
opacity: 0.6;
|
|
52
52
|
animation: sb-pin-pulse 1.2s ease-in-out infinite;
|
|
53
53
|
}
|
|
54
54
|
.sb-comment-pin-failed {
|
|
55
|
-
border-color: var(--color-destructive, #d1242f) !important;
|
|
55
|
+
border-color: var(--sb--color-destructive, #d1242f) !important;
|
|
56
56
|
cursor: pointer;
|
|
57
57
|
animation: sb-pin-shake 0.4s ease-in-out;
|
|
58
58
|
}
|
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
position: absolute;
|
|
72
72
|
width: 280px;
|
|
73
73
|
z-index: 100001;
|
|
74
|
-
background: var(--color-popover, #fff);
|
|
75
|
-
color: var(--color-popover-foreground, #1f2328);
|
|
76
|
-
border: 1px solid var(--color-border, #d1d5db);
|
|
74
|
+
background: var(--sb--color-popover, #fff);
|
|
75
|
+
color: var(--sb--color-popover-foreground, #1f2328);
|
|
76
|
+
border: 1px solid var(--sb--color-border, #d1d5db);
|
|
77
77
|
border-radius: 0.5rem;
|
|
78
78
|
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
|
79
79
|
overflow: hidden;
|
|
@@ -86,9 +86,9 @@
|
|
|
86
86
|
max-height: 480px;
|
|
87
87
|
z-index: 100001;
|
|
88
88
|
scrollbar-width: none;
|
|
89
|
-
background: var(--color-popover, #fff);
|
|
90
|
-
color: var(--color-popover-foreground, #1f2328);
|
|
91
|
-
border: 1px solid var(--color-border, #d1d5db);
|
|
89
|
+
background: var(--sb--color-popover, #fff);
|
|
90
|
+
color: var(--sb--color-popover-foreground, #1f2328);
|
|
91
|
+
border: 1px solid var(--sb--color-border, #d1d5db);
|
|
92
92
|
border-radius: 0.5rem;
|
|
93
93
|
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
|
94
94
|
overflow: hidden;
|
|
@@ -110,8 +110,8 @@
|
|
|
110
110
|
z-index: 99998;
|
|
111
111
|
width: 420px;
|
|
112
112
|
max-width: 90vw;
|
|
113
|
-
background: var(--color-background, #fff);
|
|
114
|
-
color: var(--color-foreground, #1f2328);
|
|
113
|
+
background: var(--sb--color-background, #fff);
|
|
114
|
+
color: var(--sb--color-foreground, #1f2328);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/* Drawer animation */
|
|
@@ -126,8 +126,8 @@
|
|
|
126
126
|
position: fixed;
|
|
127
127
|
bottom: 12px; left: 50%; transform: translateX(-50%);
|
|
128
128
|
z-index: 99999;
|
|
129
|
-
background: var(--color-popover, #fff);
|
|
130
|
-
color: var(--color-popover-foreground, #1f2328);
|
|
129
|
+
background: var(--sb--color-popover, #fff);
|
|
130
|
+
color: var(--sb--color-popover-foreground, #1f2328);
|
|
131
131
|
padding: 6px 16px; border-radius: 8px; font-size: 13px;
|
|
132
132
|
line-height: 1.4; backdrop-filter: blur(12px);
|
|
133
133
|
pointer-events: none;
|
|
@@ -10,6 +10,7 @@ import { mount, unmount } from 'svelte'
|
|
|
10
10
|
import CommentWindowComponent from './CommentWindow.svelte'
|
|
11
11
|
import { getCachedUser } from '../auth.js'
|
|
12
12
|
import { saveDraft, replyDraftKey } from '../commentDrafts.js'
|
|
13
|
+
import './comment-layout.css'
|
|
13
14
|
|
|
14
15
|
// Track the currently open window so only one is open at a time
|
|
15
16
|
let activeWindow = null
|
|
@@ -20,6 +21,7 @@ let activeWindow = null
|
|
|
20
21
|
* @param {object} comment - The parsed comment object (with meta, text, replies, reactionGroups, author, etc.)
|
|
21
22
|
* @param {object} discussion - The discussion object (id needed for replies)
|
|
22
23
|
* @param {object} [callbacks] - Optional callbacks
|
|
24
|
+
* @param {(xPct: number, yPct: number) => { left: string, top: string }} [callbacks.getAnchorPosition] - Resolve coordinates to overlay position
|
|
23
25
|
* @param {() => void} [callbacks.onClose] - Called when window is closed
|
|
24
26
|
* @param {() => void} [callbacks.onMove] - Called after comment is moved (for re-rendering pins)
|
|
25
27
|
* @returns {{ el: HTMLElement, destroy: () => void }}
|
|
@@ -32,8 +34,11 @@ export function showCommentWindow(container, comment, discussion, callbacks = {}
|
|
|
32
34
|
|
|
33
35
|
const user = getCachedUser()
|
|
34
36
|
const win = document.createElement('div')
|
|
37
|
+
const anchor = callbacks.getAnchorPosition
|
|
38
|
+
? callbacks.getAnchorPosition(comment.meta?.x ?? 0, comment.meta?.y ?? 0)
|
|
39
|
+
: { left: `${comment.meta?.x ?? 0}%`, top: `${comment.meta?.y ?? 0}%` }
|
|
35
40
|
win.className = 'sb-comment-window absolute'
|
|
36
|
-
win.style.cssText = `z-index:100001;width:360px;max-height:480px;left:${
|
|
41
|
+
win.style.cssText = `z-index:100001;width:360px;max-height:480px;left:${anchor.left};top:${anchor.top};transform:translate(12px,-50%)`
|
|
37
42
|
|
|
38
43
|
// Stop click from propagating to overlay
|
|
39
44
|
win.addEventListener('click', (e) => e.stopPropagation())
|
|
@@ -7,77 +7,77 @@
|
|
|
7
7
|
|
|
8
8
|
/* --- Light theme (default) --- */
|
|
9
9
|
:root {
|
|
10
|
-
--sb
|
|
11
|
-
--sb
|
|
12
|
-
--sb
|
|
13
|
-
--sb
|
|
14
|
-
--sb
|
|
15
|
-
--sb
|
|
16
|
-
--sb
|
|
17
|
-
--sb
|
|
18
|
-
--sb
|
|
19
|
-
--sb
|
|
20
|
-
--sb
|
|
10
|
+
--sb--bg: #ffffff;
|
|
11
|
+
--sb--bg-inset: #f6f8fa;
|
|
12
|
+
--sb--bg-muted: #f3f4f6;
|
|
13
|
+
--sb--border: #d1d5db;
|
|
14
|
+
--sb--border-muted: #e5e7eb;
|
|
15
|
+
--sb--fg: #1f2328;
|
|
16
|
+
--sb--fg-muted: #656d76;
|
|
17
|
+
--sb--fg-accent: #0969da;
|
|
18
|
+
--sb--fg-success: #1a7f37;
|
|
19
|
+
--sb--fg-danger: #d1242f;
|
|
20
|
+
--sb--btn-success: #1a7f37;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/* --- Dark theme (any dark-* variant) --- */
|
|
24
24
|
[data-sb-theme^="dark"] {
|
|
25
|
-
--sb
|
|
26
|
-
--sb
|
|
27
|
-
--sb
|
|
28
|
-
--sb
|
|
29
|
-
--sb
|
|
30
|
-
--sb
|
|
31
|
-
--sb
|
|
32
|
-
--sb
|
|
33
|
-
--sb
|
|
34
|
-
--sb
|
|
35
|
-
--sb
|
|
25
|
+
--sb--bg: #161b22;
|
|
26
|
+
--sb--bg-inset: #0d1117;
|
|
27
|
+
--sb--bg-muted: #21262d;
|
|
28
|
+
--sb--border: #30363d;
|
|
29
|
+
--sb--border-muted: #21262d;
|
|
30
|
+
--sb--fg: #e6edf3;
|
|
31
|
+
--sb--fg-muted: #8b949e;
|
|
32
|
+
--sb--fg-accent: #58a6ff;
|
|
33
|
+
--sb--fg-success: #3fb950;
|
|
34
|
+
--sb--fg-danger: #f85149;
|
|
35
|
+
--sb--btn-success: #238636;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/* --- Semantic utility classes (supplement Tachyons) --- */
|
|
39
|
-
.sb-bg { background-color: var(--sb
|
|
40
|
-
.sb-bg-inset { background-color: var(--sb
|
|
41
|
-
.sb-bg-muted { background-color: var(--sb
|
|
42
|
-
.sb-b-default { border-color: var(--sb
|
|
43
|
-
.sb-b-muted { border-color: var(--sb
|
|
44
|
-
.sb-fg { color: var(--sb
|
|
45
|
-
.sb-fg-muted { color: var(--sb
|
|
46
|
-
.sb-fg-accent { color: var(--sb
|
|
47
|
-
.sb-fg-success { color: var(--sb
|
|
48
|
-
.sb-fg-danger { color: var(--sb
|
|
49
|
-
.sb-btn-success { background-color: var(--sb
|
|
39
|
+
.sb-bg { background-color: var(--sb--bg); }
|
|
40
|
+
.sb-bg-inset { background-color: var(--sb--bg-inset); }
|
|
41
|
+
.sb-bg-muted { background-color: var(--sb--bg-muted); }
|
|
42
|
+
.sb-b-default { border-color: var(--sb--border); }
|
|
43
|
+
.sb-b-muted { border-color: var(--sb--border-muted); }
|
|
44
|
+
.sb-fg { color: var(--sb--fg); }
|
|
45
|
+
.sb-fg-muted { color: var(--sb--fg-muted); }
|
|
46
|
+
.sb-fg-accent { color: var(--sb--fg-accent); }
|
|
47
|
+
.sb-fg-success { color: var(--sb--fg-success); }
|
|
48
|
+
.sb-fg-danger { color: var(--sb--fg-danger); }
|
|
49
|
+
.sb-btn-success { background-color: var(--sb--btn-success); color: #fff; }
|
|
50
50
|
.sb-btn-success:hover { filter: brightness(1.15); }
|
|
51
|
-
.sb-btn-cancel { background-color: var(--sb
|
|
52
|
-
.sb-btn-cancel:hover { background-color: var(--sb
|
|
51
|
+
.sb-btn-cancel { background-color: var(--sb--bg-muted); border: 1px solid var(--sb--border); color: var(--sb--fg); }
|
|
52
|
+
.sb-btn-cancel:hover { background-color: var(--sb--border-muted); }
|
|
53
53
|
|
|
54
54
|
.sb-input {
|
|
55
|
-
background: var(--sb
|
|
56
|
-
border: 1px solid var(--sb
|
|
57
|
-
color: var(--sb
|
|
55
|
+
background: var(--sb--bg-inset);
|
|
56
|
+
border: 1px solid var(--sb--border);
|
|
57
|
+
color: var(--sb--fg);
|
|
58
58
|
outline: none;
|
|
59
59
|
}
|
|
60
60
|
.sb-input:focus {
|
|
61
|
-
border-color: var(--sb
|
|
62
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--sb
|
|
61
|
+
border-color: var(--sb--fg-accent);
|
|
62
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--sb--fg-accent) 15%, transparent);
|
|
63
63
|
}
|
|
64
|
-
.sb-input::placeholder { color: var(--sb
|
|
64
|
+
.sb-input::placeholder { color: var(--sb--fg-muted); }
|
|
65
65
|
|
|
66
66
|
.sb-shadow { box-shadow: 0 8px 24px rgba(0,0,0,0.15), 0 2px 6px rgba(0,0,0,0.1); }
|
|
67
67
|
[data-sb-theme^="dark"] .sb-shadow { box-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.3); }
|
|
68
68
|
|
|
69
69
|
.sb-pill {
|
|
70
|
-
border: 1px solid var(--sb
|
|
70
|
+
border: 1px solid var(--sb--border);
|
|
71
71
|
background: none;
|
|
72
|
-
color: var(--sb
|
|
72
|
+
color: var(--sb--fg-muted);
|
|
73
73
|
cursor: pointer;
|
|
74
74
|
transition: border-color 100ms, background 100ms;
|
|
75
75
|
}
|
|
76
|
-
.sb-pill:hover { border-color: var(--sb
|
|
76
|
+
.sb-pill:hover { border-color: var(--sb--fg-accent); }
|
|
77
77
|
.sb-pill-active {
|
|
78
|
-
border-color: color-mix(in srgb, var(--sb
|
|
79
|
-
background: color-mix(in srgb, var(--sb
|
|
80
|
-
color: var(--sb
|
|
78
|
+
border-color: color-mix(in srgb, var(--sb--fg-accent) 40%, transparent);
|
|
79
|
+
background: color-mix(in srgb, var(--sb--fg-accent) 10%, transparent);
|
|
80
|
+
color: var(--sb--fg-accent);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/* --- Comment mode cursor --- */
|
|
@@ -162,15 +162,15 @@
|
|
|
162
162
|
.sb-reaction-btn { width: 28px; height: 28px; transition: background 100ms; }
|
|
163
163
|
.sb-reaction-btn-active {
|
|
164
164
|
width: 28px; height: 28px;
|
|
165
|
-
background: color-mix(in srgb, var(--sb
|
|
166
|
-
box-shadow: inset 0 0 0 1px var(--sb
|
|
165
|
+
background: color-mix(in srgb, var(--sb--fg-accent) 10%, transparent);
|
|
166
|
+
box-shadow: inset 0 0 0 1px var(--sb--fg-accent);
|
|
167
167
|
transition: background 100ms;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/* Banner */
|
|
171
171
|
.sb-banner {
|
|
172
172
|
bottom: 12px; left: 50%; transform: translateX(-50%);
|
|
173
|
-
z-index: 99999; background: var(--sb
|
|
173
|
+
z-index: 99999; background: var(--sb--bg); color: var(--sb--fg);
|
|
174
174
|
padding: 6px 16px; border-radius: 8px; font-size: 13px;
|
|
175
175
|
line-height: 1.4; backdrop-filter: blur(12px);
|
|
176
176
|
pointer-events: none;
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
|
|
184
184
|
/* Pin */
|
|
185
185
|
.sb-comment-pin {
|
|
186
|
-
border: 3px solid hsl(var(--pin-hue, 140), 50%, 38%);
|
|
186
|
+
border: 3px solid hsl(var(--sb--pin-hue, 140), 50%, 38%);
|
|
187
187
|
z-index: 100000; width: 32px; height: 32px;
|
|
188
188
|
margin-left: -16px; margin-top: -16px;
|
|
189
189
|
transition: transform 100ms ease-in-out;
|
|
@@ -193,16 +193,16 @@
|
|
|
193
193
|
.sb-comment-pin:active { cursor: grabbing; }
|
|
194
194
|
.sb-pin-img { width: 100%; height: 100%; object-fit: cover; }
|
|
195
195
|
.sb-comment-pin[data-resolved="true"] {
|
|
196
|
-
border-color: var(--sb
|
|
196
|
+
border-color: var(--sb--fg-muted);
|
|
197
197
|
opacity: 0.5;
|
|
198
198
|
}
|
|
199
199
|
.sb-comment-pin-pending {
|
|
200
|
-
border-color: var(--sb
|
|
200
|
+
border-color: var(--sb--fg-muted) !important;
|
|
201
201
|
opacity: 0.6;
|
|
202
202
|
animation: sb-pin-pulse 1.2s ease-in-out infinite;
|
|
203
203
|
}
|
|
204
204
|
.sb-comment-pin-failed {
|
|
205
|
-
border-color: var(--sb
|
|
205
|
+
border-color: var(--sb--fg-danger) !important;
|
|
206
206
|
cursor: pointer;
|
|
207
207
|
animation: sb-pin-shake 0.4s ease-in-out;
|
|
208
208
|
}
|
|
@@ -218,14 +218,14 @@
|
|
|
218
218
|
|
|
219
219
|
/* Error alert */
|
|
220
220
|
.sb-error-alert {
|
|
221
|
-
background: color-mix(in srgb, var(--sb
|
|
222
|
-
border: 1px solid color-mix(in srgb, var(--sb
|
|
221
|
+
background: color-mix(in srgb, var(--sb--fg-danger) 10%, transparent);
|
|
222
|
+
border: 1px solid color-mix(in srgb, var(--sb--fg-danger) 30%, transparent);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/* Resolved badge */
|
|
226
226
|
.sb-badge-resolved {
|
|
227
227
|
font-size: 10px;
|
|
228
|
-
background: color-mix(in srgb, var(--sb
|
|
228
|
+
background: color-mix(in srgb, var(--sb--fg-success) 10%, transparent);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/* Drawer button */
|
|
@@ -10,6 +10,8 @@ import { mount, unmount } from 'svelte'
|
|
|
10
10
|
import CommentsDrawerComponent from './CommentsDrawer.svelte'
|
|
11
11
|
import { isAuthenticated } from '../auth.js'
|
|
12
12
|
import { setCommentMode } from '../commentMode.js'
|
|
13
|
+
import './comment-layout.css'
|
|
14
|
+
import './comments.css'
|
|
13
15
|
|
|
14
16
|
let activeDrawer = null
|
|
15
17
|
|
|
@@ -9,6 +9,7 @@ import { mount, unmount } from 'svelte'
|
|
|
9
9
|
import ComposerComponent from './Composer.svelte'
|
|
10
10
|
import { getCachedUser } from '../auth.js'
|
|
11
11
|
import { saveDraft, clearDraft, composerDraftKey } from '../commentDrafts.js'
|
|
12
|
+
import './comment-layout.css'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Show the comment composer at a given position within a container.
|
|
@@ -17,6 +18,7 @@ import { saveDraft, clearDraft, composerDraftKey } from '../commentDrafts.js'
|
|
|
17
18
|
* @param {number} yPct - Y coordinate as percentage of container height
|
|
18
19
|
* @param {string} route - Current route path
|
|
19
20
|
* @param {object} [callbacks] - Optional callbacks
|
|
21
|
+
* @param {(xPct: number, yPct: number) => { left: string, top: string }} [callbacks.getAnchorPosition] - Resolve coordinates to overlay position
|
|
20
22
|
* @param {() => void} [callbacks.onCancel] - Called when composer is dismissed
|
|
21
23
|
* @param {(text: string) => void} [callbacks.onSubmitOptimistic] - Called with text for optimistic submission
|
|
22
24
|
* @returns {{ el: HTMLElement, destroy: () => void }}
|
|
@@ -39,8 +41,11 @@ export function showComposer(container, xPct, yPct, route, callbacks = {}) {
|
|
|
39
41
|
const draftKey = composerDraftKey(route)
|
|
40
42
|
|
|
41
43
|
function applyPosition() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
const anchor = callbacks.getAnchorPosition
|
|
45
|
+
? callbacks.getAnchorPosition(pos.x, pos.y)
|
|
46
|
+
: { left: `${pos.x}%`, top: `${pos.y}%` }
|
|
47
|
+
composer.style.left = anchor.left
|
|
48
|
+
composer.style.top = anchor.top
|
|
44
49
|
composer.style.transform = 'translate(12px, -50%)'
|
|
45
50
|
|
|
46
51
|
requestAnimationFrame(() => {
|