@flanksource/clicky-ui 0.3.25 → 0.3.27
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/NOTICE.md +1 -0
- package/dist/ai.cjs +4 -0
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.js +6 -2
- package/dist/ai.js.map +1 -1
- package/dist/chat.cjs +8 -0
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.js +9 -1
- package/dist/chat.js.map +1 -1
- package/dist/comments/CommentCard.cjs +89 -14
- package/dist/comments/CommentCard.cjs.map +1 -1
- package/dist/comments/CommentCard.d.ts.map +1 -1
- package/dist/comments/CommentCard.js +90 -15
- package/dist/comments/CommentCard.js.map +1 -1
- package/dist/comments/CommentComposer.cjs +25 -2
- package/dist/comments/CommentComposer.cjs.map +1 -1
- package/dist/comments/CommentComposer.d.ts.map +1 -1
- package/dist/comments/CommentComposer.js +25 -2
- package/dist/comments/CommentComposer.js.map +1 -1
- package/dist/comments/CommentProvider.cjs +21 -14
- package/dist/comments/CommentProvider.cjs.map +1 -1
- package/dist/comments/CommentProvider.d.ts +1 -1
- package/dist/comments/CommentProvider.d.ts.map +1 -1
- package/dist/comments/CommentProvider.js +21 -14
- package/dist/comments/CommentProvider.js.map +1 -1
- package/dist/comments/CommentSidePanel.cjs +1 -1
- package/dist/comments/CommentSidePanel.cjs.map +1 -1
- package/dist/comments/CommentSidePanel.js +1 -1
- package/dist/comments/CommentSidePanel.js.map +1 -1
- package/dist/comments/comment-types.cjs.map +1 -1
- package/dist/comments/comment-types.d.ts +6 -0
- package/dist/comments/comment-types.d.ts.map +1 -1
- package/dist/comments/comment-types.js.map +1 -1
- package/dist/components/DimensionField.cjs +56 -0
- package/dist/components/DimensionField.cjs.map +1 -0
- package/dist/components/DimensionField.d.ts +17 -0
- package/dist/components/DimensionField.d.ts.map +1 -0
- package/dist/components/DimensionField.js +56 -0
- package/dist/components/DimensionField.js.map +1 -0
- package/dist/components.cjs +4 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +2 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +4 -0
- package/dist/components.js.map +1 -1
- package/dist/data/Clicky.cjs +4 -5
- package/dist/data/Clicky.cjs.map +1 -1
- package/dist/data/Clicky.d.ts.map +1 -1
- package/dist/data/Clicky.js +4 -5
- package/dist/data/Clicky.js.map +1 -1
- package/dist/data/FixtureEditor/fixture-frontmatter.cjs +4 -0
- package/dist/data/FixtureEditor/fixture-frontmatter.cjs.map +1 -1
- package/dist/data/FixtureEditor/fixture-frontmatter.d.ts.map +1 -1
- package/dist/data/FixtureEditor/fixture-frontmatter.js +4 -0
- package/dist/data/FixtureEditor/fixture-frontmatter.js.map +1 -1
- package/dist/data/TaskExecDetails.cjs +8 -3
- package/dist/data/TaskExecDetails.cjs.map +1 -1
- package/dist/data/TaskExecDetails.d.ts +4 -0
- package/dist/data/TaskExecDetails.d.ts.map +1 -1
- package/dist/data/TaskExecDetails.js +9 -4
- package/dist/data/TaskExecDetails.js.map +1 -1
- package/dist/data/TaskManagerButton.cjs +101 -7
- package/dist/data/TaskManagerButton.cjs.map +1 -1
- package/dist/data/TaskManagerButton.d.ts +6 -1
- package/dist/data/TaskManagerButton.d.ts.map +1 -1
- package/dist/data/TaskManagerButton.js +103 -9
- package/dist/data/TaskManagerButton.js.map +1 -1
- package/dist/data/TaskProcessDetails.cjs +2 -0
- package/dist/data/TaskProcessDetails.cjs.map +1 -1
- package/dist/data/TaskProcessDetails.d.ts.map +1 -1
- package/dist/data/TaskProcessDetails.js +2 -0
- package/dist/data/TaskProcessDetails.js.map +1 -1
- package/dist/data/TaskProgress.cjs +14 -4
- package/dist/data/TaskProgress.cjs.map +1 -1
- package/dist/data/TaskProgress.js +14 -4
- package/dist/data/TaskProgress.js.map +1 -1
- package/dist/data/TaskSnapshot.cjs.map +1 -1
- package/dist/data/TaskSnapshot.d.ts +1 -0
- package/dist/data/TaskSnapshot.d.ts.map +1 -1
- package/dist/data/TaskSnapshot.js.map +1 -1
- package/dist/data/ai/ChatWindow.cjs +18 -23
- package/dist/data/ai/ChatWindow.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.d.ts +1 -1
- package/dist/data/ai/ChatWindow.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.js +19 -24
- package/dist/data/ai/ChatWindow.js.map +1 -1
- package/dist/data/ai/ChatWindow.preferences.cjs +12 -21
- package/dist/data/ai/ChatWindow.preferences.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.preferences.d.ts +3 -2
- package/dist/data/ai/ChatWindow.preferences.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.preferences.js +12 -21
- package/dist/data/ai/ChatWindow.preferences.js.map +1 -1
- package/dist/data/ai/ChatWindow.tool-catalog.cjs +21 -38
- package/dist/data/ai/ChatWindow.tool-catalog.cjs.map +1 -1
- package/dist/data/ai/ChatWindow.tool-catalog.d.ts +5 -5
- package/dist/data/ai/ChatWindow.tool-catalog.d.ts.map +1 -1
- package/dist/data/ai/ChatWindow.tool-catalog.js +21 -38
- package/dist/data/ai/ChatWindow.tool-catalog.js.map +1 -1
- package/dist/data/ai/ChatWindow.types.d.ts +2 -2
- package/dist/data/ai/ChatWindow.types.d.ts.map +1 -1
- package/dist/data/ai/ChatWindowManager.cjs +2 -2
- package/dist/data/ai/ChatWindowManager.cjs.map +1 -1
- package/dist/data/ai/ChatWindowManager.js +2 -2
- package/dist/data/ai/ChatWindowManager.js.map +1 -1
- package/dist/data/ai/ChatWindowRequestBody.cjs +4 -2
- package/dist/data/ai/ChatWindowRequestBody.cjs.map +1 -1
- package/dist/data/ai/ChatWindowRequestBody.d.ts +15 -3
- package/dist/data/ai/ChatWindowRequestBody.d.ts.map +1 -1
- package/dist/data/ai/ChatWindowRequestBody.js +4 -2
- package/dist/data/ai/ChatWindowRequestBody.js.map +1 -1
- package/dist/data/ai/PromptRunEditor/index.cjs +2 -3
- package/dist/data/ai/PromptRunEditor/index.cjs.map +1 -1
- package/dist/data/ai/PromptRunEditor/index.d.ts.map +1 -1
- package/dist/data/ai/PromptRunEditor/index.js +2 -3
- package/dist/data/ai/PromptRunEditor/index.js.map +1 -1
- package/dist/data/ai/PromptRunEditor/model.cjs +1 -0
- package/dist/data/ai/PromptRunEditor/model.cjs.map +1 -1
- package/dist/data/ai/PromptRunEditor/model.d.ts +1 -1
- package/dist/data/ai/PromptRunEditor/model.d.ts.map +1 -1
- package/dist/data/ai/PromptRunEditor/model.js +1 -0
- package/dist/data/ai/PromptRunEditor/model.js.map +1 -1
- package/dist/data/ai/RuntimeModePicker.cjs +11 -9
- package/dist/data/ai/RuntimeModePicker.cjs.map +1 -1
- package/dist/data/ai/RuntimeModePicker.d.ts.map +1 -1
- package/dist/data/ai/RuntimeModePicker.js +12 -10
- package/dist/data/ai/RuntimeModePicker.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/ModelSection.cjs +5 -0
- package/dist/data/ai/SpecRuntimeEditor/ModelSection.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/ModelSection.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/ModelSection.js +5 -0
- package/dist/data/ai/SpecRuntimeEditor/ModelSection.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.cjs +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.d.ts +2 -0
- package/dist/data/ai/SpecRuntimeEditor.model.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.js +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.js.map +1 -1
- package/dist/data/ai/ToolPreferences.cjs +5 -5
- package/dist/data/ai/ToolPreferences.cjs.map +1 -1
- package/dist/data/ai/ToolPreferences.d.ts +6 -5
- package/dist/data/ai/ToolPreferences.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferences.js +5 -5
- package/dist/data/ai/ToolPreferences.js.map +1 -1
- package/dist/data/ai/ToolPreferences.model.cjs +61 -37
- package/dist/data/ai/ToolPreferences.model.cjs.map +1 -1
- package/dist/data/ai/ToolPreferences.model.d.ts +32 -13
- package/dist/data/ai/ToolPreferences.model.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferences.model.js +62 -38
- package/dist/data/ai/ToolPreferences.model.js.map +1 -1
- package/dist/data/ai/ToolPreferencesList.cjs +17 -20
- package/dist/data/ai/ToolPreferencesList.cjs.map +1 -1
- package/dist/data/ai/ToolPreferencesList.d.ts +16 -7
- package/dist/data/ai/ToolPreferencesList.d.ts.map +1 -1
- package/dist/data/ai/ToolPreferencesList.js +18 -21
- package/dist/data/ai/ToolPreferencesList.js.map +1 -1
- package/dist/data/ai/chat-window-context.cjs +1 -1
- package/dist/data/ai/chat-window-context.cjs.map +1 -1
- package/dist/data/ai/chat-window-context.d.ts +7 -7
- package/dist/data/ai/chat-window-context.d.ts.map +1 -1
- package/dist/data/ai/chat-window-context.js +1 -1
- package/dist/data/ai/chat-window-context.js.map +1 -1
- package/dist/data/ai/index.d.ts +3 -1
- package/dist/data/ai/index.d.ts.map +1 -1
- package/dist/data/chat/Chat.cjs +4 -2
- package/dist/data/chat/Chat.cjs.map +1 -1
- package/dist/data/chat/Chat.d.ts.map +1 -1
- package/dist/data/chat/Chat.js +4 -2
- package/dist/data/chat/Chat.js.map +1 -1
- package/dist/data/chat/Chat.runtime.cjs +14 -3
- package/dist/data/chat/Chat.runtime.cjs.map +1 -1
- package/dist/data/chat/Chat.runtime.d.ts.map +1 -1
- package/dist/data/chat/Chat.runtime.js +15 -4
- package/dist/data/chat/Chat.runtime.js.map +1 -1
- package/dist/data/chat/clickyOperationsToTools.cjs +2 -1
- package/dist/data/chat/clickyOperationsToTools.cjs.map +1 -1
- package/dist/data/chat/clickyOperationsToTools.d.ts.map +1 -1
- package/dist/data/chat/clickyOperationsToTools.js +2 -1
- package/dist/data/chat/clickyOperationsToTools.js.map +1 -1
- package/dist/data/chat/index.d.ts +2 -1
- package/dist/data/chat/index.d.ts.map +1 -1
- package/dist/data/chat/tool-policy.cjs +132 -0
- package/dist/data/chat/tool-policy.cjs.map +1 -0
- package/dist/data/chat/tool-policy.d.ts +81 -0
- package/dist/data/chat/tool-policy.d.ts.map +1 -0
- package/dist/data/chat/tool-policy.js +132 -0
- package/dist/data/chat/tool-policy.js.map +1 -0
- package/dist/data/chat/types.cjs +20 -0
- package/dist/data/chat/types.cjs.map +1 -1
- package/dist/data/chat/types.d.ts +22 -2
- package/dist/data/chat/types.d.ts.map +1 -1
- package/dist/data/chat/types.js +20 -0
- package/dist/data/chat/types.js.map +1 -1
- package/dist/data/debugConsoleSignal.cjs +64 -0
- package/dist/data/debugConsoleSignal.cjs.map +1 -0
- package/dist/data/debugConsoleSignal.d.ts +83 -0
- package/dist/data/debugConsoleSignal.d.ts.map +1 -0
- package/dist/data/debugConsoleSignal.js +64 -0
- package/dist/data/debugConsoleSignal.js.map +1 -0
- package/dist/data/har/HarPanel.cjs +20 -14
- package/dist/data/har/HarPanel.cjs.map +1 -1
- package/dist/data/har/HarPanel.d.ts.map +1 -1
- package/dist/data/har/HarPanel.js +20 -14
- package/dist/data/har/HarPanel.js.map +1 -1
- package/dist/data/har/types.d.ts +18 -0
- package/dist/data/har/types.d.ts.map +1 -1
- package/dist/data/query-browser/QueryBrowser.cjs +4 -42
- package/dist/data/query-browser/QueryBrowser.cjs.map +1 -1
- package/dist/data/query-browser/QueryBrowser.d.ts.map +1 -1
- package/dist/data/query-browser/QueryBrowser.js +5 -43
- package/dist/data/query-browser/QueryBrowser.js.map +1 -1
- package/dist/data/query-browser/QueryBrowser.types.cjs.map +1 -1
- package/dist/data/query-browser/QueryBrowser.types.d.ts +0 -1
- package/dist/data/query-browser/QueryBrowser.types.d.ts.map +1 -1
- package/dist/data/query-browser/QueryBrowser.types.js.map +1 -1
- package/dist/data/runtime/RuntimeBar.cjs +26 -41
- package/dist/data/runtime/RuntimeBar.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBar.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBar.js +28 -43
- package/dist/data/runtime/RuntimeBar.js.map +1 -1
- package/dist/data/runtime/RuntimeBar.model.cjs +65 -0
- package/dist/data/runtime/RuntimeBar.model.cjs.map +1 -1
- package/dist/data/runtime/RuntimeBar.model.d.ts +11 -3
- package/dist/data/runtime/RuntimeBar.model.d.ts.map +1 -1
- package/dist/data/runtime/RuntimeBar.model.js +65 -0
- package/dist/data/runtime/RuntimeBar.model.js.map +1 -1
- package/dist/data/runtime/model-capabilities.cjs +8 -5
- package/dist/data/runtime/model-capabilities.cjs.map +1 -1
- package/dist/data/runtime/model-capabilities.d.ts +6 -1
- package/dist/data/runtime/model-capabilities.d.ts.map +1 -1
- package/dist/data/runtime/model-capabilities.js +8 -5
- package/dist/data/runtime/model-capabilities.js.map +1 -1
- package/dist/data/runtime/runtime-mode.cjs +17 -0
- package/dist/data/runtime/runtime-mode.cjs.map +1 -1
- package/dist/data/runtime/runtime-mode.d.ts +6 -0
- package/dist/data/runtime/runtime-mode.d.ts.map +1 -1
- package/dist/data/runtime/runtime-mode.js +17 -0
- package/dist/data/runtime/runtime-mode.js.map +1 -1
- package/dist/data/useDebugAction.cjs +29 -0
- package/dist/data/useDebugAction.cjs.map +1 -0
- package/dist/data/useDebugAction.d.ts +16 -0
- package/dist/data/useDebugAction.d.ts.map +1 -0
- package/dist/data/useDebugAction.js +29 -0
- package/dist/data/useDebugAction.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +16 -8
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +3 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +16 -8
- package/dist/devtools/DebugConsole.cjs +138 -0
- package/dist/devtools/DebugConsole.cjs.map +1 -0
- package/dist/devtools/DebugConsole.d.ts +23 -0
- package/dist/devtools/DebugConsole.d.ts.map +1 -0
- package/dist/devtools/DebugConsole.js +138 -0
- package/dist/devtools/DebugConsole.js.map +1 -0
- package/dist/devtools/DebugConsoleButton.cjs +59 -0
- package/dist/devtools/DebugConsoleButton.cjs.map +1 -0
- package/dist/devtools/DebugConsoleButton.d.ts +20 -0
- package/dist/devtools/DebugConsoleButton.d.ts.map +1 -0
- package/dist/devtools/DebugConsoleButton.js +59 -0
- package/dist/devtools/DebugConsoleButton.js.map +1 -0
- package/dist/devtools/DebugConsoleDock.cjs +112 -0
- package/dist/devtools/DebugConsoleDock.cjs.map +1 -0
- package/dist/devtools/DebugConsoleDock.d.ts +26 -0
- package/dist/devtools/DebugConsoleDock.d.ts.map +1 -0
- package/dist/devtools/DebugConsoleDock.js +112 -0
- package/dist/devtools/DebugConsoleDock.js.map +1 -0
- package/dist/devtools/ExecutionDetail.cjs +135 -0
- package/dist/devtools/ExecutionDetail.cjs.map +1 -0
- package/dist/devtools/ExecutionDetail.d.ts +19 -0
- package/dist/devtools/ExecutionDetail.d.ts.map +1 -0
- package/dist/devtools/ExecutionDetail.js +135 -0
- package/dist/devtools/ExecutionDetail.js.map +1 -0
- package/dist/devtools/debugClient.cjs +172 -0
- package/dist/devtools/debugClient.cjs.map +1 -0
- package/dist/devtools/debugClient.d.ts +98 -0
- package/dist/devtools/debugClient.d.ts.map +1 -0
- package/dist/devtools/debugClient.js +172 -0
- package/dist/devtools/debugClient.js.map +1 -0
- package/dist/devtools/debugStore.cjs +115 -0
- package/dist/devtools/debugStore.cjs.map +1 -0
- package/dist/devtools/debugStore.d.ts +83 -0
- package/dist/devtools/debugStore.d.ts.map +1 -0
- package/dist/devtools/debugStore.js +115 -0
- package/dist/devtools/debugStore.js.map +1 -0
- package/dist/devtools/dockState.cjs +51 -0
- package/dist/devtools/dockState.cjs.map +1 -0
- package/dist/devtools/dockState.d.ts +31 -0
- package/dist/devtools/dockState.d.ts.map +1 -0
- package/dist/devtools/dockState.js +51 -0
- package/dist/devtools/dockState.js.map +1 -0
- package/dist/devtools/tabs/ConsoleTab.cjs +36 -0
- package/dist/devtools/tabs/ConsoleTab.cjs.map +1 -0
- package/dist/devtools/tabs/ConsoleTab.d.ts +19 -0
- package/dist/devtools/tabs/ConsoleTab.d.ts.map +1 -0
- package/dist/devtools/tabs/ConsoleTab.js +36 -0
- package/dist/devtools/tabs/ConsoleTab.js.map +1 -0
- package/dist/devtools/tabs/InspectionCaches.cjs +150 -0
- package/dist/devtools/tabs/InspectionCaches.cjs.map +1 -0
- package/dist/devtools/tabs/InspectionCaches.d.ts +23 -0
- package/dist/devtools/tabs/InspectionCaches.d.ts.map +1 -0
- package/dist/devtools/tabs/InspectionCaches.js +150 -0
- package/dist/devtools/tabs/InspectionCaches.js.map +1 -0
- package/dist/devtools/tabs/InspectionTab.cjs +316 -0
- package/dist/devtools/tabs/InspectionTab.cjs.map +1 -0
- package/dist/devtools/tabs/InspectionTab.d.ts +27 -0
- package/dist/devtools/tabs/InspectionTab.d.ts.map +1 -0
- package/dist/devtools/tabs/InspectionTab.js +316 -0
- package/dist/devtools/tabs/InspectionTab.js.map +1 -0
- package/dist/devtools/tabs/NetworkTab.cjs +91 -0
- package/dist/devtools/tabs/NetworkTab.cjs.map +1 -0
- package/dist/devtools/tabs/NetworkTab.d.ts +20 -0
- package/dist/devtools/tabs/NetworkTab.d.ts.map +1 -0
- package/dist/devtools/tabs/NetworkTab.js +91 -0
- package/dist/devtools/tabs/NetworkTab.js.map +1 -0
- package/dist/devtools/tabs/QueriesTab.cjs +85 -0
- package/dist/devtools/tabs/QueriesTab.cjs.map +1 -0
- package/dist/devtools/tabs/QueriesTab.d.ts +24 -0
- package/dist/devtools/tabs/QueriesTab.d.ts.map +1 -0
- package/dist/devtools/tabs/QueriesTab.js +85 -0
- package/dist/devtools/tabs/QueriesTab.js.map +1 -0
- package/dist/devtools/types.cjs +26 -0
- package/dist/devtools/types.cjs.map +1 -0
- package/dist/devtools/types.d.ts +235 -0
- package/dist/devtools/types.d.ts.map +1 -0
- package/dist/devtools/types.js +26 -0
- package/dist/devtools/types.js.map +1 -0
- package/dist/devtools/useDebugStream.cjs +46 -0
- package/dist/devtools/useDebugStream.cjs.map +1 -0
- package/dist/devtools/useDebugStream.d.ts +23 -0
- package/dist/devtools/useDebugStream.d.ts.map +1 -0
- package/dist/devtools/useDebugStream.js +46 -0
- package/dist/devtools/useDebugStream.js.map +1 -0
- package/dist/devtools.cjs +51 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.ts +37 -0
- package/dist/devtools.d.ts.map +1 -0
- package/dist/devtools.js +51 -0
- package/dist/devtools.js.map +1 -0
- package/dist/icons/components/UiOpenScad.cjs +10 -0
- package/dist/icons/components/UiOpenScad.cjs.map +1 -0
- package/dist/icons/components/UiOpenScad.d.ts +9 -0
- package/dist/icons/components/UiOpenScad.d.ts.map +1 -0
- package/dist/icons/components/UiOpenScad.js +10 -0
- package/dist/icons/components/UiOpenScad.js.map +1 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/icons/registry.cjs +2 -0
- package/dist/icons/registry.cjs.map +1 -1
- package/dist/icons/registry.d.ts.map +1 -1
- package/dist/icons/registry.js +2 -0
- package/dist/icons/registry.js.map +1 -1
- package/dist/icons.cjs +2 -0
- package/dist/icons.cjs.map +1 -1
- package/dist/icons.js +2 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.cjs +20 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/dist/layout/AppShell.cjs +15 -10
- package/dist/layout/AppShell.cjs.map +1 -1
- package/dist/layout/AppShell.d.ts +11 -0
- package/dist/layout/AppShell.d.ts.map +1 -1
- package/dist/layout/AppShell.js +15 -10
- package/dist/layout/AppShell.js.map +1 -1
- package/dist/layout/AppShell.nav.cjs +170 -49
- package/dist/layout/AppShell.nav.cjs.map +1 -1
- package/dist/layout/AppShell.nav.d.ts.map +1 -1
- package/dist/layout/AppShell.nav.js +170 -49
- package/dist/layout/AppShell.nav.js.map +1 -1
- package/dist/overlay/CellActions.cjs +18 -124
- package/dist/overlay/CellActions.cjs.map +1 -1
- package/dist/overlay/CellActions.d.ts +1 -1
- package/dist/overlay/CellActions.d.ts.map +1 -1
- package/dist/overlay/CellActions.js +18 -124
- package/dist/overlay/CellActions.js.map +1 -1
- package/dist/overlay/ContextMenu.cjs +144 -0
- package/dist/overlay/ContextMenu.cjs.map +1 -0
- package/dist/overlay/ContextMenu.d.ts +8 -0
- package/dist/overlay/ContextMenu.d.ts.map +1 -0
- package/dist/overlay/ContextMenu.js +144 -0
- package/dist/overlay/ContextMenu.js.map +1 -0
- package/dist/profiles/connections/connectionBrowserModel.cjs +14 -65
- package/dist/profiles/connections/connectionBrowserModel.cjs.map +1 -1
- package/dist/profiles/connections/connectionBrowserModel.d.ts +35 -35
- package/dist/profiles/connections/connectionBrowserModel.d.ts.map +1 -1
- package/dist/profiles/connections/connectionBrowserModel.js +14 -65
- package/dist/profiles/connections/connectionBrowserModel.js.map +1 -1
- package/dist/profiles/connections/connectionQueryWorkspace.cjs +29 -9
- package/dist/profiles/connections/connectionQueryWorkspace.cjs.map +1 -1
- package/dist/profiles/connections/connectionQueryWorkspace.d.ts +3 -2
- package/dist/profiles/connections/connectionQueryWorkspace.d.ts.map +1 -1
- package/dist/profiles/connections/connectionQueryWorkspace.js +30 -10
- package/dist/profiles/connections/connectionQueryWorkspace.js.map +1 -1
- package/dist/profiles/connections/connectionQueryWorkspaceModel.cjs +15 -0
- package/dist/profiles/connections/connectionQueryWorkspaceModel.cjs.map +1 -1
- package/dist/profiles/connections/connectionQueryWorkspaceModel.d.ts +22 -1
- package/dist/profiles/connections/connectionQueryWorkspaceModel.d.ts.map +1 -1
- package/dist/profiles/connections/connectionQueryWorkspaceModel.js +15 -0
- package/dist/profiles/connections/connectionQueryWorkspaceModel.js.map +1 -1
- package/dist/profiles/connections/index.d.ts +1 -0
- package/dist/profiles/connections/index.d.ts.map +1 -1
- package/dist/profiles/connections/inspectionStatus.cjs +44 -0
- package/dist/profiles/connections/inspectionStatus.cjs.map +1 -0
- package/dist/profiles/connections/inspectionStatus.d.ts +5 -0
- package/dist/profiles/connections/inspectionStatus.d.ts.map +1 -0
- package/dist/profiles/connections/inspectionStatus.js +44 -0
- package/dist/profiles/connections/inspectionStatus.js.map +1 -0
- package/dist/profiles/connections/useInspection.cjs +123 -0
- package/dist/profiles/connections/useInspection.cjs.map +1 -0
- package/dist/profiles/connections/useInspection.d.ts +35 -0
- package/dist/profiles/connections/useInspection.d.ts.map +1 -0
- package/dist/profiles/connections/useInspection.js +123 -0
- package/dist/profiles/connections/useInspection.js.map +1 -0
- package/dist/profiles/editor/profileBuilderWorkspace.cjs +55 -23
- package/dist/profiles/editor/profileBuilderWorkspace.cjs.map +1 -1
- package/dist/profiles/editor/profileBuilderWorkspace.d.ts.map +1 -1
- package/dist/profiles/editor/profileBuilderWorkspace.js +56 -24
- package/dist/profiles/editor/profileBuilderWorkspace.js.map +1 -1
- package/dist/profiles/elasticsearch/esQueryBuilder.cjs +12 -3
- package/dist/profiles/elasticsearch/esQueryBuilder.cjs.map +1 -1
- package/dist/profiles/elasticsearch/esQueryBuilder.d.ts +2 -2
- package/dist/profiles/elasticsearch/esQueryBuilder.d.ts.map +1 -1
- package/dist/profiles/elasticsearch/esQueryBuilder.js +12 -3
- package/dist/profiles/elasticsearch/esQueryBuilder.js.map +1 -1
- package/dist/profiles/fields/profileFieldEditor.cjs +137 -22
- package/dist/profiles/fields/profileFieldEditor.cjs.map +1 -1
- package/dist/profiles/fields/profileFieldEditor.d.ts.map +1 -1
- package/dist/profiles/fields/profileFieldEditor.js +137 -22
- package/dist/profiles/fields/profileFieldEditor.js.map +1 -1
- package/dist/profiles/fields/profileFieldFilterEditor.cjs +171 -82
- package/dist/profiles/fields/profileFieldFilterEditor.cjs.map +1 -1
- package/dist/profiles/fields/profileFieldFilterEditor.d.ts +0 -9
- package/dist/profiles/fields/profileFieldFilterEditor.d.ts.map +1 -1
- package/dist/profiles/fields/profileFieldFilterEditor.js +174 -85
- package/dist/profiles/fields/profileFieldFilterEditor.js.map +1 -1
- package/dist/profiles/fields/profileFieldIcons.cjs +6 -3
- package/dist/profiles/fields/profileFieldIcons.cjs.map +1 -1
- package/dist/profiles/fields/profileFieldIcons.js +6 -3
- package/dist/profiles/fields/profileFieldIcons.js.map +1 -1
- package/dist/profiles/query/profileSampleFilterLookup.cjs +34 -0
- package/dist/profiles/query/profileSampleFilterLookup.cjs.map +1 -0
- package/dist/profiles/query/profileSampleFilterLookup.d.ts +12 -0
- package/dist/profiles/query/profileSampleFilterLookup.d.ts.map +1 -0
- package/dist/profiles/query/profileSampleFilterLookup.js +34 -0
- package/dist/profiles/query/profileSampleFilterLookup.js.map +1 -0
- package/dist/profiles/query/profileSamplePayload.cjs +6 -4
- package/dist/profiles/query/profileSamplePayload.cjs.map +1 -1
- package/dist/profiles/query/profileSamplePayload.d.ts +11 -4
- package/dist/profiles/query/profileSamplePayload.d.ts.map +1 -1
- package/dist/profiles/query/profileSamplePayload.js +6 -4
- package/dist/profiles/query/profileSamplePayload.js.map +1 -1
- package/dist/profiles/query/profileSampleResult.cjs +26 -0
- package/dist/profiles/query/profileSampleResult.cjs.map +1 -0
- package/dist/profiles/query/profileSampleResult.d.ts +11 -0
- package/dist/profiles/query/profileSampleResult.d.ts.map +1 -0
- package/dist/profiles/query/profileSampleResult.js +26 -0
- package/dist/profiles/query/profileSampleResult.js.map +1 -0
- package/dist/profiles/query/queryTargetPicker.cjs +5 -3
- package/dist/profiles/query/queryTargetPicker.cjs.map +1 -1
- package/dist/profiles/query/queryTargetPicker.d.ts +3 -2
- package/dist/profiles/query/queryTargetPicker.d.ts.map +1 -1
- package/dist/profiles/query/queryTargetPicker.js +5 -3
- package/dist/profiles/query/queryTargetPicker.js.map +1 -1
- package/dist/profiles/wizard/profileWizard.cjs +14 -5
- package/dist/profiles/wizard/profileWizard.cjs.map +1 -1
- package/dist/profiles/wizard/profileWizard.d.ts.map +1 -1
- package/dist/profiles/wizard/profileWizard.js +15 -6
- package/dist/profiles/wizard/profileWizard.js.map +1 -1
- package/dist/profiles/wizard/profileWizardModel.cjs +35 -6
- package/dist/profiles/wizard/profileWizardModel.cjs.map +1 -1
- package/dist/profiles/wizard/profileWizardModel.d.ts +29 -4
- package/dist/profiles/wizard/profileWizardModel.d.ts.map +1 -1
- package/dist/profiles/wizard/profileWizardModel.js +35 -6
- package/dist/profiles/wizard/profileWizardModel.js.map +1 -1
- package/dist/profiles/wizard/profileWizardQueryStep.cjs +44 -11
- package/dist/profiles/wizard/profileWizardQueryStep.cjs.map +1 -1
- package/dist/profiles/wizard/profileWizardQueryStep.d.ts.map +1 -1
- package/dist/profiles/wizard/profileWizardQueryStep.js +46 -13
- package/dist/profiles/wizard/profileWizardQueryStep.js.map +1 -1
- package/dist/profiles/wizard/profileWizardSteps.cjs +33 -2
- package/dist/profiles/wizard/profileWizardSteps.cjs.map +1 -1
- package/dist/profiles/wizard/profileWizardSteps.d.ts +11 -2
- package/dist/profiles/wizard/profileWizardSteps.d.ts.map +1 -1
- package/dist/profiles/wizard/profileWizardSteps.js +33 -2
- package/dist/profiles/wizard/profileWizardSteps.js.map +1 -1
- package/dist/profiles.cjs +8 -2
- package/dist/profiles.cjs.map +1 -1
- package/dist/profiles.js +10 -4
- package/dist/profiles.js.map +1 -1
- package/dist/rpc/FilterForm.cjs +21 -237
- package/dist/rpc/FilterForm.cjs.map +1 -1
- package/dist/rpc/FilterForm.d.ts +3 -1
- package/dist/rpc/FilterForm.d.ts.map +1 -1
- package/dist/rpc/FilterForm.js +21 -237
- package/dist/rpc/FilterForm.js.map +1 -1
- package/dist/rpc/FilterFormFields.cjs +240 -0
- package/dist/rpc/FilterFormFields.cjs.map +1 -0
- package/dist/rpc/FilterFormFields.d.ts +12 -0
- package/dist/rpc/FilterFormFields.d.ts.map +1 -0
- package/dist/rpc/FilterFormFields.js +240 -0
- package/dist/rpc/FilterFormFields.js.map +1 -0
- package/dist/rpc/router.cjs +3 -2
- package/dist/rpc/router.cjs.map +1 -1
- package/dist/rpc/router.d.ts.map +1 -1
- package/dist/rpc/router.js +3 -2
- package/dist/rpc/router.js.map +1 -1
- package/dist/rpc/types.cjs.map +1 -1
- package/dist/rpc/types.d.ts +3 -1
- package/dist/rpc/types.d.ts.map +1 -1
- package/dist/rpc/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -1
- package/dist/data/query-info/QueryInfoDialog.cjs +0 -106
- package/dist/data/query-info/QueryInfoDialog.cjs.map +0 -1
- package/dist/data/query-info/QueryInfoDialog.d.ts +0 -16
- package/dist/data/query-info/QueryInfoDialog.d.ts.map +0 -1
- package/dist/data/query-info/QueryInfoDialog.js +0 -106
- package/dist/data/query-info/QueryInfoDialog.js.map +0 -1
- package/dist/data/query-info/index.d.ts +0 -4
- package/dist/data/query-info/index.d.ts.map +0 -1
- package/dist/data/query-info/queryInfo.cjs +0 -42
- package/dist/data/query-info/queryInfo.cjs.map +0 -1
- package/dist/data/query-info/queryInfo.d.ts +0 -45
- package/dist/data/query-info/queryInfo.d.ts.map +0 -1
- package/dist/data/query-info/queryInfo.js +0 -42
- package/dist/data/query-info/queryInfo.js.map +0 -1
- package/dist/data/query-info/useQueryInfo.cjs +0 -48
- package/dist/data/query-info/useQueryInfo.cjs.map +0 -1
- package/dist/data/query-info/useQueryInfo.d.ts +0 -26
- package/dist/data/query-info/useQueryInfo.d.ts.map +0 -1
- package/dist/data/query-info/useQueryInfo.js +0 -48
- package/dist/data/query-info/useQueryInfo.js.map +0 -1
|
@@ -8,12 +8,40 @@ import { DropdownMenu } from "../overlay/DropdownMenu.js";
|
|
|
8
8
|
import { parseTimestamp, formatRelativeTime } from "../data/cells/timestamp-format.js";
|
|
9
9
|
import { CommentAuthorAvatar } from "./CommentAuthor.js";
|
|
10
10
|
import { CommentMarkdown } from "./CommentMarkdown.js";
|
|
11
|
-
import { authorDisplayName, truncatePlain, resolveFacetOption, toneToBadgeTone, resolveStatusConfig } from "./comment-utils.js";
|
|
11
|
+
import { isUnresolved, authorDisplayName, truncatePlain, resolveFacetOption, toneToBadgeTone, resolveStatusConfig } from "./comment-utils.js";
|
|
12
|
+
import { UiCheck } from "../icons/components/UiCheck.js";
|
|
12
13
|
import { UiFullscreen } from "../icons/components/UiFullscreen.js";
|
|
13
14
|
import { UiDotsVertical } from "../icons/components/UiDotsVertical.js";
|
|
14
|
-
import {
|
|
15
|
+
import { UiThumbsUp } from "../icons/components/UiThumbsUp.js";
|
|
16
|
+
import { UiThumbsDown } from "../icons/components/UiThumbsDown.js";
|
|
15
17
|
import { UiClose } from "../icons/components/UiClose.js";
|
|
16
18
|
import { UiTrash } from "../icons/components/UiTrash.js";
|
|
19
|
+
function completionStatus(config) {
|
|
20
|
+
for (const completionName of ["resolved", "closed"]) {
|
|
21
|
+
const status = config.statuses.find(
|
|
22
|
+
(candidate) => candidate.value.toLowerCase() === completionName || candidate.label.toLowerCase() === completionName
|
|
23
|
+
);
|
|
24
|
+
if (status) return status;
|
|
25
|
+
}
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
function completionActionLabel(status) {
|
|
29
|
+
return status.value.toLowerCase() === "closed" || status.label.toLowerCase() === "closed" ? "Close comment" : "Resolve comment";
|
|
30
|
+
}
|
|
31
|
+
function RatingChip({ rating }) {
|
|
32
|
+
if (!rating) return null;
|
|
33
|
+
const positive = rating === "positive";
|
|
34
|
+
return /* @__PURE__ */ jsx(
|
|
35
|
+
Badge,
|
|
36
|
+
{
|
|
37
|
+
variant: "soft",
|
|
38
|
+
tone: positive ? "success" : "danger",
|
|
39
|
+
size: "xs",
|
|
40
|
+
icon: positive ? UiThumbsUp : UiThumbsDown,
|
|
41
|
+
children: positive ? "Positive rating" : "Negative rating"
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
17
45
|
function StatusChip({
|
|
18
46
|
status,
|
|
19
47
|
config,
|
|
@@ -114,7 +142,7 @@ function statusMenuItems(comment, config, isReply, opts) {
|
|
|
114
142
|
}
|
|
115
143
|
if (!isReply && opts.onUpdateStatus) {
|
|
116
144
|
for (const status of config.statuses) {
|
|
117
|
-
if (status.value === opts.
|
|
145
|
+
if (status.value === opts.completionStatus) continue;
|
|
118
146
|
const current = status.value === comment.status;
|
|
119
147
|
items.push({
|
|
120
148
|
label: /* @__PURE__ */ jsxs(
|
|
@@ -177,10 +205,8 @@ function CommentBody({
|
|
|
177
205
|
const [statusError, setStatusError] = useState("");
|
|
178
206
|
const isReply = Boolean(comment.parentId);
|
|
179
207
|
const date = parseTimestamp(comment.createdAt);
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
);
|
|
183
|
-
const canResolve = !isReply && onUpdateStatus != null && resolvedStatus != null && comment.status !== resolvedStatus.value;
|
|
208
|
+
const completion = completionStatus(config);
|
|
209
|
+
const canComplete = !isReply && onUpdateStatus != null && completion != null && isUnresolved(config, comment.status);
|
|
184
210
|
const updateStatus = async (status) => {
|
|
185
211
|
setStatusError("");
|
|
186
212
|
try {
|
|
@@ -196,7 +222,7 @@ function CommentBody({
|
|
|
196
222
|
...onDelete ? { onDelete } : {},
|
|
197
223
|
...onCollapse ? { onCollapse } : {},
|
|
198
224
|
...onMaximize ? { onMaximize } : {},
|
|
199
|
-
...
|
|
225
|
+
...completion ? { completionStatus: completion.value } : {}
|
|
200
226
|
});
|
|
201
227
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
|
|
202
228
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
|
|
@@ -213,15 +239,18 @@ function CommentBody({
|
|
|
213
239
|
}
|
|
214
240
|
)
|
|
215
241
|
] }),
|
|
216
|
-
!isReply && /* @__PURE__ */
|
|
242
|
+
!isReply && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap gap-1", children: [
|
|
243
|
+
/* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
|
|
244
|
+
/* @__PURE__ */ jsx(FacetBadges, { comment, config })
|
|
245
|
+
] })
|
|
217
246
|
] }),
|
|
218
247
|
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
219
|
-
|
|
248
|
+
canComplete && /* @__PURE__ */ jsx(
|
|
220
249
|
IconAction,
|
|
221
250
|
{
|
|
222
251
|
icon: UiCheck,
|
|
223
|
-
label:
|
|
224
|
-
onClick: () => void updateStatus(
|
|
252
|
+
label: completionActionLabel(completion),
|
|
253
|
+
onClick: () => void updateStatus(completion.value),
|
|
225
254
|
className: "hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300"
|
|
226
255
|
}
|
|
227
256
|
),
|
|
@@ -245,7 +274,7 @@ function CommentBody({
|
|
|
245
274
|
)
|
|
246
275
|
] })
|
|
247
276
|
] }),
|
|
248
|
-
/* @__PURE__ */ jsx("div", { className: "text-[13px] leading-5 text-foreground", children: renderBody ? renderBody(comment.body) : /* @__PURE__ */ jsx(CommentMarkdown, { text: comment.body }) }),
|
|
277
|
+
comment.body.trim() && /* @__PURE__ */ jsx("div", { className: "text-[13px] leading-5 text-foreground", children: renderBody ? renderBody(comment.body) : /* @__PURE__ */ jsx(CommentMarkdown, { text: comment.body }) }),
|
|
249
278
|
statusError && /* @__PURE__ */ jsxs(
|
|
250
279
|
"div",
|
|
251
280
|
{
|
|
@@ -280,7 +309,22 @@ function CommentCard(props) {
|
|
|
280
309
|
const { comment, config, compact, defaultExpanded } = props;
|
|
281
310
|
const [expanded, setExpanded] = useState(defaultExpanded ?? false);
|
|
282
311
|
const [maximized, setMaximized] = useState(false);
|
|
312
|
+
const [collapsedStatusError, setCollapsedStatusError] = useState("");
|
|
283
313
|
const isReply = Boolean(comment.parentId);
|
|
314
|
+
const completion = completionStatus(config);
|
|
315
|
+
const canComplete = !isReply && props.onUpdateStatus != null && completion != null && isUnresolved(config, comment.status);
|
|
316
|
+
const completeCollapsed = async () => {
|
|
317
|
+
var _a;
|
|
318
|
+
if (!completion) return;
|
|
319
|
+
setCollapsedStatusError("");
|
|
320
|
+
try {
|
|
321
|
+
await ((_a = props.onUpdateStatus) == null ? void 0 : _a.call(props, completion.value));
|
|
322
|
+
} catch (error) {
|
|
323
|
+
setCollapsedStatusError(
|
|
324
|
+
error instanceof Error ? error.message : "Unexpected error"
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
284
328
|
const body = /* @__PURE__ */ jsx(
|
|
285
329
|
CommentBody,
|
|
286
330
|
{
|
|
@@ -324,8 +368,28 @@ function CommentCard(props) {
|
|
|
324
368
|
children: [
|
|
325
369
|
/* @__PURE__ */ jsx(CommentAuthorAvatar, { author: comment.author, bare: true }),
|
|
326
370
|
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] font-semibold text-foreground", children: authorDisplayName(comment.author) }),
|
|
327
|
-
!isReply && /* @__PURE__ */
|
|
328
|
-
|
|
371
|
+
!isReply && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
372
|
+
/* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
|
|
373
|
+
/* @__PURE__ */ jsx(FacetBadges, { comment, config, compact: true })
|
|
374
|
+
] }),
|
|
375
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[12px] text-foreground/80", children: comment.body.trim() ? truncatePlain(comment.body) : comment.rating === "positive" ? "Positive rating" : comment.rating === "negative" ? "Negative rating" : "Empty comment" }),
|
|
376
|
+
canComplete && /* @__PURE__ */ jsx(
|
|
377
|
+
"span",
|
|
378
|
+
{
|
|
379
|
+
role: "presentation",
|
|
380
|
+
onClick: (e) => e.stopPropagation(),
|
|
381
|
+
onKeyDown: (e) => e.stopPropagation(),
|
|
382
|
+
children: /* @__PURE__ */ jsx(
|
|
383
|
+
IconAction,
|
|
384
|
+
{
|
|
385
|
+
icon: UiCheck,
|
|
386
|
+
label: completionActionLabel(completion),
|
|
387
|
+
onClick: () => void completeCollapsed(),
|
|
388
|
+
className: "hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300"
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
),
|
|
329
393
|
/* @__PURE__ */ jsx(
|
|
330
394
|
"span",
|
|
331
395
|
{
|
|
@@ -361,6 +425,17 @@ function CommentCard(props) {
|
|
|
361
425
|
]
|
|
362
426
|
}
|
|
363
427
|
),
|
|
428
|
+
collapsedStatusError && /* @__PURE__ */ jsxs(
|
|
429
|
+
"div",
|
|
430
|
+
{
|
|
431
|
+
role: "alert",
|
|
432
|
+
className: "rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive",
|
|
433
|
+
children: [
|
|
434
|
+
"Couldn't update comment: ",
|
|
435
|
+
collapsedStatusError
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
),
|
|
364
439
|
modal
|
|
365
440
|
] });
|
|
366
441
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentCard.js","sources":["../../src/comments/CommentCard.tsx"],"sourcesContent":["import { useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Badge } from \"../data/Badge\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { Modal } from \"../overlay/Modal\";\nimport { DropdownMenu, type DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport {\n parseTimestamp,\n formatRelativeTime,\n} from \"../data/cells/timestamp-format\";\nimport {\n UiCheck,\n UiClose,\n UiDotsVertical,\n UiFullscreen,\n UiTrash,\n} from \"../icons\";\nimport { CommentAuthorAvatar } from \"./CommentAuthor\";\nimport { CommentMarkdown } from \"./CommentMarkdown\";\nimport {\n authorDisplayName,\n resolveFacetOption,\n resolveStatusConfig,\n toneToBadgeTone,\n truncatePlain,\n} from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentCardProps = {\n comment: Comment;\n config: CommentConfig;\n /** Tighter padding for dense rails. */\n compact?: boolean;\n /** Render expanded on first mount. */\n defaultExpanded?: boolean;\n /** Custom body renderer; defaults to the lightweight CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n /** Change this comment's status (roots only). */\n onUpdateStatus?: (status: string) => void | Promise<void>;\n /** Delete this comment. */\n onDelete?: () => void;\n /** Begin a reply to this comment. */\n onReply?: () => void;\n /** Advance the checklist item at `index` to its next status. */\n onChecklistToggle?: (index: number) => void;\n};\n\nfunction StatusChip({\n status,\n config,\n showLabel,\n}: {\n status: string | undefined;\n config: CommentConfig;\n showLabel?: boolean;\n}) {\n const cfg = resolveStatusConfig(config, status);\n if (!cfg) return null;\n return (\n <Badge\n variant=\"soft\"\n tone={toneToBadgeTone(cfg.tone)}\n size=\"xs\"\n {...(cfg.icon ? { icon: cfg.icon } : {})}\n >\n {showLabel ? cfg.label : null}\n </Badge>\n );\n}\n\nfunction FacetBadges({\n comment,\n config,\n compact,\n}: {\n comment: Comment;\n config: CommentConfig;\n compact?: boolean;\n}) {\n const facets = config.facets ?? [];\n const chips = facets.flatMap((facet) => {\n const value = comment.facets?.[facet.key];\n const option = resolveFacetOption(facet, value);\n if (!option) return [];\n return [\n <Badge\n key={facet.key}\n variant=\"soft\"\n tone={toneToBadgeTone(option.tone)}\n size=\"xs\"\n >\n {compact ? (option.short ?? option.label) : option.label}\n </Badge>,\n ];\n });\n return chips.length > 0 ? <>{chips}</> : null;\n}\n\nfunction ChecklistChips({\n comment,\n onChecklistToggle,\n}: {\n comment: Comment;\n onChecklistToggle?: (index: number) => void;\n}) {\n if (!comment.checklist || comment.checklist.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {comment.checklist.map((item, i) => {\n const done = /done|complete|resolved/i.test(item.status);\n const active = /progress|doing/i.test(item.status);\n return (\n <button\n key={i}\n type=\"button\"\n onClick={() => onChecklistToggle?.(i)}\n disabled={!onChecklistToggle}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] transition-opacity\",\n onChecklistToggle && \"cursor-pointer hover:opacity-80\",\n done\n ? \"bg-green-100 text-green-800 line-through dark:bg-green-500/20 dark:text-green-300\"\n : active\n ? \"bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-300\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n\nfunction RefChips({ comment }: { comment: Comment }) {\n if (!comment.refs || comment.refs.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {comment.refs.map((group, i) => (\n <span\n key={i}\n className=\"inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground\"\n >\n <span className=\"font-medium\">{group.label}:</span>\n <span className={group.mono ? \"font-mono\" : undefined}>\n {group.items.join(\", \")}\n </span>\n </span>\n ))}\n </div>\n );\n}\n\nfunction statusMenuItems(\n comment: Comment,\n config: CommentConfig,\n isReply: boolean,\n opts: {\n onUpdateStatus?: (status: string) => void;\n onDelete?: () => void;\n onCollapse?: () => void;\n onMaximize?: () => void;\n resolvedStatus?: string;\n },\n): DropdownMenuItem[] {\n const items: DropdownMenuItem[] = [];\n if (opts.onMaximize) {\n items.push({\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: opts.onMaximize,\n });\n }\n if (opts.onCollapse) {\n items.push({ label: \"Collapse\", icon: UiClose, onSelect: opts.onCollapse });\n }\n if (!isReply && opts.onUpdateStatus) {\n for (const status of config.statuses) {\n if (status.value === opts.resolvedStatus) continue;\n const current = status.value === comment.status;\n items.push({\n label: (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5\",\n current && \"font-semibold\",\n )}\n >\n <StatusChip status={status.value} config={config} />\n {status.label}\n </span>\n ),\n onSelect: () => opts.onUpdateStatus?.(status.value),\n });\n }\n }\n if (opts.onDelete) {\n items.push({ label: \"Delete\", icon: UiTrash, onSelect: opts.onDelete });\n }\n return items;\n}\n\nfunction IconAction({\n icon,\n label,\n onClick,\n className,\n}: {\n icon: StaticIconComponent;\n label: string;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n \"rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n className,\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n );\n}\n\nfunction CommentBody({\n comment,\n config,\n renderBody,\n onUpdateStatus,\n onDelete,\n onReply,\n onChecklistToggle,\n onCollapse,\n onMaximize,\n}: CommentCardProps & { onCollapse?: () => void; onMaximize?: () => void }) {\n const [statusError, setStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const date = parseTimestamp(comment.createdAt);\n const resolvedStatus = config.statuses.find(\n (status) =>\n status.value.toLowerCase() === \"resolved\" ||\n status.label.toLowerCase() === \"resolved\",\n );\n const canResolve =\n !isReply &&\n onUpdateStatus != null &&\n resolvedStatus != null &&\n comment.status !== resolvedStatus.value;\n const updateStatus = async (status: string) => {\n setStatusError(\"\");\n try {\n await onUpdateStatus?.(status);\n } catch (error) {\n setStatusError(\n error instanceof Error ? error.message : \"Unexpected error\",\n );\n }\n };\n const menuItems = statusMenuItems(comment, config, isReply, {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status) => void updateStatus(status) }\n : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onCollapse ? { onCollapse } : {}),\n ...(onMaximize ? { onMaximize } : {}),\n ...(resolvedStatus ? { resolvedStatus: resolvedStatus.value } : {}),\n });\n\n return (\n <div className=\"space-y-2.5\">\n <div className=\"flex items-start gap-2.5\">\n <CommentAuthorAvatar author={comment.author} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"truncate text-xs font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {date && (\n <span\n title={date.toLocaleString()}\n className=\"shrink-0 text-[10px] text-muted-foreground\"\n >\n {formatRelativeTime(date)}\n </span>\n )}\n </div>\n {!isReply && (\n <div className=\"mt-1 flex flex-wrap gap-1\">\n <FacetBadges comment={comment} config={config} />\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {canResolve && (\n <IconAction\n icon={UiCheck}\n label=\"Resolve comment\"\n onClick={() => void updateStatus(resolvedStatus.value)}\n className=\"hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300\"\n />\n )}\n {menuItems.length > 0 && (\n <DropdownMenu\n align=\"right\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={menuItems}\n />\n )}\n </div>\n </div>\n <div className=\"text-[13px] leading-5 text-foreground\">\n {renderBody ? (\n renderBody(comment.body)\n ) : (\n <CommentMarkdown text={comment.body} />\n )}\n </div>\n {statusError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n Couldn't update comment: {statusError}\n </div>\n )}\n {!isReply && (\n <ChecklistChips\n comment={comment}\n {...(onChecklistToggle ? { onChecklistToggle } : {})}\n />\n )}\n {!isReply && <RefChips comment={comment} />}\n {onReply && (\n <button\n type=\"button\"\n onClick={onReply}\n className=\"-ml-2 rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10\"\n >\n Reply\n </button>\n )}\n </div>\n );\n}\n\n/**\n * A single comment, collapsible between a compact one-line preview and a full\n * card. The full view can be maximized into a modal. Fully controlled — all\n * mutations are delegated to the supplied callbacks.\n */\nexport function CommentCard(props: CommentCardProps) {\n const { comment, config, compact, defaultExpanded } = props;\n const [expanded, setExpanded] = useState(defaultExpanded ?? false);\n const [maximized, setMaximized] = useState(false);\n const isReply = Boolean(comment.parentId);\n\n const body = (\n <CommentBody\n {...props}\n onMaximize={() => setMaximized(true)}\n {...(expanded ? { onCollapse: () => setExpanded(false) } : {})}\n />\n );\n\n const modal = (\n <Modal\n open={maximized}\n onClose={() => setMaximized(false)}\n title=\"Comment\"\n size=\"lg\"\n >\n <CommentBody {...props} />\n </Modal>\n );\n\n if (!expanded) {\n return (\n <>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpanded(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setExpanded(true);\n }\n }}\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs transition-colors hover:bg-muted/50\",\n !isReply && \"border border-border bg-background shadow-sm\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/30\",\n )}\n >\n <CommentAuthorAvatar author={comment.author} bare />\n <span className=\"shrink-0 text-[11px] font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {!isReply && (\n <FacetBadges comment={comment} config={config} compact />\n )}\n <span className=\"min-w-0 flex-1 truncate text-[12px] text-foreground/80\">\n {truncatePlain(comment.body)}\n </span>\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <DropdownMenu\n align=\"right\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:opacity-100 group-hover/comment:opacity-70\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={[\n { label: \"Expand\", onSelect: () => setExpanded(true) },\n {\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: () => setMaximized(true),\n },\n ]}\n />\n </span>\n </div>\n {modal}\n </>\n );\n }\n\n return (\n <>\n <div\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment rounded-xl bg-background px-3.5 py-3 text-xs\",\n !isReply &&\n \"border border-border/80 shadow-sm transition-shadow hover:shadow-md\",\n compact && \"px-2.5 py-2.5\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/20 shadow-none\",\n )}\n >\n {body}\n </div>\n {modal}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentCard.js","sources":["../../src/comments/CommentCard.tsx"],"sourcesContent":["import { useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Badge } from \"../data/Badge\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { Modal } from \"../overlay/Modal\";\nimport { DropdownMenu, type DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport {\n parseTimestamp,\n formatRelativeTime,\n} from \"../data/cells/timestamp-format\";\nimport {\n UiCheck,\n UiClose,\n UiDotsVertical,\n UiFullscreen,\n UiThumbsDown,\n UiThumbsUp,\n UiTrash,\n} from \"../icons\";\nimport { CommentAuthorAvatar } from \"./CommentAuthor\";\nimport { CommentMarkdown } from \"./CommentMarkdown\";\nimport {\n authorDisplayName,\n isUnresolved,\n resolveFacetOption,\n resolveStatusConfig,\n toneToBadgeTone,\n truncatePlain,\n} from \"./comment-utils\";\nimport type {\n Comment,\n CommentConfig,\n CommentRating,\n CommentStatusConfig,\n} from \"./comment-types\";\n\nfunction completionStatus(config: CommentConfig) {\n for (const completionName of [\"resolved\", \"closed\"]) {\n const status = config.statuses.find(\n (candidate) =>\n candidate.value.toLowerCase() === completionName ||\n candidate.label.toLowerCase() === completionName,\n );\n if (status) return status;\n }\n return undefined;\n}\n\nfunction completionActionLabel(status: CommentStatusConfig) {\n return status.value.toLowerCase() === \"closed\" ||\n status.label.toLowerCase() === \"closed\"\n ? \"Close comment\"\n : \"Resolve comment\";\n}\n\nexport type CommentCardProps = {\n comment: Comment;\n config: CommentConfig;\n /** Tighter padding for dense rails. */\n compact?: boolean;\n /** Render expanded on first mount. */\n defaultExpanded?: boolean;\n /** Custom body renderer; defaults to the lightweight CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n /** Change this comment's status (roots only). */\n onUpdateStatus?: (status: string) => void | Promise<void>;\n /** Delete this comment. */\n onDelete?: () => void;\n /** Begin a reply to this comment. */\n onReply?: () => void;\n /** Advance the checklist item at `index` to its next status. */\n onChecklistToggle?: (index: number) => void;\n};\n\nfunction RatingChip({ rating }: { rating: CommentRating | undefined }) {\n if (!rating) return null;\n const positive = rating === \"positive\";\n return (\n <Badge\n variant=\"soft\"\n tone={positive ? \"success\" : \"danger\"}\n size=\"xs\"\n icon={positive ? UiThumbsUp : UiThumbsDown}\n >\n {positive ? \"Positive rating\" : \"Negative rating\"}\n </Badge>\n );\n}\n\nfunction StatusChip({\n status,\n config,\n showLabel,\n}: {\n status: string | undefined;\n config: CommentConfig;\n showLabel?: boolean;\n}) {\n const cfg = resolveStatusConfig(config, status);\n if (!cfg) return null;\n return (\n <Badge\n variant=\"soft\"\n tone={toneToBadgeTone(cfg.tone)}\n size=\"xs\"\n {...(cfg.icon ? { icon: cfg.icon } : {})}\n >\n {showLabel ? cfg.label : null}\n </Badge>\n );\n}\n\nfunction FacetBadges({\n comment,\n config,\n compact,\n}: {\n comment: Comment;\n config: CommentConfig;\n compact?: boolean;\n}) {\n const facets = config.facets ?? [];\n const chips = facets.flatMap((facet) => {\n const value = comment.facets?.[facet.key];\n const option = resolveFacetOption(facet, value);\n if (!option) return [];\n return [\n <Badge\n key={facet.key}\n variant=\"soft\"\n tone={toneToBadgeTone(option.tone)}\n size=\"xs\"\n >\n {compact ? (option.short ?? option.label) : option.label}\n </Badge>,\n ];\n });\n return chips.length > 0 ? <>{chips}</> : null;\n}\n\nfunction ChecklistChips({\n comment,\n onChecklistToggle,\n}: {\n comment: Comment;\n onChecklistToggle?: (index: number) => void;\n}) {\n if (!comment.checklist || comment.checklist.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {comment.checklist.map((item, i) => {\n const done = /done|complete|resolved/i.test(item.status);\n const active = /progress|doing/i.test(item.status);\n return (\n <button\n key={i}\n type=\"button\"\n onClick={() => onChecklistToggle?.(i)}\n disabled={!onChecklistToggle}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] transition-opacity\",\n onChecklistToggle && \"cursor-pointer hover:opacity-80\",\n done\n ? \"bg-green-100 text-green-800 line-through dark:bg-green-500/20 dark:text-green-300\"\n : active\n ? \"bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-300\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n\nfunction RefChips({ comment }: { comment: Comment }) {\n if (!comment.refs || comment.refs.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {comment.refs.map((group, i) => (\n <span\n key={i}\n className=\"inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground\"\n >\n <span className=\"font-medium\">{group.label}:</span>\n <span className={group.mono ? \"font-mono\" : undefined}>\n {group.items.join(\", \")}\n </span>\n </span>\n ))}\n </div>\n );\n}\n\nfunction statusMenuItems(\n comment: Comment,\n config: CommentConfig,\n isReply: boolean,\n opts: {\n onUpdateStatus?: (status: string) => void;\n onDelete?: () => void;\n onCollapse?: () => void;\n onMaximize?: () => void;\n completionStatus?: string;\n },\n): DropdownMenuItem[] {\n const items: DropdownMenuItem[] = [];\n if (opts.onMaximize) {\n items.push({\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: opts.onMaximize,\n });\n }\n if (opts.onCollapse) {\n items.push({ label: \"Collapse\", icon: UiClose, onSelect: opts.onCollapse });\n }\n if (!isReply && opts.onUpdateStatus) {\n for (const status of config.statuses) {\n if (status.value === opts.completionStatus) continue;\n const current = status.value === comment.status;\n items.push({\n label: (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5\",\n current && \"font-semibold\",\n )}\n >\n <StatusChip status={status.value} config={config} />\n {status.label}\n </span>\n ),\n onSelect: () => opts.onUpdateStatus?.(status.value),\n });\n }\n }\n if (opts.onDelete) {\n items.push({ label: \"Delete\", icon: UiTrash, onSelect: opts.onDelete });\n }\n return items;\n}\n\nfunction IconAction({\n icon,\n label,\n onClick,\n className,\n}: {\n icon: StaticIconComponent;\n label: string;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n \"rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n className,\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n );\n}\n\nfunction CommentBody({\n comment,\n config,\n renderBody,\n onUpdateStatus,\n onDelete,\n onReply,\n onChecklistToggle,\n onCollapse,\n onMaximize,\n}: CommentCardProps & { onCollapse?: () => void; onMaximize?: () => void }) {\n const [statusError, setStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const date = parseTimestamp(comment.createdAt);\n const completion = completionStatus(config);\n const canComplete =\n !isReply &&\n onUpdateStatus != null &&\n completion != null &&\n isUnresolved(config, comment.status);\n const updateStatus = async (status: string) => {\n setStatusError(\"\");\n try {\n await onUpdateStatus?.(status);\n } catch (error) {\n setStatusError(\n error instanceof Error ? error.message : \"Unexpected error\",\n );\n }\n };\n const menuItems = statusMenuItems(comment, config, isReply, {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status) => void updateStatus(status) }\n : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onCollapse ? { onCollapse } : {}),\n ...(onMaximize ? { onMaximize } : {}),\n ...(completion ? { completionStatus: completion.value } : {}),\n });\n\n return (\n <div className=\"space-y-2.5\">\n <div className=\"flex items-start gap-2.5\">\n <CommentAuthorAvatar author={comment.author} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"truncate text-xs font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {date && (\n <span\n title={date.toLocaleString()}\n className=\"shrink-0 text-[10px] text-muted-foreground\"\n >\n {formatRelativeTime(date)}\n </span>\n )}\n </div>\n {!isReply && (\n <div className=\"mt-1 flex flex-wrap gap-1\">\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} />\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {canComplete && (\n <IconAction\n icon={UiCheck}\n label={completionActionLabel(completion)}\n onClick={() => void updateStatus(completion.value)}\n className=\"hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300\"\n />\n )}\n {menuItems.length > 0 && (\n <DropdownMenu\n align=\"right\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={menuItems}\n />\n )}\n </div>\n </div>\n {comment.body.trim() && (\n <div className=\"text-[13px] leading-5 text-foreground\">\n {renderBody ? (\n renderBody(comment.body)\n ) : (\n <CommentMarkdown text={comment.body} />\n )}\n </div>\n )}\n {statusError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n Couldn't update comment: {statusError}\n </div>\n )}\n {!isReply && (\n <ChecklistChips\n comment={comment}\n {...(onChecklistToggle ? { onChecklistToggle } : {})}\n />\n )}\n {!isReply && <RefChips comment={comment} />}\n {onReply && (\n <button\n type=\"button\"\n onClick={onReply}\n className=\"-ml-2 rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10\"\n >\n Reply\n </button>\n )}\n </div>\n );\n}\n\n/**\n * A single comment, collapsible between a compact one-line preview and a full\n * card. The full view can be maximized into a modal. Fully controlled — all\n * mutations are delegated to the supplied callbacks.\n */\nexport function CommentCard(props: CommentCardProps) {\n const { comment, config, compact, defaultExpanded } = props;\n const [expanded, setExpanded] = useState(defaultExpanded ?? false);\n const [maximized, setMaximized] = useState(false);\n const [collapsedStatusError, setCollapsedStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const completion = completionStatus(config);\n const canComplete =\n !isReply &&\n props.onUpdateStatus != null &&\n completion != null &&\n isUnresolved(config, comment.status);\n const completeCollapsed = async () => {\n if (!completion) return;\n setCollapsedStatusError(\"\");\n try {\n await props.onUpdateStatus?.(completion.value);\n } catch (error) {\n setCollapsedStatusError(\n error instanceof Error ? error.message : \"Unexpected error\",\n );\n }\n };\n\n const body = (\n <CommentBody\n {...props}\n onMaximize={() => setMaximized(true)}\n {...(expanded ? { onCollapse: () => setExpanded(false) } : {})}\n />\n );\n\n const modal = (\n <Modal\n open={maximized}\n onClose={() => setMaximized(false)}\n title=\"Comment\"\n size=\"lg\"\n >\n <CommentBody {...props} />\n </Modal>\n );\n\n if (!expanded) {\n return (\n <>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpanded(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setExpanded(true);\n }\n }}\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs transition-colors hover:bg-muted/50\",\n !isReply && \"border border-border bg-background shadow-sm\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/30\",\n )}\n >\n <CommentAuthorAvatar author={comment.author} bare />\n <span className=\"shrink-0 text-[11px] font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {!isReply && (\n <>\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} compact />\n </>\n )}\n <span className=\"min-w-0 flex-1 truncate text-[12px] text-foreground/80\">\n {comment.body.trim()\n ? truncatePlain(comment.body)\n : comment.rating === \"positive\"\n ? \"Positive rating\"\n : comment.rating === \"negative\"\n ? \"Negative rating\"\n : \"Empty comment\"}\n </span>\n {canComplete && (\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <IconAction\n icon={UiCheck}\n label={completionActionLabel(completion)}\n onClick={() => void completeCollapsed()}\n className=\"hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300\"\n />\n </span>\n )}\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <DropdownMenu\n align=\"right\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:opacity-100 group-hover/comment:opacity-70\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={[\n { label: \"Expand\", onSelect: () => setExpanded(true) },\n {\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: () => setMaximized(true),\n },\n ]}\n />\n </span>\n </div>\n {collapsedStatusError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n Couldn't update comment: {collapsedStatusError}\n </div>\n )}\n {modal}\n </>\n );\n }\n\n return (\n <>\n <div\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment rounded-xl bg-background px-3.5 py-3 text-xs\",\n !isReply &&\n \"border border-border/80 shadow-sm transition-shadow hover:shadow-md\",\n compact && \"px-2.5 py-2.5\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/20 shadow-none\",\n )}\n >\n {body}\n </div>\n {modal}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@ const DropdownMenu = require("../overlay/DropdownMenu.cjs");
|
|
|
8
8
|
const MentionTextarea = require("./MentionTextarea.cjs");
|
|
9
9
|
const commentUtils = require("./comment-utils.cjs");
|
|
10
10
|
const UiClose = require("../icons/components/UiClose.cjs");
|
|
11
|
+
const UiThumbsUp = require("../icons/components/UiThumbsUp.cjs");
|
|
12
|
+
const UiThumbsDown = require("../icons/components/UiThumbsDown.cjs");
|
|
11
13
|
const UiArrowUp = require("../icons/components/UiArrowUp.cjs");
|
|
12
14
|
const FACET_DOT_CLASS = {
|
|
13
15
|
neutral: "bg-muted-foreground",
|
|
@@ -95,6 +97,7 @@ function CommentComposer({
|
|
|
95
97
|
const [open, setOpen] = react.useState(!collapsible || autoFocus);
|
|
96
98
|
const [body, setBody] = react.useState("");
|
|
97
99
|
const [facets, setFacets] = react.useState({});
|
|
100
|
+
const [rating, setRating] = react.useState();
|
|
98
101
|
const [submitting, setSubmitting] = react.useState(false);
|
|
99
102
|
const submittingRef = react.useRef(false);
|
|
100
103
|
const mentionsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
@@ -102,6 +105,7 @@ function CommentComposer({
|
|
|
102
105
|
function reset() {
|
|
103
106
|
setBody("");
|
|
104
107
|
setFacets({});
|
|
108
|
+
setRating(void 0);
|
|
105
109
|
mentionsRef.current.clear();
|
|
106
110
|
}
|
|
107
111
|
function setFacet(key, value) {
|
|
@@ -115,7 +119,7 @@ function CommentComposer({
|
|
|
115
119
|
}
|
|
116
120
|
async function submit() {
|
|
117
121
|
const text = body.trim();
|
|
118
|
-
if (!text || submittingRef.current) return;
|
|
122
|
+
if (!text && !rating || submittingRef.current) return;
|
|
119
123
|
submittingRef.current = true;
|
|
120
124
|
setSubmitting(true);
|
|
121
125
|
const mentions = [...mentionsRef.current.values()].filter(
|
|
@@ -125,6 +129,7 @@ function CommentComposer({
|
|
|
125
129
|
await (onCreate == null ? void 0 : onCreate({
|
|
126
130
|
body: text,
|
|
127
131
|
anchor: anchor ?? null,
|
|
132
|
+
...rating ? { rating } : {},
|
|
128
133
|
...Object.keys(facets).length > 0 ? { facets } : {},
|
|
129
134
|
...mentions.length > 0 ? { mentions } : {}
|
|
130
135
|
}));
|
|
@@ -187,6 +192,24 @@ function CommentComposer({
|
|
|
187
192
|
}
|
|
188
193
|
),
|
|
189
194
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-x-3 bottom-2.5 flex items-center gap-1.5", children: [
|
|
195
|
+
[
|
|
196
|
+
["positive", "Positive rating", UiThumbsUp.UiThumbsUp],
|
|
197
|
+
["negative", "Negative rating", UiThumbsDown.UiThumbsDown]
|
|
198
|
+
].map(([value, label, icon]) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
199
|
+
"button",
|
|
200
|
+
{
|
|
201
|
+
type: "button",
|
|
202
|
+
"aria-label": label,
|
|
203
|
+
"aria-pressed": rating === value,
|
|
204
|
+
onClick: () => setRating((current) => current === value ? void 0 : value),
|
|
205
|
+
className: utils.cn(
|
|
206
|
+
"inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
207
|
+
rating === value && (value === "positive" ? "bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300" : "bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300")
|
|
208
|
+
),
|
|
209
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon, className: "text-sm" })
|
|
210
|
+
},
|
|
211
|
+
value
|
|
212
|
+
)),
|
|
190
213
|
(config.facets ?? []).map((facet) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
191
214
|
FacetPicker,
|
|
192
215
|
{
|
|
@@ -203,7 +226,7 @@ function CommentComposer({
|
|
|
203
226
|
"data-testid": "comment-compose-send",
|
|
204
227
|
"aria-label": "Post comment",
|
|
205
228
|
onClick: () => void submit(),
|
|
206
|
-
disabled: submitting || !body.trim(),
|
|
229
|
+
disabled: submitting || !body.trim() && !rating,
|
|
207
230
|
className: "ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground",
|
|
208
231
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiArrowUp.UiArrowUp, className: "text-xs" })
|
|
209
232
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentComposer.cjs","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if (!text || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || !body.trim()}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":["jsx","DropdownMenu","jsxs","cn","toneToBadgeTone","useState","useRef","MentionTextarea","Icon","UiClose","UiArrowUp"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentComposer.cjs","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose, UiThumbsDown, UiThumbsUp } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n CommentRating,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [rating, setRating] = useState<CommentRating | undefined>();\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n setRating(undefined);\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if ((!text && !rating) || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(rating ? { rating } : {}),\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(\n [\n [\"positive\", \"Positive rating\", UiThumbsUp],\n [\"negative\", \"Negative rating\", UiThumbsDown],\n ] as const\n ).map(([value, label, icon]) => (\n <button\n key={value}\n type=\"button\"\n aria-label={label}\n aria-pressed={rating === value}\n onClick={() =>\n setRating((current) => (current === value ? undefined : value))\n }\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n rating === value &&\n (value === \"positive\"\n ? \"bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300\"\n : \"bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300\"),\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n ))}\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || (!body.trim() && !rating)}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":["jsx","DropdownMenu","jsxs","cn","toneToBadgeTone","useState","useRef","MentionTextarea","Icon","UiClose","UiThumbsUp","UiThumbsDown","UiArrowUp"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/comments/CommentComposer.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,kBAAkB,EAElB,cAAc,
|
|
1
|
+
{"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/comments/CommentComposer.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,kBAAkB,EAElB,cAAc,EAEf,MAAM,iBAAiB,CAAC;AAWzB,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,6DAA6D;IAC7D,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1E,CAAC;AAyEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,MAAM,EACN,WAAkB,EAClB,SAAiB,EACjB,WAA8B,EAC9B,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,2CAkJtB"}
|
|
@@ -6,6 +6,8 @@ import { DropdownMenu } from "../overlay/DropdownMenu.js";
|
|
|
6
6
|
import { MentionTextarea } from "./MentionTextarea.js";
|
|
7
7
|
import { toneToBadgeTone } from "./comment-utils.js";
|
|
8
8
|
import { UiClose } from "../icons/components/UiClose.js";
|
|
9
|
+
import { UiThumbsUp } from "../icons/components/UiThumbsUp.js";
|
|
10
|
+
import { UiThumbsDown } from "../icons/components/UiThumbsDown.js";
|
|
9
11
|
import { UiArrowUp } from "../icons/components/UiArrowUp.js";
|
|
10
12
|
const FACET_DOT_CLASS = {
|
|
11
13
|
neutral: "bg-muted-foreground",
|
|
@@ -93,6 +95,7 @@ function CommentComposer({
|
|
|
93
95
|
const [open, setOpen] = useState(!collapsible || autoFocus);
|
|
94
96
|
const [body, setBody] = useState("");
|
|
95
97
|
const [facets, setFacets] = useState({});
|
|
98
|
+
const [rating, setRating] = useState();
|
|
96
99
|
const [submitting, setSubmitting] = useState(false);
|
|
97
100
|
const submittingRef = useRef(false);
|
|
98
101
|
const mentionsRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -100,6 +103,7 @@ function CommentComposer({
|
|
|
100
103
|
function reset() {
|
|
101
104
|
setBody("");
|
|
102
105
|
setFacets({});
|
|
106
|
+
setRating(void 0);
|
|
103
107
|
mentionsRef.current.clear();
|
|
104
108
|
}
|
|
105
109
|
function setFacet(key, value) {
|
|
@@ -113,7 +117,7 @@ function CommentComposer({
|
|
|
113
117
|
}
|
|
114
118
|
async function submit() {
|
|
115
119
|
const text = body.trim();
|
|
116
|
-
if (!text || submittingRef.current) return;
|
|
120
|
+
if (!text && !rating || submittingRef.current) return;
|
|
117
121
|
submittingRef.current = true;
|
|
118
122
|
setSubmitting(true);
|
|
119
123
|
const mentions = [...mentionsRef.current.values()].filter(
|
|
@@ -123,6 +127,7 @@ function CommentComposer({
|
|
|
123
127
|
await (onCreate == null ? void 0 : onCreate({
|
|
124
128
|
body: text,
|
|
125
129
|
anchor: anchor ?? null,
|
|
130
|
+
...rating ? { rating } : {},
|
|
126
131
|
...Object.keys(facets).length > 0 ? { facets } : {},
|
|
127
132
|
...mentions.length > 0 ? { mentions } : {}
|
|
128
133
|
}));
|
|
@@ -185,6 +190,24 @@ function CommentComposer({
|
|
|
185
190
|
}
|
|
186
191
|
),
|
|
187
192
|
/* @__PURE__ */ jsxs("div", { className: "absolute inset-x-3 bottom-2.5 flex items-center gap-1.5", children: [
|
|
193
|
+
[
|
|
194
|
+
["positive", "Positive rating", UiThumbsUp],
|
|
195
|
+
["negative", "Negative rating", UiThumbsDown]
|
|
196
|
+
].map(([value, label, icon]) => /* @__PURE__ */ jsx(
|
|
197
|
+
"button",
|
|
198
|
+
{
|
|
199
|
+
type: "button",
|
|
200
|
+
"aria-label": label,
|
|
201
|
+
"aria-pressed": rating === value,
|
|
202
|
+
onClick: () => setRating((current) => current === value ? void 0 : value),
|
|
203
|
+
className: cn(
|
|
204
|
+
"inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
205
|
+
rating === value && (value === "positive" ? "bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300" : "bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300")
|
|
206
|
+
),
|
|
207
|
+
children: /* @__PURE__ */ jsx(Icon, { icon, className: "text-sm" })
|
|
208
|
+
},
|
|
209
|
+
value
|
|
210
|
+
)),
|
|
188
211
|
(config.facets ?? []).map((facet) => /* @__PURE__ */ jsx(
|
|
189
212
|
FacetPicker,
|
|
190
213
|
{
|
|
@@ -201,7 +224,7 @@ function CommentComposer({
|
|
|
201
224
|
"data-testid": "comment-compose-send",
|
|
202
225
|
"aria-label": "Post comment",
|
|
203
226
|
onClick: () => void submit(),
|
|
204
|
-
disabled: submitting || !body.trim(),
|
|
227
|
+
disabled: submitting || !body.trim() && !rating,
|
|
205
228
|
className: "ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground",
|
|
206
229
|
children: /* @__PURE__ */ jsx(Icon, { icon: UiArrowUp, className: "text-xs" })
|
|
207
230
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentComposer.js","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if (!text || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || !body.trim()}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,kBAA6C;AAAA,EACjD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AACR;AAgBA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,WAAW,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AAC5D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,OAAM;AAAA,MACN,eAAc;AAAA,MACd,SACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA,YACA,WACI,6BACA;AAAA,UAAA;AAAA,UAGL,UAAA;AAAA,YAAA,WAAW,SAAS,QAAQ,MAAM;AAAA,YAClC,YACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS,CAAC,MAAM;AACd,oBAAE,gBAAA;AACF,2BAAS,MAAS;AAAA,gBACpB;AAAA,gBACD,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAED;AAAA,QAAA;AAAA,MAAA;AAAA,MAKL,UAAA,CAAC,UACA,oBAAC,OAAA,EAAI,WAAU,QACZ,UAAA,MAAM,QAAQ,IAAI,CAAC,WAClB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,qBAAS,OAAO,UAAU,QAAQ,SAAY,OAAO,KAAK;AAC1D,kBAAA;AAAA,UACF;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA,OAAO,UAAU,SAAS;AAAA,UAAA;AAAA,UAG5B,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA,gBAAgB,gBAAgB,OAAO,IAAI,CAAC;AAAA,gBAAA;AAAA,cAC9C;AAAA,YAAA;AAAA,YAED,OAAO;AAAA,UAAA;AAAA,QAAA;AAAA,QAjBH,OAAO;AAAA,MAAA,CAmBf,EAAA,CACH;AAAA,IAAA;AAAA,EAAA;AAIR;AAOO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,CAAC,eAAe,SAAS;AAC1D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AACnC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiC,CAAA,CAAE;AAC/D,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,gBAAgB,OAAO,KAAK;AAClC,QAAM,cAAc,OAAoC,oBAAI,KAAK;AACjE,QAAM,WAAW,OAA4B,IAAI;AAEjD,WAAS,QAAQ;AACf,YAAQ,EAAE;AACV,cAAU,CAAA,CAAE;AACZ,gBAAY,QAAQ,MAAA;AAAA,EACtB;AAEA,WAAS,SAAS,KAAa,OAA2B;AACxD,cAAU,CAAC,SAAS;AAClB,UAAI,UAAU,QAAW;AACvB,cAAM,EAAE,CAAC,GAAG,GAAG,OAAO,GAAG,SAAS;AAClC,eAAO;AAAA,MACT;AACA,aAAO,EAAE,GAAG,MAAM,CAAC,GAAG,GAAG,MAAA;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,iBAAe,SAAS;AACtB,UAAM,OAAO,KAAK,KAAA;AAClB,QAAI,CAAC,QAAQ,cAAc,QAAS;AACpC,kBAAc,UAAU;AACxB,kBAAc,IAAI;AAClB,UAAM,WAAW,CAAC,GAAG,YAAY,QAAQ,OAAA,CAAQ,EAAE;AAAA,MAAO,CAAC,MACzD,KAAK,YAAA,EAAc,SAAS,IAAI,EAAE,KAAK,YAAA,CAAa,EAAE;AAAA,IAAA;AAExD,QAAI;AACF,aAAM,qCAAW;AAAA,QACf,MAAM;AAAA,QACN,QAAQ,UAAU;AAAA,QAClB,GAAI,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,EAAE,OAAA,IAAW,CAAA;AAAA,QAClD,GAAI,SAAS,SAAS,IAAI,EAAE,SAAA,IAAa,CAAA;AAAA,MAAC;AAE5C,iBAAW,WAAW,SAAU,wCAAY,SAAS,EAAE,MAAM;AAC7D,YAAA;AACA,UAAI,qBAAqB,KAAK;AAAA,IAChC,UAAA;AACE,oBAAc,UAAU;AACxB,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,KAAK,KAAA,EAAO,WAAW,GAAG;AACrC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,QAAQ,IAAI;AAAA,QAC3B,WAAU;AAAA,QAET,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAY;AAAA,MACZ,aAAW;AAAA,MACX,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU;AAAA,YACV,cAAc,OAAO,gBAAgB,CAAA;AAAA,YACrC,iBAAiB,CAAC,MAAM,YAAY,QAAQ,IAAI,EAAE,IAAI,CAAC;AAAA,YACvD,UAAU,MAAM,KAAK,OAAA;AAAA,YACrB,UAAU,MAAM;AACd,oBAAA;AACA,kBAAI,qBAAqB,KAAK;AAAA,YAChC;AAAA,YACA;AAAA,YACA,WAAW,aAAa;AAAA,YACxB,eAAY;AAAA,YACZ,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEX,eACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM;AACb,oBAAA;AACA,sBAAQ,KAAK;AAAA,YACf;AAAA,YACA,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,SAAS,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAG7C,qBAAC,OAAA,EAAI,WAAU,2DACX,UAAA;AAAA,WAAA,OAAO,UAAU,CAAA,GAAI,IAAI,CAAC,UAC1B;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC;AAAA,cACA,OAAO,OAAO,MAAM,GAAG;AAAA,cACvB,UAAU,CAAC,UAAU,SAAS,MAAM,KAAK,KAAK;AAAA,YAAA;AAAA,YAHzC,MAAM;AAAA,UAAA,CAKd;AAAA,UACD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,eAAY;AAAA,cACZ,cAAW;AAAA,cACX,SAAS,MAAM,KAAK,OAAA;AAAA,cACpB,UAAU,cAAc,CAAC,KAAK,KAAA;AAAA,cAC9B,WAAU;AAAA,cAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,WAAW,WAAU,UAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,QAC7C,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
1
|
+
{"version":3,"file":"CommentComposer.js","sources":["../../src/comments/CommentComposer.tsx"],"sourcesContent":["import { useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu } from \"../overlay/DropdownMenu\";\nimport { UiArrowUp, UiClose, UiThumbsDown, UiThumbsUp } from \"../icons\";\nimport type { BadgeTone } from \"../data/Badge\";\nimport { MentionTextarea } from \"./MentionTextarea\";\nimport { toneToBadgeTone } from \"./comment-utils\";\nimport type {\n CommentAnchor,\n CommentConfig,\n CommentCreateInput,\n CommentFacet,\n CommentMention,\n CommentRating,\n} from \"./comment-types\";\n\n// Static literals so Tailwind's source scanner emits these utilities.\nconst FACET_DOT_CLASS: Record<BadgeTone, string> = {\n neutral: \"bg-muted-foreground\",\n success: \"bg-green-500\",\n danger: \"bg-red-500\",\n warning: \"bg-yellow-500\",\n info: \"bg-blue-500\",\n};\n\nexport type CommentComposerProps = {\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n /** Start collapsed as an \"Add a comment…\" pill until clicked. */\n collapsible?: boolean;\n autoFocus?: boolean;\n placeholder?: string;\n /** Create a new root comment. */\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n /** Fired once per mention referenced by a posted comment. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\nfunction FacetPicker({\n facet,\n value,\n onChange,\n}: {\n facet: CommentFacet;\n value: string | undefined;\n onChange: (value: string | undefined) => void;\n}) {\n const selected = facet.options.find((o) => o.value === value);\n return (\n <DropdownMenu\n align=\"left\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium transition-colors\",\n selected\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:bg-muted/60\",\n )}\n >\n {selected ? selected.label : facet.label}\n {selected && (\n <span\n role=\"presentation\"\n className=\"ml-0.5 cursor-pointer hover:text-foreground\"\n onClick={(e) => {\n e.stopPropagation();\n onChange(undefined);\n }}\n >\n ×\n </span>\n )}\n </span>\n }\n >\n {(close) => (\n <div className=\"py-1\">\n {facet.options.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => {\n onChange(option.value === value ? undefined : option.value);\n close();\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-popover-foreground hover:bg-accent hover:text-accent-foreground\",\n option.value === value && \"bg-muted font-semibold\",\n )}\n >\n <span\n className={cn(\n \"inline-block size-2 rounded-full\",\n FACET_DOT_CLASS[toneToBadgeTone(option.tone)],\n )}\n />\n {option.label}\n </button>\n ))}\n </div>\n )}\n </DropdownMenu>\n );\n}\n\n/**\n * The new-comment composer: a mention-aware textarea, one picker per configured\n * facet, and a send button. Starts as a pill when `collapsible`. Owns only its\n * own draft state; posting is delegated to `onCreate`.\n */\nexport function CommentComposer({\n config,\n anchor,\n collapsible = true,\n autoFocus = false,\n placeholder = \"Add a comment…\",\n onCreate,\n onMention,\n}: CommentComposerProps) {\n const [open, setOpen] = useState(!collapsible || autoFocus);\n const [body, setBody] = useState(\"\");\n const [facets, setFacets] = useState<Record<string, string>>({});\n const [rating, setRating] = useState<CommentRating | undefined>();\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n const mentionsRef = useRef<Map<string, CommentMention>>(new Map());\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n function reset() {\n setBody(\"\");\n setFacets({});\n setRating(undefined);\n mentionsRef.current.clear();\n }\n\n function setFacet(key: string, value: string | undefined) {\n setFacets((prev) => {\n if (value === undefined) {\n const { [key]: _drop, ...rest } = prev;\n return rest;\n }\n return { ...prev, [key]: value };\n });\n }\n\n async function submit() {\n const text = body.trim();\n if ((!text && !rating) || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n const mentions = [...mentionsRef.current.values()].filter((m) =>\n body.toLowerCase().includes(`@${m.name.toLowerCase()}`),\n );\n try {\n await onCreate?.({\n body: text,\n anchor: anchor ?? null,\n ...(rating ? { rating } : {}),\n ...(Object.keys(facets).length > 0 ? { facets } : {}),\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body: text });\n reset();\n if (collapsible) setOpen(false);\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n\n if (!open && body.trim().length === 0) {\n return (\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"w-full rounded-full border border-border bg-muted/25 px-4 py-2 text-left text-sm text-muted-foreground/70 shadow-sm transition-colors hover:border-primary/30 hover:bg-muted/40\"\n >\n {placeholder}\n </button>\n );\n }\n\n return (\n <div\n data-testid=\"comment-compose-box\"\n aria-busy={submitting}\n className=\"relative rounded-2xl border border-border bg-muted/25 px-3 pt-3 pb-10 shadow-sm transition-[border-color,box-shadow] focus-within:border-primary/40 focus-within:shadow-md\"\n >\n <MentionTextarea\n ref={inputRef}\n value={body}\n onChange={setBody}\n mentionables={config.mentionables ?? []}\n onMentionSelect={(m) => mentionsRef.current.set(m.id, m)}\n onSubmit={() => void submit()}\n onCancel={() => {\n reset();\n if (collapsible) setOpen(false);\n }}\n placeholder={placeholder}\n autoFocus={autoFocus || open}\n data-testid=\"comment-compose-input\"\n className=\"min-h-[48px] pr-10\"\n />\n {collapsible && (\n <button\n type=\"button\"\n onClick={() => {\n reset();\n setOpen(false);\n }}\n aria-label=\"Close composer\"\n className=\"absolute right-3 top-3 rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiClose} className=\"text-xs\" />\n </button>\n )}\n <div className=\"absolute inset-x-3 bottom-2.5 flex items-center gap-1.5\">\n {(\n [\n [\"positive\", \"Positive rating\", UiThumbsUp],\n [\"negative\", \"Negative rating\", UiThumbsDown],\n ] as const\n ).map(([value, label, icon]) => (\n <button\n key={value}\n type=\"button\"\n aria-label={label}\n aria-pressed={rating === value}\n onClick={() =>\n setRating((current) => (current === value ? undefined : value))\n }\n className={cn(\n \"inline-flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n rating === value &&\n (value === \"positive\"\n ? \"bg-green-100 text-green-700 [[data-theme=dark]_&]:bg-green-500/20 [[data-theme=dark]_&]:text-green-300\"\n : \"bg-red-100 text-red-700 [[data-theme=dark]_&]:bg-red-500/20 [[data-theme=dark]_&]:text-red-300\"),\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n ))}\n {(config.facets ?? []).map((facet) => (\n <FacetPicker\n key={facet.key}\n facet={facet}\n value={facets[facet.key]}\n onChange={(value) => setFacet(facet.key, value)}\n />\n ))}\n <button\n type=\"button\"\n data-testid=\"comment-compose-send\"\n aria-label=\"Post comment\"\n onClick={() => void submit()}\n disabled={submitting || (!body.trim() && !rating)}\n className=\"ml-auto inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\"\n >\n <Icon icon={UiArrowUp} className=\"text-xs\" />\n </button>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|