@dfosco/storyboard-core 4.2.0-beta.2 → 4.2.0-beta.21
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/commandpalette.config.json +109 -24
- package/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +17379 -28568
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +5 -2
- package/scaffold/agents/prompt-agent.agent.md +181 -0
- package/scaffold/agents/terminal-agent.agent.md +351 -0
- package/scaffold/codex/config.toml +246 -0
- package/scaffold/manifest.json +5 -0
- package/scaffold/skills/canvas/SKILL.md +5 -4
- package/scaffold/skills/ship/SKILL.md +1 -1
- package/scaffold/storyboard.config.json +14 -1
- package/scaffold/toolbar.config.json +1 -1
- package/src/ActionMenuButton.jsx +100 -0
- package/src/AutosyncMenuButton.css +67 -0
- package/src/AutosyncMenuButton.jsx +241 -0
- package/src/BranchSelect.jsx +29 -0
- package/src/BranchSelect.module.css +30 -0
- package/src/CanvasAgentsMenu.jsx +87 -0
- package/src/CanvasCreateMenu.jsx +609 -0
- package/src/CanvasSnap.css +27 -0
- package/src/CanvasSnap.jsx +51 -0
- package/src/CanvasUndoRedo.css +36 -0
- package/src/CanvasUndoRedo.jsx +62 -0
- package/src/CanvasZoomControl.css +53 -0
- package/src/CanvasZoomControl.jsx +49 -0
- package/src/CanvasZoomToFit.css +18 -0
- package/src/CanvasZoomToFit.jsx +26 -0
- package/src/CommandMenu.css +8 -0
- package/src/CommandMenu.jsx +286 -0
- package/src/CommandPalette.jsx +35 -0
- package/src/CommandPaletteTrigger.jsx +25 -0
- package/src/CommentsMenuButton.jsx +38 -0
- package/src/CoreUIBar.css +47 -0
- package/src/CoreUIBar.jsx +855 -0
- package/src/CreateMenuButton.jsx +116 -0
- package/src/HideChromeTrigger.jsx +40 -0
- package/src/InspectorPanel.css +109 -0
- package/src/InspectorPanel.jsx +629 -0
- package/src/PwaInstallBanner.css +42 -0
- package/src/PwaInstallBanner.jsx +124 -0
- package/src/SidePanel.jsx +260 -0
- package/src/ThemeMenuButton.jsx +136 -0
- package/src/autosync/server.js +202 -5
- package/src/autosync/server.test.js +112 -0
- package/src/canvas/__tests__/agent-integration.test.js +593 -0
- package/src/canvas/__tests__/helpers/browser.js +95 -0
- package/src/canvas/__tests__/helpers/canvas-api.js +129 -0
- package/src/canvas/__tests__/helpers/perf.js +118 -0
- package/src/canvas/__tests__/helpers/setup.js +176 -0
- package/src/canvas/__tests__/helpers/tmux.js +130 -0
- package/src/canvas/__tests__/helpers/transcript.js +129 -0
- package/src/canvas/__tests__/terminal-integration.test.js +175 -0
- package/src/canvas/hot-pool.js +757 -0
- package/src/canvas/materializer.js +31 -0
- package/src/canvas/materializer.test.js +56 -0
- package/src/canvas/selectedWidgets.js +65 -7
- package/src/canvas/server.js +1801 -22
- package/src/canvas/server.test.js +239 -0
- package/src/canvas/terminal-config.js +331 -0
- package/src/canvas/terminal-registry.js +38 -0
- package/src/canvas/terminal-server.js +1037 -29
- package/src/canvas/writeGuard.js +51 -3
- package/src/canvasConfig.js +67 -1
- package/src/canvasConfig.test.js +79 -1
- package/src/cli/agent.js +85 -0
- package/src/cli/branch.js +232 -0
- package/src/cli/canvasAdd.js +59 -12
- package/src/cli/canvasBatch.js +98 -0
- package/src/cli/canvasBounds.js +1 -1
- package/src/cli/canvasRead.js +1 -1
- package/src/cli/canvasUpdate.js +179 -0
- package/src/cli/create.js +38 -14
- package/src/cli/dev.js +157 -83
- package/src/cli/exit.js +23 -24
- package/src/cli/index.js +55 -2
- package/src/cli/proxy.js +96 -37
- package/src/cli/schemas.js +22 -4
- package/src/cli/server.js +148 -25
- package/src/cli/serverUrl.js +8 -3
- package/src/cli/sessions.js +131 -5
- package/src/cli/setup.js +109 -11
- package/src/cli/terminal-commands.js +16 -8
- package/src/cli/terminal-messaging.js +231 -0
- package/src/cli/terminal-welcome.js +365 -33
- package/src/commandActions.js +1 -0
- package/src/commandPaletteConfig.js +9 -0
- package/src/comments/auth.js +2 -1
- package/src/comments/ui/AuthModal.jsx +114 -0
- package/src/comments/ui/CommentWindow.jsx +329 -0
- package/src/comments/ui/CommentsDrawer.jsx +102 -0
- package/src/comments/ui/Composer.jsx +64 -0
- package/src/comments/ui/authModal.test.js +1 -1
- package/src/comments/ui/commentWindow.js +16 -17
- package/src/comments/ui/commentsDrawer.js +25 -26
- package/src/comments/ui/composer.js +23 -24
- package/src/comments/ui/index.js +2 -3
- package/src/configSchema.js +59 -1
- package/src/configStore.js +161 -0
- package/src/core-ui-colors.css +12 -0
- package/src/devtools.js +17 -19
- package/src/devtools.test.js +18 -9
- package/src/featureFlags.js +12 -5
- package/src/fuzzySearch.test.js +10 -0
- package/src/index.js +14 -2
- package/src/lib/components/ui/alert/alert-action.jsx +11 -0
- package/src/lib/components/ui/alert/alert-description.jsx +11 -0
- package/src/lib/components/ui/alert/alert-title.jsx +11 -0
- package/src/lib/components/ui/alert/alert.jsx +25 -0
- package/src/lib/components/ui/alert/index.js +15 -15
- package/src/lib/components/ui/avatar/avatar-badge.jsx +22 -0
- package/src/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
- package/src/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
- package/src/lib/components/ui/avatar/avatar-group.jsx +19 -0
- package/src/lib/components/ui/avatar/avatar-image.jsx +15 -0
- package/src/lib/components/ui/avatar/avatar.jsx +19 -0
- package/src/lib/components/ui/avatar/index.js +20 -20
- package/src/lib/components/ui/badge/badge.jsx +31 -0
- package/src/lib/components/ui/badge/index.js +2 -2
- package/src/lib/components/ui/button/button.jsx +100 -0
- package/src/lib/components/ui/button/index.js +9 -9
- package/src/lib/components/ui/card/card-action.jsx +11 -0
- package/src/lib/components/ui/card/card-content.jsx +11 -0
- package/src/lib/components/ui/card/card-description.jsx +11 -0
- package/src/lib/components/ui/card/card-footer.jsx +11 -0
- package/src/lib/components/ui/card/card-header.jsx +19 -0
- package/src/lib/components/ui/card/card-title.jsx +11 -0
- package/src/lib/components/ui/card/card.jsx +17 -0
- package/src/lib/components/ui/card/index.js +23 -23
- package/src/lib/components/ui/checkbox/checkbox.jsx +29 -0
- package/src/lib/components/ui/checkbox/index.js +5 -5
- package/src/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
- package/src/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
- package/src/lib/components/ui/collapsible/collapsible.jsx +7 -0
- package/src/lib/components/ui/collapsible/index.js +11 -11
- package/src/lib/components/ui/dialog/dialog-close.jsx +7 -0
- package/src/lib/components/ui/dialog/dialog-content.jsx +34 -0
- package/src/lib/components/ui/dialog/dialog-description.jsx +15 -0
- package/src/lib/components/ui/dialog/dialog-footer.jsx +23 -0
- package/src/lib/components/ui/dialog/dialog-header.jsx +11 -0
- package/src/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
- package/src/lib/components/ui/dialog/dialog-portal.jsx +4 -0
- package/src/lib/components/ui/dialog/dialog-title.jsx +15 -0
- package/src/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
- package/src/lib/components/ui/dialog/dialog.jsx +4 -0
- package/src/lib/components/ui/dialog/index.js +32 -32
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
- package/src/lib/components/ui/dropdown-menu/index.js +52 -52
- package/src/lib/components/ui/input/index.js +5 -5
- package/src/lib/components/ui/input/input.jsx +19 -0
- package/src/lib/components/ui/label/index.js +5 -5
- package/src/lib/components/ui/label/label.jsx +19 -0
- package/src/lib/components/ui/panel/index.js +21 -21
- package/src/lib/components/ui/panel/panel-body.jsx +11 -0
- package/src/lib/components/ui/panel/panel-close.jsx +16 -0
- package/src/lib/components/ui/panel/panel-content.jsx +29 -0
- package/src/lib/components/ui/panel/panel-footer.jsx +11 -0
- package/src/lib/components/ui/panel/panel-header.jsx +11 -0
- package/src/lib/components/ui/panel/panel-title.jsx +12 -0
- package/src/lib/components/ui/panel/panel.jsx +4 -0
- package/src/lib/components/ui/popover/index.js +26 -26
- package/src/lib/components/ui/popover/popover-close.jsx +7 -0
- package/src/lib/components/ui/popover/popover-content.jsx +22 -0
- package/src/lib/components/ui/popover/popover-description.jsx +11 -0
- package/src/lib/components/ui/popover/popover-header.jsx +11 -0
- package/src/lib/components/ui/popover/popover-portal.jsx +4 -0
- package/src/lib/components/ui/popover/popover-title.jsx +11 -0
- package/src/lib/components/ui/popover/popover-trigger.jsx +8 -0
- package/src/lib/components/ui/popover/popover.jsx +4 -0
- package/src/lib/components/ui/searchable-list.jsx +159 -0
- package/src/lib/components/ui/select/index.js +35 -35
- package/src/lib/components/ui/select/select-content.jsx +30 -0
- package/src/lib/components/ui/select/select-group-heading.jsx +17 -0
- package/src/lib/components/ui/select/select-group.jsx +15 -0
- package/src/lib/components/ui/select/select-item.jsx +26 -0
- package/src/lib/components/ui/select/select-label.jsx +11 -0
- package/src/lib/components/ui/select/select-portal.jsx +4 -0
- package/src/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
- package/src/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
- package/src/lib/components/ui/select/select-separator.jsx +15 -0
- package/src/lib/components/ui/select/select-trigger.jsx +25 -0
- package/src/lib/components/ui/select/select.jsx +4 -0
- package/src/lib/components/ui/separator/index.js +5 -5
- package/src/lib/components/ui/separator/separator.jsx +22 -0
- package/src/lib/components/ui/sheet/index.js +32 -32
- package/src/lib/components/ui/sheet/sheet-close.jsx +7 -0
- package/src/lib/components/ui/sheet/sheet-content.jsx +35 -0
- package/src/lib/components/ui/sheet/sheet-description.jsx +15 -0
- package/src/lib/components/ui/sheet/sheet-footer.jsx +11 -0
- package/src/lib/components/ui/sheet/sheet-header.jsx +11 -0
- package/src/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
- package/src/lib/components/ui/sheet/sheet-portal.jsx +4 -0
- package/src/lib/components/ui/sheet/sheet-title.jsx +15 -0
- package/src/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
- package/src/lib/components/ui/sheet/sheet.jsx +4 -0
- package/src/lib/components/ui/textarea/index.js +5 -5
- package/src/lib/components/ui/textarea/textarea.jsx +18 -0
- package/src/lib/components/ui/toggle/index.js +6 -9
- package/src/lib/components/ui/toggle/toggle.jsx +36 -0
- package/src/lib/components/ui/toggle-group/index.js +8 -8
- package/src/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
- package/src/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
- package/src/lib/components/ui/tooltip/index.js +3 -3
- package/src/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
- package/src/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
- package/src/lib/components/ui/tooltip/tooltip.jsx +11 -0
- package/src/lib/components/ui/trigger-button/index.js +3 -3
- package/src/lib/components/ui/trigger-button/trigger-button.css +38 -0
- package/src/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
- package/src/logger/devLogger.js +238 -0
- package/src/logger/devLogger.test.js +193 -0
- package/src/modes.test.js +4 -4
- package/src/mountStoryboardCore.js +123 -27
- package/src/paletteProviders.js +3 -0
- package/src/paletteProviders.test.js +2 -2
- package/src/server/index.js +98 -36
- package/src/sidepanel.css +214 -0
- package/src/styles/tailwind.css +1 -1
- package/src/svelte-plugin-ui/__tests__/ModeSwitch.test.ts +8 -8
- package/src/svelte-plugin-ui/__tests__/ToolbarShell.test.ts +11 -10
- package/src/svelte-plugin-ui/components/Icon.css +11 -0
- package/src/svelte-plugin-ui/components/Icon.jsx +281 -0
- package/src/svelte-plugin-ui/components/ModeSwitch.css +90 -0
- package/src/svelte-plugin-ui/components/ModeSwitch.jsx +47 -0
- package/src/svelte-plugin-ui/components/ToolbarShell.css +80 -0
- package/src/svelte-plugin-ui/components/ToolbarShell.jsx +84 -0
- package/src/svelte-plugin-ui/components/Viewfinder.css +412 -0
- package/src/svelte-plugin-ui/components/Viewfinder.jsx +512 -0
- package/src/svelte-plugin-ui/mount.ts +12 -16
- package/src/toolRegistry.js +4 -4
- package/src/toolbarConfigStore.js +30 -0
- package/src/tools/handlers/autosync.js +1 -1
- package/src/tools/handlers/canvasAddWidget.js +1 -1
- package/src/tools/handlers/canvasAgents.js +19 -0
- package/src/tools/handlers/canvasToolbar.js +8 -8
- package/src/tools/handlers/commandPalette.js +9 -0
- package/src/tools/handlers/comments.js +1 -1
- package/src/tools/handlers/create.js +1 -1
- package/src/tools/handlers/devtools.js +16 -0
- package/src/tools/handlers/devtools.test.js +38 -0
- package/src/tools/handlers/flows.js +1 -1
- package/src/tools/handlers/hideChrome.js +9 -0
- package/src/tools/handlers/paletteTheme.js +35 -0
- package/src/tools/handlers/theme.js +1 -1
- package/src/tools/registry.js +4 -1
- package/src/tools/surfaces/commandList.js +3 -3
- package/src/tools/surfaces/mainToolbar.js +3 -3
- package/src/tools/surfaces/registry.js +4 -4
- package/src/ui/design-modes.ts +2 -2
- package/src/ui/viewfinder.ts +1 -1
- package/src/vite/server-plugin.js +242 -60
- package/src/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
- package/src/workshop/features/createCanvas/index.js +1 -1
- package/src/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
- package/src/workshop/features/createFlow/index.js +1 -1
- package/src/workshop/features/createPage/CreatePageForm.jsx +304 -0
- package/src/workshop/features/createPage/index.js +1 -1
- package/src/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
- package/src/workshop/features/createPrototype/index.js +1 -1
- package/src/workshop/features/createPrototype/server.js +98 -0
- package/src/workshop/features/createStory/CreateStoryForm.jsx +208 -0
- package/src/workshop/features/createStory/index.js +1 -1
- package/src/workshop/ui/WorkshopPanel.jsx +98 -0
- package/src/workshop/ui/mount.ts +1 -1
- package/src/worktree/port.js +48 -0
- package/src/worktree/serverRegistry.js +120 -0
- package/toolbar.config.json +93 -42
- package/widgets.config.json +580 -12
- package/src/ActionMenuButton.svelte +0 -119
- package/src/AutosyncMenuButton.svelte +0 -397
- package/src/CanvasCreateMenu.svelte +0 -295
- package/src/CanvasSnap.svelte +0 -87
- package/src/CanvasUndoRedo.svelte +0 -108
- package/src/CanvasZoomControl.svelte +0 -111
- package/src/CanvasZoomToFit.svelte +0 -52
- package/src/CommandMenu.svelte +0 -249
- package/src/CommandPalette.svelte +0 -33
- package/src/CommentsMenuButton.svelte +0 -53
- package/src/CoreUIBar.svelte +0 -847
- package/src/CreateMenuButton.svelte +0 -133
- package/src/DocPanel.svelte +0 -299
- package/src/InspectorPanel.svelte +0 -745
- package/src/PwaInstallBanner.svelte +0 -124
- package/src/SidePanel.svelte +0 -480
- package/src/ThemeMenuButton.svelte +0 -132
- package/src/comments/ui/AuthModal.svelte +0 -108
- package/src/comments/ui/CommentWindow.svelte +0 -333
- package/src/comments/ui/CommentsDrawer.svelte +0 -96
- package/src/comments/ui/Composer.svelte +0 -65
- package/src/lib/components/ui/alert/alert-action.svelte +0 -19
- package/src/lib/components/ui/alert/alert-description.svelte +0 -22
- package/src/lib/components/ui/alert/alert-title.svelte +0 -22
- package/src/lib/components/ui/alert/alert.svelte +0 -38
- package/src/lib/components/ui/avatar/avatar-badge.svelte +0 -25
- package/src/lib/components/ui/avatar/avatar-fallback.svelte +0 -20
- package/src/lib/components/ui/avatar/avatar-group-count.svelte +0 -22
- package/src/lib/components/ui/avatar/avatar-group.svelte +0 -22
- package/src/lib/components/ui/avatar/avatar-image.svelte +0 -17
- package/src/lib/components/ui/avatar/avatar.svelte +0 -24
- package/src/lib/components/ui/badge/badge.svelte +0 -44
- package/src/lib/components/ui/button/button.svelte +0 -108
- package/src/lib/components/ui/card/card-action.svelte +0 -21
- package/src/lib/components/ui/card/card-content.svelte +0 -19
- package/src/lib/components/ui/card/card-description.svelte +0 -19
- package/src/lib/components/ui/card/card-footer.svelte +0 -18
- package/src/lib/components/ui/card/card-header.svelte +0 -21
- package/src/lib/components/ui/card/card-title.svelte +0 -14
- package/src/lib/components/ui/card/card.svelte +0 -21
- package/src/lib/components/ui/checkbox/checkbox.svelte +0 -39
- package/src/lib/components/ui/collapsible/collapsible-content.svelte +0 -7
- package/src/lib/components/ui/collapsible/collapsible-trigger.svelte +0 -7
- package/src/lib/components/ui/collapsible/collapsible.svelte +0 -11
- package/src/lib/components/ui/dialog/dialog-close.svelte +0 -11
- package/src/lib/components/ui/dialog/dialog-content.svelte +0 -42
- package/src/lib/components/ui/dialog/dialog-description.svelte +0 -17
- package/src/lib/components/ui/dialog/dialog-footer.svelte +0 -29
- package/src/lib/components/ui/dialog/dialog-header.svelte +0 -19
- package/src/lib/components/ui/dialog/dialog-overlay.svelte +0 -17
- package/src/lib/components/ui/dialog/dialog-portal.svelte +0 -7
- package/src/lib/components/ui/dialog/dialog-title.svelte +0 -17
- package/src/lib/components/ui/dialog/dialog-trigger.svelte +0 -11
- package/src/lib/components/ui/dialog/dialog.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +0 -16
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -40
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -27
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +0 -18
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -24
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +0 -20
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +0 -16
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +0 -34
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -17
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +0 -19
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +0 -17
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +0 -27
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +0 -7
- package/src/lib/components/ui/input/input.svelte +0 -40
- package/src/lib/components/ui/label/label.svelte +0 -20
- package/src/lib/components/ui/panel/panel-body.svelte +0 -13
- package/src/lib/components/ui/panel/panel-close.svelte +0 -16
- package/src/lib/components/ui/panel/panel-content.svelte +0 -33
- package/src/lib/components/ui/panel/panel-footer.svelte +0 -13
- package/src/lib/components/ui/panel/panel-header.svelte +0 -16
- package/src/lib/components/ui/panel/panel-title.svelte +0 -14
- package/src/lib/components/ui/panel/panel.svelte +0 -15
- package/src/lib/components/ui/popover/popover-close.svelte +0 -7
- package/src/lib/components/ui/popover/popover-content.svelte +0 -27
- package/src/lib/components/ui/popover/popover-description.svelte +0 -19
- package/src/lib/components/ui/popover/popover-header.svelte +0 -19
- package/src/lib/components/ui/popover/popover-portal.svelte +0 -7
- package/src/lib/components/ui/popover/popover-title.svelte +0 -19
- package/src/lib/components/ui/popover/popover-trigger.svelte +0 -17
- package/src/lib/components/ui/popover/popover.svelte +0 -7
- package/src/lib/components/ui/select/select-content.svelte +0 -40
- package/src/lib/components/ui/select/select-group-heading.svelte +0 -19
- package/src/lib/components/ui/select/select-group.svelte +0 -17
- package/src/lib/components/ui/select/select-item.svelte +0 -38
- package/src/lib/components/ui/select/select-label.svelte +0 -18
- package/src/lib/components/ui/select/select-portal.svelte +0 -7
- package/src/lib/components/ui/select/select-scroll-down-button.svelte +0 -20
- package/src/lib/components/ui/select/select-scroll-up-button.svelte +0 -20
- package/src/lib/components/ui/select/select-separator.svelte +0 -17
- package/src/lib/components/ui/select/select-trigger.svelte +0 -27
- package/src/lib/components/ui/select/select.svelte +0 -11
- package/src/lib/components/ui/separator/separator.svelte +0 -23
- package/src/lib/components/ui/sheet/sheet-close.svelte +0 -7
- package/src/lib/components/ui/sheet/sheet-content.svelte +0 -43
- package/src/lib/components/ui/sheet/sheet-description.svelte +0 -17
- package/src/lib/components/ui/sheet/sheet-footer.svelte +0 -18
- package/src/lib/components/ui/sheet/sheet-header.svelte +0 -19
- package/src/lib/components/ui/sheet/sheet-overlay.svelte +0 -17
- package/src/lib/components/ui/sheet/sheet-portal.svelte +0 -7
- package/src/lib/components/ui/sheet/sheet-title.svelte +0 -17
- package/src/lib/components/ui/sheet/sheet-trigger.svelte +0 -7
- package/src/lib/components/ui/sheet/sheet.svelte +0 -7
- package/src/lib/components/ui/textarea/textarea.svelte +0 -21
- package/src/lib/components/ui/toggle/toggle.svelte +0 -45
- package/src/lib/components/ui/toggle-group/toggle-group-item.svelte +0 -35
- package/src/lib/components/ui/toggle-group/toggle-group.svelte +0 -63
- package/src/lib/components/ui/tooltip/tooltip-content.svelte +0 -24
- package/src/lib/components/ui/tooltip/tooltip-trigger.svelte +0 -27
- package/src/lib/components/ui/tooltip/tooltip.svelte +0 -9
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +0 -106
- package/src/svelte-plugin-ui/components/Icon.svelte +0 -181
- package/src/svelte-plugin-ui/components/ModeSwitch.svelte +0 -121
- package/src/svelte-plugin-ui/components/ToolbarShell.svelte +0 -150
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +0 -1001
- package/src/tools/handlers/docs.js +0 -11
- package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +0 -139
- package/src/workshop/features/createFlow/CreateFlowForm.svelte +0 -314
- package/src/workshop/features/createPage/CreatePageForm.svelte +0 -249
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +0 -287
- package/src/workshop/features/createStory/CreateStoryForm.svelte +0 -161
- package/src/workshop/ui/WorkshopPanel.svelte +0 -97
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
ThemeMenuButton — toolbar dropdown for switching the app color scheme.
|
|
3
|
-
|
|
4
|
-
Renders a radio group of theme options (System, Light, Dark, etc.),
|
|
5
|
-
followed by a separator and "Theme settings" submenu with sync toggles.
|
|
6
|
-
-->
|
|
7
|
-
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
import { TriggerButton } from './lib/components/ui/trigger-button/index.js'
|
|
10
|
-
import * as DropdownMenu from './lib/components/ui/dropdown-menu/index.js'
|
|
11
|
-
import Icon from './svelte-plugin-ui/components/Icon.svelte'
|
|
12
|
-
import { themeState, setTheme, THEMES, type ThemeValue, themeSyncState, setThemeSyncTarget, type ThemeSyncTargets } from './stores/themeStore.js'
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
config?: {
|
|
16
|
-
ariaLabel?: string
|
|
17
|
-
icon?: string
|
|
18
|
-
meta?: Record<string, any>
|
|
19
|
-
label?: string
|
|
20
|
-
menuWidth?: string
|
|
21
|
-
}
|
|
22
|
-
data?: any
|
|
23
|
-
localOnly?: boolean
|
|
24
|
-
tabindex?: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let { config = {}, data, localOnly, tabindex = -1 }: Props = $props()
|
|
28
|
-
|
|
29
|
-
let menuOpen = $state(false)
|
|
30
|
-
let canvasActive = $state(false)
|
|
31
|
-
|
|
32
|
-
function handleSelect(value: ThemeValue) {
|
|
33
|
-
setTheme(value)
|
|
34
|
-
menuOpen = false
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function handleSyncToggle(e: Event, target: keyof ThemeSyncTargets) {
|
|
38
|
-
e.preventDefault()
|
|
39
|
-
setThemeSyncTarget(target, !$themeSyncState[target])
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
$effect(() => {
|
|
43
|
-
function handleCanvasMounted() {
|
|
44
|
-
canvasActive = true
|
|
45
|
-
}
|
|
46
|
-
function handleCanvasUnmounted() {
|
|
47
|
-
canvasActive = false
|
|
48
|
-
}
|
|
49
|
-
document.addEventListener('storyboard:canvas:mounted', handleCanvasMounted)
|
|
50
|
-
document.addEventListener('storyboard:canvas:unmounted', handleCanvasUnmounted)
|
|
51
|
-
|
|
52
|
-
const state = (window as any).__storyboardCanvasBridgeState
|
|
53
|
-
canvasActive = state?.active === true
|
|
54
|
-
if (!canvasActive) {
|
|
55
|
-
document.dispatchEvent(new CustomEvent('storyboard:canvas:status-request'))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return () => {
|
|
59
|
-
document.removeEventListener('storyboard:canvas:mounted', handleCanvasMounted)
|
|
60
|
-
document.removeEventListener('storyboard:canvas:unmounted', handleCanvasUnmounted)
|
|
61
|
-
}
|
|
62
|
-
})
|
|
63
|
-
</script>
|
|
64
|
-
|
|
65
|
-
<DropdownMenu.Root bind:open={menuOpen}>
|
|
66
|
-
<DropdownMenu.Trigger>
|
|
67
|
-
{#snippet child({ props })}
|
|
68
|
-
<TriggerButton
|
|
69
|
-
active={menuOpen}
|
|
70
|
-
size="icon-xl"
|
|
71
|
-
aria-label={config.ariaLabel || 'Theme'}
|
|
72
|
-
{tabindex}
|
|
73
|
-
{...props}
|
|
74
|
-
>
|
|
75
|
-
<Icon name={config.icon || 'primer/sun'} size={16} {...(config.meta || {})} />
|
|
76
|
-
</TriggerButton>
|
|
77
|
-
{/snippet}
|
|
78
|
-
</DropdownMenu.Trigger>
|
|
79
|
-
|
|
80
|
-
<DropdownMenu.Content side="top" align="end" sideOffset={16} style={config.menuWidth ? `min-width: ${config.menuWidth}` : undefined} class="min-w-[200px]">
|
|
81
|
-
{#if config.label}
|
|
82
|
-
<DropdownMenu.Label>{config.label}</DropdownMenu.Label>
|
|
83
|
-
{/if}
|
|
84
|
-
|
|
85
|
-
<DropdownMenu.RadioGroup value={$themeState.theme}>
|
|
86
|
-
{#each THEMES as option (option.value)}
|
|
87
|
-
<DropdownMenu.RadioItem
|
|
88
|
-
value={option.value}
|
|
89
|
-
onclick={() => handleSelect(option.value)}
|
|
90
|
-
>
|
|
91
|
-
{option.name}
|
|
92
|
-
</DropdownMenu.RadioItem>
|
|
93
|
-
{/each}
|
|
94
|
-
</DropdownMenu.RadioGroup>
|
|
95
|
-
|
|
96
|
-
<DropdownMenu.Separator />
|
|
97
|
-
|
|
98
|
-
<DropdownMenu.Sub>
|
|
99
|
-
<DropdownMenu.SubTrigger>Theme settings</DropdownMenu.SubTrigger>
|
|
100
|
-
<DropdownMenu.SubContent class="min-w-[180px]">
|
|
101
|
-
<DropdownMenu.Label>Apply theme to</DropdownMenu.Label>
|
|
102
|
-
{#if canvasActive}
|
|
103
|
-
<DropdownMenu.CheckboxItem
|
|
104
|
-
checked={$themeSyncState.canvas}
|
|
105
|
-
onSelect={(e) => handleSyncToggle(e, 'canvas')}
|
|
106
|
-
>
|
|
107
|
-
Canvas
|
|
108
|
-
</DropdownMenu.CheckboxItem>
|
|
109
|
-
{:else}
|
|
110
|
-
<DropdownMenu.CheckboxItem
|
|
111
|
-
checked={$themeSyncState.prototype}
|
|
112
|
-
onSelect={(e) => handleSyncToggle(e, 'prototype')}
|
|
113
|
-
>
|
|
114
|
-
Prototype
|
|
115
|
-
</DropdownMenu.CheckboxItem>
|
|
116
|
-
{/if}
|
|
117
|
-
<DropdownMenu.CheckboxItem
|
|
118
|
-
checked={$themeSyncState.toolbar}
|
|
119
|
-
onSelect={(e) => handleSyncToggle(e, 'toolbar')}
|
|
120
|
-
>
|
|
121
|
-
Tools
|
|
122
|
-
</DropdownMenu.CheckboxItem>
|
|
123
|
-
<DropdownMenu.CheckboxItem
|
|
124
|
-
checked={$themeSyncState.codeBoxes}
|
|
125
|
-
onSelect={(e) => handleSyncToggle(e, 'codeBoxes')}
|
|
126
|
-
>
|
|
127
|
-
Code boxes
|
|
128
|
-
</DropdownMenu.CheckboxItem>
|
|
129
|
-
</DropdownMenu.SubContent>
|
|
130
|
-
</DropdownMenu.Sub>
|
|
131
|
-
</DropdownMenu.Content>
|
|
132
|
-
</DropdownMenu.Root>
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
AuthModal — PAT entry modal for comments authentication.
|
|
3
|
-
Uses shadcn Button, Input, Label, Alert, Avatar.
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
<script lang="ts">
|
|
7
|
-
import { onMount } from 'svelte'
|
|
8
|
-
import { setToken, validateToken } from '../auth.js'
|
|
9
|
-
import { getCommentsConfig } from '../config.js'
|
|
10
|
-
import { Button } from '../../lib/components/ui/button/index.js'
|
|
11
|
-
import { Input } from '../../lib/components/ui/input/index.js'
|
|
12
|
-
import { Label } from '../../lib/components/ui/label/index.js'
|
|
13
|
-
import * as Alert from '../../lib/components/ui/alert/index.js'
|
|
14
|
-
import * as Avatar from '../../lib/components/ui/avatar/index.js'
|
|
15
|
-
|
|
16
|
-
interface Props {
|
|
17
|
-
onDone?: (user: { login: string; avatarUrl: string }) => void
|
|
18
|
-
onClose?: () => void
|
|
19
|
-
initialError?: string | null
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let { onDone, onClose, initialError = null }: Props = $props()
|
|
23
|
-
|
|
24
|
-
let token = $state('')
|
|
25
|
-
let submitting = $state(false)
|
|
26
|
-
let error: string | null = $state(null)
|
|
27
|
-
let user: { login: string; avatarUrl: string } | null = $state(null)
|
|
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()
|
|
46
|
-
|
|
47
|
-
onMount(() => {
|
|
48
|
-
error = initialError
|
|
49
|
-
inputEl?.focus()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
async function submit() {
|
|
53
|
-
const val = token.trim()
|
|
54
|
-
if (!val) return
|
|
55
|
-
submitting = true; error = null
|
|
56
|
-
try { const result = await validateToken(val); setToken(val); user = result }
|
|
57
|
-
catch (err: any) { error = err.message }
|
|
58
|
-
finally { submitting = false }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function done() { if (user) onDone?.(user) }
|
|
62
|
-
|
|
63
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
64
|
-
if (e.key === 'Enter' && !user) submit()
|
|
65
|
-
}
|
|
66
|
-
</script>
|
|
67
|
-
|
|
68
|
-
<div class="bg-popover text-popover-foreground border border-border rounded-lg shadow-lg overflow-hidden max-w-[600px] w-full font-sans">
|
|
69
|
-
<div class="flex items-center justify-between px-4 py-3 border-b border-border">
|
|
70
|
-
<h2 class="text-medium font-semibold">Sign in for comments</h2>
|
|
71
|
-
<Button variant="ghost" size="icon" onclick={onClose} aria-label="Close" class="h-7 w-7 text-muted-foreground">×</Button>
|
|
72
|
-
</div>
|
|
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>
|
|
78
|
-
<p class="text-sm text-muted-foreground leading-relaxed">
|
|
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:
|
|
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>
|
|
88
|
-
<div class="space-y-1">
|
|
89
|
-
<Label for="sb-auth-token-input">Personal Access Token</Label>
|
|
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} />
|
|
91
|
-
</div>
|
|
92
|
-
{#if user}
|
|
93
|
-
<div class="flex items-center py-1 gap-3">
|
|
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>
|
|
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>
|
|
96
|
-
</div>
|
|
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>
|
|
103
|
-
</div>
|
|
104
|
-
<div class="flex items-center justify-end px-4 py-3 border-t border-border gap-2">
|
|
105
|
-
<Button variant="outline" size="sm" onclick={onClose}>Cancel</Button>
|
|
106
|
-
<Button size="sm" disabled={submitting} onclick={user ? done : submit}>{user ? 'Done' : (submitting ? 'Validating\u2026' : 'Sign in')}</Button>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
CommentWindow — thread viewer popup showing a comment with replies and reactions.
|
|
3
|
-
Uses shadcn-svelte Button, Textarea, Avatar, Badge, Separator.
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
<script lang="ts">
|
|
7
|
-
import { replyToComment, addReaction, removeReaction, resolveComment, unresolveComment, editComment, editReply, deleteComment } from '../api.js'
|
|
8
|
-
import { saveDraft, getDraft, clearDraft, replyDraftKey } from '../commentDrafts.js'
|
|
9
|
-
import { Button } from '../../lib/components/ui/button/index.js'
|
|
10
|
-
import { Textarea } from '../../lib/components/ui/textarea/index.js'
|
|
11
|
-
import * as Avatar from '../../lib/components/ui/avatar/index.js'
|
|
12
|
-
import { Separator } from '../../lib/components/ui/separator/index.js'
|
|
13
|
-
import { cn } from '../../lib/utils/index.js'
|
|
14
|
-
|
|
15
|
-
const REACTION_EMOJI: Record<string, string> = {
|
|
16
|
-
THUMBS_UP: '👍', THUMBS_DOWN: '👎', LAUGH: '😄', HOORAY: '🎉',
|
|
17
|
-
CONFUSED: '😕', HEART: '❤️', ROCKET: '🚀', EYES: '👀',
|
|
18
|
-
}
|
|
19
|
-
const emojiEntries = Object.entries(REACTION_EMOJI)
|
|
20
|
-
|
|
21
|
-
interface Props {
|
|
22
|
-
comment: any
|
|
23
|
-
discussion: any
|
|
24
|
-
user?: any
|
|
25
|
-
onClose?: () => void
|
|
26
|
-
onMove?: () => void
|
|
27
|
-
winEl?: HTMLElement
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let { comment, discussion, user = null, onClose, onMove, winEl }: Props = $props()
|
|
31
|
-
|
|
32
|
-
const draftKey = $derived(replyDraftKey(comment.id))
|
|
33
|
-
|
|
34
|
-
const resolved = $derived(!!comment.meta?.resolved)
|
|
35
|
-
let resolving = $state(false)
|
|
36
|
-
let copied = $state(false)
|
|
37
|
-
let editing = $state(false)
|
|
38
|
-
let editText = $state('')
|
|
39
|
-
let saving = $state(false)
|
|
40
|
-
const commentText = $derived(comment.text ?? '')
|
|
41
|
-
// svelte-ignore state_referenced_locally
|
|
42
|
-
let replyText = $state(getDraft(draftKey)?.text ?? '')
|
|
43
|
-
let submittingReply = $state(false)
|
|
44
|
-
let editingReply = $state(-1)
|
|
45
|
-
let editReplyText = $state('')
|
|
46
|
-
let savingReply = $state(false)
|
|
47
|
-
let pickerTarget: string | null = $state(null)
|
|
48
|
-
// svelte-ignore state_referenced_locally
|
|
49
|
-
let reactions: any[] = $state([...(comment.reactionGroups ?? [])])
|
|
50
|
-
// svelte-ignore state_referenced_locally
|
|
51
|
-
let replyReactions: any[][] = $state((comment.replies ?? []).map((r: any) => [...(r.reactionGroups ?? [])]))
|
|
52
|
-
// svelte-ignore state_referenced_locally
|
|
53
|
-
let replyTexts: string[] = $state((comment.replies ?? []).map((r: any) => r.text ?? r.body ?? ''))
|
|
54
|
-
|
|
55
|
-
const replies = $derived(comment.replies ?? [])
|
|
56
|
-
const canEdit = $derived(!!(user && comment.author?.login === user.login))
|
|
57
|
-
const canReply = $derived(!!(user && discussion))
|
|
58
|
-
|
|
59
|
-
function handleReplyBlur() {
|
|
60
|
-
if (replyText.trim()) {
|
|
61
|
-
saveDraft(draftKey, { type: 'reply', text: replyText })
|
|
62
|
-
} else {
|
|
63
|
-
clearDraft(draftKey)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function timeAgo(dateStr: string) {
|
|
68
|
-
return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function emojiFor(content: string) { return REACTION_EMOJI[content] ?? content }
|
|
72
|
-
function isReacted(content: string) { return reactions.some((r: any) => r.content === content && r.viewerHasReacted) }
|
|
73
|
-
function isReplyReacted(ri: number, content: string) { return (replyReactions[ri] ?? []).some((r: any) => r.content === content && r.viewerHasReacted) }
|
|
74
|
-
|
|
75
|
-
function pillClass(active: boolean) {
|
|
76
|
-
return cn(
|
|
77
|
-
'inline-flex items-center text-xs px-2 py-0.5 rounded-full border cursor-pointer transition-colors',
|
|
78
|
-
active ? 'border-primary bg-primary/10 text-primary' : 'border-border bg-transparent text-muted-foreground'
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function emojiPickerBtnClass(active: boolean) {
|
|
83
|
-
return cn(
|
|
84
|
-
'flex items-center justify-center w-7 h-7 rounded border-none text-base cursor-pointer transition-colors',
|
|
85
|
-
active ? 'bg-primary/10' : 'bg-transparent hover:bg-muted'
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function toggleResolve() {
|
|
90
|
-
resolving = true
|
|
91
|
-
try {
|
|
92
|
-
if (resolved) {
|
|
93
|
-
await unresolveComment(comment.id, comment._rawBody ?? comment.body ?? '')
|
|
94
|
-
comment.meta = { ...comment.meta }; delete comment.meta.resolved
|
|
95
|
-
resolving = false; onMove?.()
|
|
96
|
-
} else {
|
|
97
|
-
await resolveComment(comment.id, comment._rawBody ?? comment.body ?? '')
|
|
98
|
-
comment.meta = { ...comment.meta, resolved: true }; onMove?.(); onClose?.()
|
|
99
|
-
}
|
|
100
|
-
} catch (err) { console.error('[storyboard] Failed to toggle resolve:', err); resolving = false }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function copyLink() {
|
|
104
|
-
const url = new URL(window.location.href); url.searchParams.set('comment', comment.id)
|
|
105
|
-
navigator.clipboard.writeText(url.toString()).then(() => { copied = true; setTimeout(() => { copied = false }, 2000) }).catch(() => {})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function saveEdit() {
|
|
109
|
-
const t = editText.trim(); if (!t) return; saving = true
|
|
110
|
-
try { await editComment(comment.id, comment._rawBody ?? comment.body ?? '', t); comment.text = t; comment._rawBody = null; editing = false } catch (err) { console.error('[storyboard] Failed to edit:', err) } finally { saving = false }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function toggleReaction(content: string, gi?: number) {
|
|
114
|
-
const group = gi !== undefined ? reactions[gi] : reactions.find((r: any) => r.content === content)
|
|
115
|
-
const was = group?.viewerHasReacted ?? false
|
|
116
|
-
if (was && group) {
|
|
117
|
-
group.users = { totalCount: Math.max(0, (group.users?.totalCount ?? 1) - 1) }; group.viewerHasReacted = false
|
|
118
|
-
reactions = group.users.totalCount === 0 ? reactions.filter((r: any) => r.content !== content) : [...reactions]
|
|
119
|
-
} else if (group) {
|
|
120
|
-
group.users = { totalCount: (group.users?.totalCount ?? 0) + 1 }; group.viewerHasReacted = true; reactions = [...reactions]
|
|
121
|
-
} else {
|
|
122
|
-
reactions = [...reactions, { content, users: { totalCount: 1 }, viewerHasReacted: true }]
|
|
123
|
-
}
|
|
124
|
-
comment.reactionGroups = reactions
|
|
125
|
-
try { if (was) await removeReaction(comment.id, content); else await addReaction(comment.id, content) } catch {}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function toggleReplyReaction(ri: number, content: string, rgi?: number) {
|
|
129
|
-
const reply = replies[ri]; if (!reply) return
|
|
130
|
-
const groups = replyReactions[ri] ?? []
|
|
131
|
-
const group = rgi !== undefined ? groups[rgi] : groups.find((r: any) => r.content === content)
|
|
132
|
-
const was = group?.viewerHasReacted ?? false
|
|
133
|
-
if (was && group) {
|
|
134
|
-
group.users = { totalCount: Math.max(0, (group.users?.totalCount ?? 1) - 1) }; group.viewerHasReacted = false
|
|
135
|
-
if (group.users.totalCount === 0) replyReactions[ri] = groups.filter((r: any) => r.content !== content)
|
|
136
|
-
} else if (group) {
|
|
137
|
-
group.users = { totalCount: (group.users?.totalCount ?? 0) + 1 }; group.viewerHasReacted = true
|
|
138
|
-
} else {
|
|
139
|
-
groups.push({ content, users: { totalCount: 1 }, viewerHasReacted: true }); replyReactions[ri] = groups
|
|
140
|
-
}
|
|
141
|
-
replyReactions = [...replyReactions]; reply.reactionGroups = replyReactions[ri]
|
|
142
|
-
try { if (was) await removeReaction(reply.id, content); else await addReaction(reply.id, content) } catch {}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function submitReply() {
|
|
146
|
-
const t = replyText.trim(); if (!t) return; submittingReply = true
|
|
147
|
-
try {
|
|
148
|
-
await replyToComment(discussion.id, comment.id, t)
|
|
149
|
-
replyText = ''
|
|
150
|
-
clearDraft(draftKey)
|
|
151
|
-
onMove?.()
|
|
152
|
-
} catch (err) { console.error('[storyboard] Reply failed:', err) } finally { submittingReply = false }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function saveReply(ri: number) {
|
|
156
|
-
const t = editReplyText.trim(); if (!t) return
|
|
157
|
-
const reply = replies[ri]; if (!reply) return; savingReply = true
|
|
158
|
-
try { await editReply(reply.id, t); reply.text = t; reply.body = t; replyTexts[ri] = t; replyTexts = [...replyTexts]; editingReply = -1 } catch (err) { console.error('[storyboard] Edit reply failed:', err) } finally { savingReply = false }
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function deleteReplyAt(ri: number) {
|
|
162
|
-
const reply = replies[ri]; if (!reply || !confirm('Delete this reply?')) return
|
|
163
|
-
try { await deleteComment(reply.id); onMove?.() } catch (err) { console.error('[storyboard] Delete failed:', err) }
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function startDrag(e: MouseEvent) {
|
|
167
|
-
const target = e.target as HTMLElement
|
|
168
|
-
if (target.closest('[data-no-drag]') || !winEl) return
|
|
169
|
-
const startX = e.clientX, startY = e.clientY, rect = winEl.getBoundingClientRect()
|
|
170
|
-
const sx = rect.left, sy = rect.top; target.style.cursor = 'grabbing'
|
|
171
|
-
const mv = (ev: MouseEvent) => { winEl!.style.position = 'fixed'; winEl!.style.left = `${sx + ev.clientX - startX}px`; winEl!.style.top = `${sy + ev.clientY - startY}px`; winEl!.style.transform = 'none' }
|
|
172
|
-
const up = () => { target.style.cursor = 'grab'; document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', up) }
|
|
173
|
-
document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up); e.preventDefault()
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function handleReplyKeydown(e: KeyboardEvent) {
|
|
177
|
-
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); submitReply() }
|
|
178
|
-
}
|
|
179
|
-
</script>
|
|
180
|
-
|
|
181
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
182
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
183
|
-
<div class="font-sans" onclick={(e) => e.stopPropagation()}>
|
|
184
|
-
<!-- Header -->
|
|
185
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
186
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
187
|
-
<div class="flex items-center justify-between px-3 py-3 border-b border-border cursor-grab select-none" onmousedown={startDrag}>
|
|
188
|
-
<div class="flex items-center gap-2">
|
|
189
|
-
{#if comment.author?.avatarUrl}
|
|
190
|
-
<Avatar.Root class="h-6 w-6">
|
|
191
|
-
<Avatar.Image src={comment.author.avatarUrl} alt={comment.author?.login} />
|
|
192
|
-
<Avatar.Fallback class="text-[10px]">{(comment.author?.login ?? '?')[0]?.toUpperCase()}</Avatar.Fallback>
|
|
193
|
-
</Avatar.Root>
|
|
194
|
-
{/if}
|
|
195
|
-
<div class="flex flex-col">
|
|
196
|
-
<span class="text-xs font-semibold">{comment.author?.login ?? 'unknown'}</span>
|
|
197
|
-
{#if comment.createdAt}
|
|
198
|
-
<span class="text-[11px] text-muted-foreground leading-tight">{timeAgo(comment.createdAt)}</span>
|
|
199
|
-
{/if}
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
203
|
-
<div class="flex items-center shrink-0 gap-1.5" data-no-drag onmousedown={(e) => e.stopPropagation()}>
|
|
204
|
-
<Button variant="ghost" size="sm" class={cn('h-6 px-2 text-[11px]', resolved ? 'text-success' : 'text-muted-foreground')} disabled={resolving} onclick={toggleResolve}>
|
|
205
|
-
{resolving ? (resolved ? 'Unresolving…' : 'Resolving…') : (resolved ? 'Resolved ✓' : 'Resolve')}
|
|
206
|
-
</Button>
|
|
207
|
-
<Button variant="ghost" size="sm" class={cn('h-6 px-2 text-[11px]', copied ? 'text-success' : 'text-muted-foreground')} onclick={copyLink}>
|
|
208
|
-
{copied ? 'Copied!' : 'Copy link'}
|
|
209
|
-
</Button>
|
|
210
|
-
<Button variant="ghost" size="icon" class="h-6 w-6 text-muted-foreground" aria-label="Close" onclick={onClose}>×</Button>
|
|
211
|
-
</div>
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
<!-- Body -->
|
|
215
|
-
<div class="flex-auto overflow-y-auto px-3 pt-3 no-scrollbar">
|
|
216
|
-
{#if !editing}
|
|
217
|
-
<p class="text-sm leading-relaxed m-0 mb-2 break-words">{commentText}</p>
|
|
218
|
-
{:else}
|
|
219
|
-
<div class="mb-2">
|
|
220
|
-
<Textarea class="min-h-[40px] max-h-[100px] text-xs mb-2" bind:value={editText} />
|
|
221
|
-
<div class="flex justify-end gap-1">
|
|
222
|
-
<Button variant="outline" size="sm" class="h-6 text-xs border border-input text-foreground" onclick={() => editing = false}>Cancel</Button>
|
|
223
|
-
<Button size="sm" class="h-6 text-xs" disabled={saving} onclick={saveEdit}>{saving ? 'Saving…' : 'Save'}</Button>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
{/if}
|
|
227
|
-
|
|
228
|
-
<!-- Reactions -->
|
|
229
|
-
<div class="flex items-center flex-wrap gap-1 mb-2">
|
|
230
|
-
{#each reactions as group, gi (group.content)}
|
|
231
|
-
{#if (group.users?.totalCount ?? 0) > 0}
|
|
232
|
-
<button class={pillClass(group.viewerHasReacted)}
|
|
233
|
-
onclick={(e) => { e.stopPropagation(); toggleReaction(group.content, gi) }}>
|
|
234
|
-
<span class="mr-1">{emojiFor(group.content)}</span><span>{group.users?.totalCount ?? 0}</span>
|
|
235
|
-
</button>
|
|
236
|
-
{/if}
|
|
237
|
-
{/each}
|
|
238
|
-
<div class="relative">
|
|
239
|
-
<button class="inline-flex items-center text-xs px-2 py-0.5 rounded-full border border-border bg-transparent text-muted-foreground cursor-pointer hover:border-primary"
|
|
240
|
-
onclick={(e) => { e.stopPropagation(); pickerTarget = pickerTarget === 'comment' ? null : 'comment' }}>😀 +</button>
|
|
241
|
-
{#if pickerTarget === 'comment'}
|
|
242
|
-
<div class="absolute bottom-full left-0 mb-1 flex gap-0.5 p-1 bg-popover border border-border rounded-lg shadow-lg z-10">
|
|
243
|
-
{#each emojiEntries as [key, emoji] (key)}
|
|
244
|
-
<button class={emojiPickerBtnClass(isReacted(key))}
|
|
245
|
-
onclick={(e) => { e.stopPropagation(); toggleReaction(key); pickerTarget = null }}>{emoji}</button>
|
|
246
|
-
{/each}
|
|
247
|
-
</div>
|
|
248
|
-
{/if}
|
|
249
|
-
</div>
|
|
250
|
-
{#if !editing && canEdit}
|
|
251
|
-
<button class="ml-auto text-xs text-muted-foreground bg-transparent border-none cursor-pointer hover:underline" onclick={() => { editing = true; editText = commentText }}>Edit</button>
|
|
252
|
-
{/if}
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
<!-- Replies -->
|
|
256
|
-
{#if replies.length > 0}
|
|
257
|
-
<Separator class="my-2" />
|
|
258
|
-
<div class="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider mb-2">{replies.length} {replies.length === 1 ? 'Reply' : 'Replies'}</div>
|
|
259
|
-
{#each replies as reply, ri (reply.id ?? ri)}
|
|
260
|
-
<div class="flex mb-2 gap-2">
|
|
261
|
-
{#if reply.author?.avatarUrl}
|
|
262
|
-
<Avatar.Root class="h-5 w-5 shrink-0">
|
|
263
|
-
<Avatar.Image src={reply.author.avatarUrl} alt={reply.author?.login} />
|
|
264
|
-
<Avatar.Fallback class="text-[10px]">{(reply.author?.login ?? '?')[0]?.toUpperCase()}</Avatar.Fallback>
|
|
265
|
-
</Avatar.Root>
|
|
266
|
-
{/if}
|
|
267
|
-
<div class="flex-auto min-w-0">
|
|
268
|
-
<div class="flex items-start justify-between mb-0.5">
|
|
269
|
-
<div class="flex flex-col">
|
|
270
|
-
<span class="text-xs font-semibold">{reply.author?.login ?? 'unknown'}</span>
|
|
271
|
-
{#if reply.createdAt}<span class="text-[11px] text-muted-foreground leading-tight">{timeAgo(reply.createdAt)}</span>{/if}
|
|
272
|
-
</div>
|
|
273
|
-
{#if user && reply.author?.login === user.login}
|
|
274
|
-
<div class="flex gap-2 ml-auto shrink-0">
|
|
275
|
-
{#if editingReply !== ri}
|
|
276
|
-
<button class="text-[11px] text-muted-foreground bg-transparent border-none cursor-pointer hover:underline" onclick={() => { editingReply = ri; editReplyText = replyTexts[ri] }}>Edit</button>
|
|
277
|
-
<button class="text-[11px] text-destructive bg-transparent border-none cursor-pointer hover:underline" onclick={() => deleteReplyAt(ri)}>Delete</button>
|
|
278
|
-
{/if}
|
|
279
|
-
</div>
|
|
280
|
-
{/if}
|
|
281
|
-
</div>
|
|
282
|
-
{#if editingReply !== ri}
|
|
283
|
-
<p class="text-sm leading-relaxed m-0 break-words">{replyTexts[ri] ?? reply.text ?? reply.body}</p>
|
|
284
|
-
{:else}
|
|
285
|
-
<div>
|
|
286
|
-
<Textarea class="min-h-[40px] max-h-[100px] text-xs mb-1" bind:value={editReplyText} />
|
|
287
|
-
<div class="flex justify-end gap-1">
|
|
288
|
-
<Button variant="outline" size="sm" class="h-6 text-xs border border-input text-foreground" onclick={() => editingReply = -1}>Cancel</Button>
|
|
289
|
-
<Button size="sm" class="h-6 text-xs" disabled={savingReply} onclick={() => saveReply(ri)}>{savingReply ? 'Saving…' : 'Save'}</Button>
|
|
290
|
-
</div>
|
|
291
|
-
</div>
|
|
292
|
-
{/if}
|
|
293
|
-
<!-- Reply reactions -->
|
|
294
|
-
<div class="flex items-center flex-wrap gap-1 mt-1">
|
|
295
|
-
{#each (replyReactions[ri] ?? []) as rg, rgi (rg.content)}
|
|
296
|
-
{#if (rg.users?.totalCount ?? 0) > 0}
|
|
297
|
-
<button class={pillClass(rg.viewerHasReacted)}
|
|
298
|
-
onclick={(e) => { e.stopPropagation(); toggleReplyReaction(ri, rg.content, rgi) }}>
|
|
299
|
-
<span class="mr-1">{emojiFor(rg.content)}</span><span>{rg.users?.totalCount ?? 0}</span>
|
|
300
|
-
</button>
|
|
301
|
-
{/if}
|
|
302
|
-
{/each}
|
|
303
|
-
<div class="relative">
|
|
304
|
-
<button class="inline-flex items-center text-xs px-2 py-0.5 rounded-full border border-border bg-transparent text-muted-foreground cursor-pointer"
|
|
305
|
-
onclick={(e) => { e.stopPropagation(); pickerTarget = pickerTarget === `reply-${ri}` ? null : `reply-${ri}` }}>😀 +</button>
|
|
306
|
-
{#if pickerTarget === `reply-${ri}`}
|
|
307
|
-
<div class="absolute bottom-full left-0 mb-1 flex gap-0.5 p-1 bg-popover border border-border rounded-lg shadow-lg z-10">
|
|
308
|
-
{#each emojiEntries as [key, emoji] (key)}
|
|
309
|
-
<button class={emojiPickerBtnClass(isReplyReacted(ri, key))}
|
|
310
|
-
onclick={(e) => { e.stopPropagation(); toggleReplyReaction(ri, key); pickerTarget = null }}>{emoji}</button>
|
|
311
|
-
{/each}
|
|
312
|
-
</div>
|
|
313
|
-
{/if}
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
{/each}
|
|
319
|
-
{/if}
|
|
320
|
-
</div>
|
|
321
|
-
|
|
322
|
-
<!-- Reply form -->
|
|
323
|
-
{#if canReply}
|
|
324
|
-
<div class="border-t border-border px-3 py-3 flex flex-col">
|
|
325
|
-
<Textarea class="min-h-[40px] max-h-[100px] text-xs mb-1" placeholder="Reply…" bind:value={replyText} onkeydown={handleReplyKeydown} onblur={handleReplyBlur} />
|
|
326
|
-
<div class="flex justify-end mt-1">
|
|
327
|
-
<Button size="sm" class="text-xs" disabled={!replyText.trim() || submittingReply} onclick={submitReply}>
|
|
328
|
-
{submittingReply ? 'Posting…' : 'Reply'}
|
|
329
|
-
</Button>
|
|
330
|
-
</div>
|
|
331
|
-
</div>
|
|
332
|
-
{/if}
|
|
333
|
-
</div>
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
CommentsDrawer — right-side panel listing all comments across all routes.
|
|
3
|
-
Uses shadcn Avatar, Badge, Button.
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
|
-
<script lang="ts">
|
|
7
|
-
import { onMount } from 'svelte'
|
|
8
|
-
import { listDiscussions, fetchRouteDiscussion } from '../api.js'
|
|
9
|
-
import { getCommentsConfig } from '../config.js'
|
|
10
|
-
import * as Avatar from '../../lib/components/ui/avatar/index.js'
|
|
11
|
-
import { Badge } from '../../lib/components/ui/badge/index.js'
|
|
12
|
-
import { Button } from '../../lib/components/ui/button/index.js'
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
onClose?: () => void
|
|
16
|
-
onNavigate?: (route: string, commentId: string) => void
|
|
17
|
-
}
|
|
18
|
-
let { onClose, onNavigate }: Props = $props()
|
|
19
|
-
|
|
20
|
-
interface CommentGroup { route: string; comments: any[] }
|
|
21
|
-
|
|
22
|
-
let loading = $state(true)
|
|
23
|
-
let error: string | null = $state(null)
|
|
24
|
-
let groups: CommentGroup[] = $state([])
|
|
25
|
-
|
|
26
|
-
function timeAgo(dateStr: string) {
|
|
27
|
-
return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
onMount(async () => {
|
|
31
|
-
try {
|
|
32
|
-
const discussions = await listDiscussions()
|
|
33
|
-
if (!discussions || discussions.length === 0) { loading = false; return }
|
|
34
|
-
const basePath = getCommentsConfig()?.basePath ?? '/'
|
|
35
|
-
const result: CommentGroup[] = []
|
|
36
|
-
for (const disc of discussions) {
|
|
37
|
-
const routeMatch = disc.title?.match(/^Comments:\s*(.+)$/)
|
|
38
|
-
if (!routeMatch) continue
|
|
39
|
-
const route = routeMatch[1]
|
|
40
|
-
if (!route.startsWith(basePath)) continue
|
|
41
|
-
let discussion: any
|
|
42
|
-
try { discussion = await fetchRouteDiscussion(route) } catch { continue }
|
|
43
|
-
if (!discussion?.comments?.length) continue
|
|
44
|
-
result.push({ route, comments: discussion.comments })
|
|
45
|
-
}
|
|
46
|
-
groups = result
|
|
47
|
-
} catch (err: any) { error = err.message } finally { loading = false }
|
|
48
|
-
})
|
|
49
|
-
</script>
|
|
50
|
-
|
|
51
|
-
<div class="flex items-center justify-between px-4 py-3 border-b border-border shrink-0">
|
|
52
|
-
<h2 class="text-base font-semibold">All Comments</h2>
|
|
53
|
-
<Button variant="ghost" size="icon" class="h-7 w-7 text-muted-foreground" aria-label="Close" onclick={onClose}>×</Button>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<div class="flex-auto overflow-y-auto">
|
|
57
|
-
{#if loading}
|
|
58
|
-
<div class="py-8 px-4 text-center text-sm text-muted-foreground">Loading comments…</div>
|
|
59
|
-
{:else if error}
|
|
60
|
-
<div class="py-8 px-4 text-center text-sm text-muted-foreground">Failed to load comments: {error}</div>
|
|
61
|
-
{:else if groups.length === 0}
|
|
62
|
-
<div class="py-8 px-4 text-center text-sm text-muted-foreground">No comments yet</div>
|
|
63
|
-
{:else}
|
|
64
|
-
{#each groups as group (group.route)}
|
|
65
|
-
<div class="border-b border-border">
|
|
66
|
-
<div class="flex items-center px-4 py-2 bg-muted/50 text-xs font-semibold text-muted-foreground">
|
|
67
|
-
<code class="text-primary">{group.route}</code>
|
|
68
|
-
<span class="ml-auto font-normal whitespace-nowrap">{group.comments.length} {group.comments.length !== 1 ? 'comments' : 'comment'}</span>
|
|
69
|
-
</div>
|
|
70
|
-
{#each group.comments as comment (comment.id)}
|
|
71
|
-
<button class="flex px-4 py-2 cursor-pointer border-none bg-transparent w-full text-left font-sans hover:bg-accent/50 transition-colors"
|
|
72
|
-
class:opacity-60={comment.meta?.resolved}
|
|
73
|
-
onclick={() => onNavigate?.(group.route, comment.id)}>
|
|
74
|
-
{#if comment.author?.avatarUrl}
|
|
75
|
-
<Avatar.Root class="h-6 w-6 shrink-0 mr-2">
|
|
76
|
-
<Avatar.Image src={comment.author.avatarUrl} alt={comment.author?.login ?? ''} />
|
|
77
|
-
<Avatar.Fallback class="text-[10px]">{(comment.author?.login ?? '?')[0]?.toUpperCase()}</Avatar.Fallback>
|
|
78
|
-
</Avatar.Root>
|
|
79
|
-
{/if}
|
|
80
|
-
<div class="flex flex-col flex-auto min-w-0 gap-0.5">
|
|
81
|
-
<div class="flex items-center gap-1">
|
|
82
|
-
<span class="text-xs font-semibold" class:text-foreground={!comment.meta?.resolved} class:text-muted-foreground={comment.meta?.resolved}>{comment.author?.login ?? 'unknown'}</span>
|
|
83
|
-
{#if comment.createdAt}<span class="text-[11px] text-muted-foreground">{timeAgo(comment.createdAt)}</span>{/if}
|
|
84
|
-
{#if comment.meta?.resolved}<Badge variant="outline" class="text-success text-[10px] px-1 py-0">Resolved</Badge>{/if}
|
|
85
|
-
</div>
|
|
86
|
-
<p class="text-sm leading-snug overflow-hidden whitespace-nowrap text-ellipsis m-0" class:text-foreground={!comment.meta?.resolved} class:text-muted-foreground={comment.meta?.resolved}>{(comment.text ?? '').slice(0, 100)}</p>
|
|
87
|
-
{#if (comment.replies?.length ?? 0) > 0}
|
|
88
|
-
<div class="text-[11px] text-muted-foreground mt-0.5">💬 {comment.replies.length} {comment.replies.length === 1 ? 'reply' : 'replies'}</div>
|
|
89
|
-
{/if}
|
|
90
|
-
</div>
|
|
91
|
-
</button>
|
|
92
|
-
{/each}
|
|
93
|
-
</div>
|
|
94
|
-
{/each}
|
|
95
|
-
{/if}
|
|
96
|
-
</div>
|