@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/ActionCard.js +2 -2
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/actions/ExecutionHistory.js +1 -1
- package/dist/components/actions/ExecutionHistory.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
- package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +23 -23
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +5 -5
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
- package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
- package/dist/components/agents/AgentCard.js +14 -11
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +4 -4
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPlanPanel.js +4 -4
- package/dist/components/ai/AIPlanPanel.js.map +1 -1
- package/dist/components/ai/AIPlaygroundTab.js +1 -1
- package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +2 -2
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
- package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +4 -4
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +4 -4
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/ai/AITasksTab.js +1 -1
- package/dist/components/ai/AITasksTab.js.map +1 -1
- package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
- package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +3 -3
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
- package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
- package/dist/components/audit/AuditDeleteDialog.js +2 -2
- package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
- package/dist/components/audit/AuditExportButton.js +5 -5
- package/dist/components/audit/AuditExportButton.js.map +1 -1
- package/dist/components/audit/AuditLogDetail.js +9 -9
- package/dist/components/audit/AuditLogDetail.js.map +1 -1
- package/dist/components/audit/AuditLogFilters.js +1 -1
- package/dist/components/audit/AuditLogFilters.js.map +1 -1
- package/dist/components/audit/AuditLogStats.js +1 -1
- package/dist/components/audit/AuditLogStats.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +28 -28
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/catalog/CatalogCard.js +3 -3
- package/dist/components/catalog/CatalogCard.js.map +1 -1
- package/dist/components/catalog/CatalogForm.js +1 -1
- package/dist/components/catalog/CatalogForm.js.map +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js +6 -6
- package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
- package/dist/components/common/CommandEditor.js +1 -1
- package/dist/components/common/CommandEditor.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/docs/DocsPreviewPanel.js +1 -1
- package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
- package/dist/components/docs/DocsRevisionHistory.js +4 -4
- package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
- package/dist/components/docs/DocsSettingsPanel.js +7 -7
- package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +1 -1
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/idp-components/ComponentCard.js +5 -5
- package/dist/components/idp-components/ComponentCard.js.map +1 -1
- package/dist/components/idp-components/ComponentForm.js +1 -1
- package/dist/components/idp-components/ComponentForm.js.map +1 -1
- package/dist/components/idp-components/DependencyGraphTab.js +7 -7
- package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
- package/dist/components/logs/LogViewer.js +14 -14
- package/dist/components/logs/LogViewer.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +2 -2
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +1 -1
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
- package/dist/components/scorecard/ScorecardPanel.js +3 -3
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/security/SecurityScanList.js +11 -11
- package/dist/components/security/SecurityScanList.js.map +1 -1
- package/dist/components/sessions/SessionCard.js +26 -19
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionStatusBadge.js +7 -7
- package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
- package/dist/components/shared/AgentSelector.js +4 -4
- package/dist/components/shared/AgentSelector.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +3 -3
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/Card.js +4 -4
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/ErrorState.js +15 -9
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/shared/FilterDropdown.js +2 -2
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/PageSkeleton.js +6 -6
- package/dist/components/shared/PageSkeleton.js.map +1 -1
- package/dist/components/shared/SearchInput.js +1 -1
- package/dist/components/shared/SearchInput.js.map +1 -1
- package/dist/components/shared/StatCard.js +7 -7
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/templates/PublishTemplateForm.js +2 -2
- package/dist/components/templates/PublishTemplateForm.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +5 -5
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/templates/TemplateWizard.js +2 -2
- package/dist/components/templates/TemplateWizard.js.map +1 -1
- package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
- package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
- package/dist/components/tunnels/ProviderBadge.js +1 -1
- package/dist/components/tunnels/ProviderBadge.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +16 -16
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
- package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
- package/dist/components/vibecalendar/LogsDrawer.js +2 -2
- package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
- package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
- package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
- package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
- package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
- package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
- package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
- package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
- package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
- package/dist/components/vibecalendar/types.js +6 -6
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
- package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +8 -8
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
- package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
- package/dist/components/vibes/VibeCard.js +22 -18
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +16 -12
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/pages/ActionsPage.js +4 -3
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +9 -9
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsLandingPage.js +4 -4
- package/dist/pages/AgentsLandingPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +14 -14
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +8 -8
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +11 -11
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +12 -9
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +7 -7
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +12 -9
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +2 -2
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +12 -12
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/HealthPage.js +12 -12
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +4 -4
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +5 -5
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +74 -41
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +6 -3
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js +7 -7
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +21 -21
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +20 -20
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +64 -52
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +21 -21
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/SharedVibeDeckPage.js +5 -5
- package/dist/pages/SharedVibeDeckPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +56 -65
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +4 -4
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +10 -10
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +6 -5
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +3 -3
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +3 -3
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +3 -3
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +15 -15
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeTaskDetailPage.js +5 -5
- package/dist/pages/VibeTaskDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +18 -18
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +11 -11
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +1 -1
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +6 -6
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/tunnels/AuditTab.js +1 -1
- package/dist/pages/tunnels/AuditTab.js.map +1 -1
- package/dist/pages/tunnels/DomainsTab.js +1 -1
- package/dist/pages/tunnels/DomainsTab.js.map +1 -1
- package/dist/pages/tunnels/ProvidersTab.js +1 -1
- package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
- package/dist/pages/tunnels/SessionsTab.js +1 -1
- package/dist/pages/tunnels/SessionsTab.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/UsageTab.js +1 -1
- package/dist/pages/tunnels/UsageTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogViewer.js","names":[],"sources":["../../../src/components/logs/LogViewer.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type FC } from 'react';\nimport { Download, Pause, Play, RefreshCw, Search, Trash2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useVibecontrolsAgentLogStreamSubscription } from '@/generated/wspace-operations';\n\ninterface LogEntry {\n timestamp: string;\n level: 'debug' | 'info' | 'warn' | 'error';\n source: string;\n message: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface LogViewerProps {\n agentId: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n level?: 'debug' | 'info' | 'warn' | 'error';\n source?: string;\n maxEntries?: number;\n className?: string;\n compact?: boolean;\n}\n\nfunction getEntryKey(entry: LogEntry): string {\n return `${entry.timestamp}:${entry.level}:${entry.source}:${entry.message}`;\n}\n\nconst LEVEL_COLORS: Record<string, string> = {\n debug: 'text-text-tertiary',\n info: 'text-status-info-text',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n debug: 'bg-bg-tertiary/30',\n info: 'bg-status-info-bg/10',\n warn: 'bg-status-warning-bg/10',\n error: 'bg-status-error-bg/10',\n};\n\nexport const LogViewer: FC<LogViewerProps> = ({\n agentId,\n profile,\n level: initialLevel = 'info',\n source: initialSource,\n maxEntries = 1000,\n className = '',\n compact = false,\n}) => {\n const { t } = useI18n();\n const [entries, setEntries] = useState<LogEntry[]>([]);\n const [paused, setPaused] = useState(false);\n const [level, setLevel] = useState(initialLevel);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const autoScrollRef = useRef(true);\n\n // GraphQL subscription — the svc polls the agent server-side every\n // ~2s and pushes only new entries here. Replaces the previous client\n // setInterval + fetch, which was firing infinitely because\n // `tr` recreated on every render mutated fetchLogs' identity, which\n // re-ran the useEffect, which re-armed the interval, which called\n // fetchLogs immediately. The subscription has no client polling at\n // all — Apollo opens one WS, the svc owns the cadence.\n const {\n data: streamData,\n error: subscriptionError,\n loading: streamLoading,\n } = useVibecontrolsAgentLogStreamSubscription({\n variables: {\n agentId,\n profile,\n level,\n source: initialSource,\n },\n skip: !agentId || paused,\n shouldResubscribe: true,\n });\n\n const tr = (key: string, fallback: string) => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const handleScroll = useCallback(() => {\n if (!containerRef.current) {\n return;\n }\n\n const element = containerRef.current;\n autoScrollRef.current = element.scrollHeight - element.scrollTop - element.clientHeight < 50;\n }, []);\n\n const scrollToBottom = useCallback(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n }, []);\n\n // Drain incoming subscription batches into the entries list. Each\n // payload is the NEW entries since the last server-side tick; merge\n // by key so re-deliveries (e.g. on reconnect backfill) don't dupe.\n useEffect(() => {\n const batch = streamData?.vibecontrolsAgentLogStream;\n if (!batch || batch.length === 0) return;\n const incoming: LogEntry[] = batch.map(\n (row: {\n timestamp: string;\n level: string;\n source?: string | null;\n message: string;\n metadataJson?: string | null;\n }) => {\n let metadata: Record<string, unknown> | undefined;\n if (row.metadataJson) {\n try {\n metadata = JSON.parse(row.metadataJson) as Record<string, unknown>;\n } catch {\n metadata = undefined;\n }\n }\n return {\n timestamp: row.timestamp,\n level: (row.level as LogEntry['level']) ?? 'info',\n source: row.source ?? '',\n message: row.message,\n metadata,\n };\n }\n );\n setEntries((current) => {\n const merged = new Map<string, LogEntry>();\n for (const entry of current) merged.set(getEntryKey(entry), entry);\n for (const entry of incoming) merged.set(getEntryKey(entry), entry);\n return Array.from(merged.values())\n .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())\n .slice(-maxEntries);\n });\n setErrorMessage(null);\n if (autoScrollRef.current) {\n requestAnimationFrame(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n });\n }\n }, [streamData, maxEntries]);\n\n useEffect(() => {\n if (subscriptionError) {\n setErrorMessage(subscriptionError.message);\n }\n }, [subscriptionError]);\n\n const connected = !subscriptionError && !streamLoading;\n const isRefreshing = streamLoading;\n\n // Reset the entries list when the agent / filter set actually changes.\n useEffect(() => {\n setEntries([]);\n setErrorMessage(null);\n }, [agentId, profile, level, initialSource]);\n\n // Manual refresh: no-op since the subscription pushes continuously.\n // Kept as a callback so the existing \"Refresh\" button doesn't have to\n // be conditionally hidden. Wraps in setEntries([]) so the operator's\n // intent of \"give me a fresh window\" is honoured — the next push\n // backfills.\n const fetchLogs = useCallback(() => {\n setEntries([]);\n }, []);\n\n const filteredEntries = searchQuery\n ? entries.filter(\n (entry) =>\n entry.message.toLowerCase().includes(searchQuery.toLowerCase()) ||\n entry.source.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : entries;\n\n const handleClear = () => setEntries([]);\n\n const handleExport = () => {\n const blob = new Blob([filteredEntries.map((entry) => JSON.stringify(entry)).join('\\n')], {\n type: 'application/x-ndjson',\n });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = `agent-logs-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.jsonl`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n const formatTime = (timestamp: string) => {\n try {\n const date = new Date(timestamp);\n return (\n date.toLocaleTimeString('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }) +\n '.' +\n String(date.getMilliseconds()).padStart(3, '0')\n );\n } catch {\n return timestamp;\n }\n };\n\n return (\n <div\n className={`flex flex-col overflow-hidden rounded-lg border border-border-primary bg-bg-elevated ${className}`}\n >\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border-primary bg-bg-secondary/50 px-3 py-2\">\n <div className=\"mr-2 flex items-center gap-1.5\">\n <div\n className={`size-2 rounded-full ${connected ? 'animate-pulse bg-status-success-bg' : 'bg-status-error-bg'}`}\n />\n <span className=\"text-xs text-text-tertiary\">\n {connected ? tr('logs.live', 'Live') : tr('logs.disconnected', 'Disconnected')}\n </span>\n </div>\n\n <select\n value={level}\n onChange={(event) => setLevel(event.target.value as LogEntry['level'])}\n className=\"rounded border border-border-secondary bg-bg-tertiary px-2 py-1 text-xs text-text-primary\"\n aria-label={tr('logs.levelFilter', 'Log level filter')}\n >\n <option value=\"debug\">DEBUG+</option>\n <option value=\"info\">INFO+</option>\n <option value=\"warn\">WARN+</option>\n <option value=\"error\">ERROR</option>\n </select>\n\n <div className=\"relative min-w-[120px] max-w-xs flex-1\">\n <Search\n size={12}\n className=\"absolute left-2 top-1/2 -translate-y-1/2 text-text-tertiary\"\n />\n <input\n aria-label=\"control\"\n type=\"text\"\n placeholder={tr('logs.filterLogs', 'Filter logs...')}\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n className=\"w-full rounded border border-border-secondary bg-bg-tertiary py-1 pl-6 pr-2 text-xs text-text-primary placeholder:text-text-tertiary\"\n />\n </div>\n\n <div className=\"ml-auto flex items-center gap-1\">\n <span className=\"text-xs text-text-tertiary\">\n {filteredEntries.length} {tr('logs.entries', 'entries')}\n </span>\n <button\n type=\"button\"\n onClick={() => void fetchLogs()}\n disabled={isRefreshing}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-tertiary disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw size={14} className={isRefreshing ? 'animate-spin' : ''} />\n </button>\n <button\n type=\"button\"\n onClick={() => setPaused((current) => !current)}\n className={`rounded p-1 transition-colors hover:bg-bg-tertiary ${paused ? 'text-status-warning-text' : 'text-text-tertiary'}`}\n title={paused ? tr('logs.resume', 'Resume') : tr('logs.pause', 'Pause')}\n >\n {paused ? <Play size={14} /> : <Pause size={14} />}\n </button>\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-tertiary\"\n title={tr('logs.clear', 'Clear')}\n >\n <Trash2 size={14} />\n </button>\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-tertiary\"\n title={tr('logs.export', 'Export')}\n >\n <Download size={14} />\n </button>\n </div>\n </div>\n\n {errorMessage ? (\n <div\n role=\"alert\"\n className=\"border-b border-border-primary bg-status-error-bg/10 px-3 py-2 text-xs text-status-error-text\"\n >\n {errorMessage}\n </div>\n ) : null}\n\n <div\n ref={containerRef}\n onScroll={handleScroll}\n className={`overflow-y-auto font-mono text-xs leading-relaxed ${compact ? 'max-h-[300px]' : 'min-h-[400px] flex-1'}`}\n style={{ maxHeight: compact ? '300px' : undefined }}\n >\n {filteredEntries.length === 0 ? (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-text-tertiary\">\n {!agentId\n ? tr('logs.connectToAgent', 'Connect to an agent to view logs')\n : errorMessage\n ? `${tr('logs.subscriptionError', 'Log stream error')}: ${errorMessage}`\n : streamLoading\n ? tr('logs.connecting', 'Connecting to log stream…')\n : tr('logs.waitingForEntries', 'Waiting for log entries…')}\n </div>\n ) : (\n filteredEntries.map((entry, index) => (\n <div\n key={`${entry.timestamp}-${entry.source}-${index}`}\n className={`flex gap-2 border-b border-border-primary/30 px-3 py-0.5 hover:bg-bg-tertiary/50 ${LEVEL_BG[entry.level] ?? ''}`}\n >\n <span className=\"w-[85px] shrink-0 text-text-tertiary\">\n {formatTime(entry.timestamp)}\n </span>\n <span\n className={`w-[42px] shrink-0 uppercase font-semibold ${LEVEL_COLORS[entry.level] ?? 'text-text-primary'}`}\n >\n {entry.level}\n </span>\n <span\n className=\"w-[100px] shrink-0 truncate text-text-secondary\"\n title={entry.source}\n >\n [{entry.source}]\n </span>\n <span className=\"flex-1 break-all text-text-primary\">\n {entry.message}\n {entry.metadata && Object.keys(entry.metadata).length > 0 ? (\n <span className=\"ml-2 text-text-tertiary\">{JSON.stringify(entry.metadata)}</span>\n ) : null}\n </span>\n </div>\n ))\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LogViewer.js","names":[],"sources":["../../../src/components/logs/LogViewer.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type FC } from 'react';\nimport { Download, Pause, Play, RefreshCw, Search, Trash2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useVibecontrolsAgentLogStreamSubscription } from '@/generated/wspace-operations';\n\ninterface LogEntry {\n timestamp: string;\n level: 'debug' | 'info' | 'warn' | 'error';\n source: string;\n message: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface LogViewerProps {\n agentId: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n level?: 'debug' | 'info' | 'warn' | 'error';\n source?: string;\n maxEntries?: number;\n className?: string;\n compact?: boolean;\n}\n\nfunction getEntryKey(entry: LogEntry): string {\n return `${entry.timestamp}:${entry.level}:${entry.source}:${entry.message}`;\n}\n\nconst LEVEL_COLORS: Record<string, string> = {\n debug: 'text-text-tertiary',\n info: 'text-status-info-text',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n debug: 'bg-bg-sunken',\n info: 'bg-status-info-bg-subtle',\n warn: 'bg-status-warning-bg-subtle',\n error: 'bg-status-error-bg-subtle',\n};\n\nexport const LogViewer: FC<LogViewerProps> = ({\n agentId,\n profile,\n level: initialLevel = 'info',\n source: initialSource,\n maxEntries = 1000,\n className = '',\n compact = false,\n}) => {\n const { t } = useI18n();\n const [entries, setEntries] = useState<LogEntry[]>([]);\n const [paused, setPaused] = useState(false);\n const [level, setLevel] = useState(initialLevel);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const autoScrollRef = useRef(true);\n\n // GraphQL subscription — the svc polls the agent server-side every\n // ~2s and pushes only new entries here. Replaces the previous client\n // setInterval + fetch, which was firing infinitely because\n // `tr` recreated on every render mutated fetchLogs' identity, which\n // re-ran the useEffect, which re-armed the interval, which called\n // fetchLogs immediately. The subscription has no client polling at\n // all — Apollo opens one WS, the svc owns the cadence.\n const {\n data: streamData,\n error: subscriptionError,\n loading: streamLoading,\n } = useVibecontrolsAgentLogStreamSubscription({\n variables: {\n agentId,\n profile,\n level,\n source: initialSource,\n },\n skip: !agentId || paused,\n shouldResubscribe: true,\n });\n\n const tr = (key: string, fallback: string) => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const handleScroll = useCallback(() => {\n if (!containerRef.current) {\n return;\n }\n\n const element = containerRef.current;\n autoScrollRef.current = element.scrollHeight - element.scrollTop - element.clientHeight < 50;\n }, []);\n\n const scrollToBottom = useCallback(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n }, []);\n\n // Drain incoming subscription batches into the entries list. Each\n // payload is the NEW entries since the last server-side tick; merge\n // by key so re-deliveries (e.g. on reconnect backfill) don't dupe.\n useEffect(() => {\n const batch = streamData?.vibecontrolsAgentLogStream;\n if (!batch || batch.length === 0) return;\n const incoming: LogEntry[] = batch.map(\n (row: {\n timestamp: string;\n level: string;\n source?: string | null;\n message: string;\n metadataJson?: string | null;\n }) => {\n let metadata: Record<string, unknown> | undefined;\n if (row.metadataJson) {\n try {\n metadata = JSON.parse(row.metadataJson) as Record<string, unknown>;\n } catch {\n metadata = undefined;\n }\n }\n return {\n timestamp: row.timestamp,\n level: (row.level as LogEntry['level']) ?? 'info',\n source: row.source ?? '',\n message: row.message,\n metadata,\n };\n }\n );\n setEntries((current) => {\n const merged = new Map<string, LogEntry>();\n for (const entry of current) merged.set(getEntryKey(entry), entry);\n for (const entry of incoming) merged.set(getEntryKey(entry), entry);\n return Array.from(merged.values())\n .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())\n .slice(-maxEntries);\n });\n setErrorMessage(null);\n if (autoScrollRef.current) {\n requestAnimationFrame(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n });\n }\n }, [streamData, maxEntries]);\n\n useEffect(() => {\n if (subscriptionError) {\n setErrorMessage(subscriptionError.message);\n }\n }, [subscriptionError]);\n\n const connected = !subscriptionError && !streamLoading;\n const isRefreshing = streamLoading;\n\n // Reset the entries list when the agent / filter set actually changes.\n useEffect(() => {\n setEntries([]);\n setErrorMessage(null);\n }, [agentId, profile, level, initialSource]);\n\n // Manual refresh: no-op since the subscription pushes continuously.\n // Kept as a callback so the existing \"Refresh\" button doesn't have to\n // be conditionally hidden. Wraps in setEntries([]) so the operator's\n // intent of \"give me a fresh window\" is honoured — the next push\n // backfills.\n const fetchLogs = useCallback(() => {\n setEntries([]);\n }, []);\n\n const filteredEntries = searchQuery\n ? entries.filter(\n (entry) =>\n entry.message.toLowerCase().includes(searchQuery.toLowerCase()) ||\n entry.source.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : entries;\n\n const handleClear = () => setEntries([]);\n\n const handleExport = () => {\n const blob = new Blob([filteredEntries.map((entry) => JSON.stringify(entry)).join('\\n')], {\n type: 'application/x-ndjson',\n });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = `agent-logs-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.jsonl`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n const formatTime = (timestamp: string) => {\n try {\n const date = new Date(timestamp);\n return (\n date.toLocaleTimeString('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }) +\n '.' +\n String(date.getMilliseconds()).padStart(3, '0')\n );\n } catch {\n return timestamp;\n }\n };\n\n return (\n <div\n className={`flex flex-col overflow-hidden rounded-lg border border-border-default bg-bg-elevated ${className}`}\n >\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border-default bg-bg-sunken px-3 py-2\">\n <div className=\"mr-2 flex items-center gap-1.5\">\n <div\n className={`size-2 rounded-full ${connected ? 'animate-pulse bg-status-success-bg' : 'bg-status-error-bg'}`}\n />\n <span className=\"text-xs text-text-tertiary\">\n {connected ? tr('logs.live', 'Live') : tr('logs.disconnected', 'Disconnected')}\n </span>\n </div>\n\n <select\n value={level}\n onChange={(event) => setLevel(event.target.value as LogEntry['level'])}\n className=\"rounded border border-border-subtle bg-bg-sunken px-2 py-1 text-xs text-text-primary\"\n aria-label={tr('logs.levelFilter', 'Log level filter')}\n >\n <option value=\"debug\">DEBUG+</option>\n <option value=\"info\">INFO+</option>\n <option value=\"warn\">WARN+</option>\n <option value=\"error\">ERROR</option>\n </select>\n\n <div className=\"relative min-w-[120px] max-w-xs flex-1\">\n <Search\n size={12}\n className=\"absolute left-2 top-1/2 -translate-y-1/2 text-text-tertiary\"\n />\n <input\n aria-label=\"control\"\n type=\"text\"\n placeholder={tr('logs.filterLogs', 'Filter logs...')}\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n className=\"w-full rounded border border-border-subtle bg-bg-sunken py-1 pl-6 pr-2 text-xs text-text-primary placeholder:text-text-tertiary\"\n />\n </div>\n\n <div className=\"ml-auto flex items-center gap-1\">\n <span className=\"text-xs text-text-tertiary\">\n {filteredEntries.length} {tr('logs.entries', 'entries')}\n </span>\n <button\n type=\"button\"\n onClick={() => void fetchLogs()}\n disabled={isRefreshing}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw size={14} className={isRefreshing ? 'animate-spin' : ''} />\n </button>\n <button\n type=\"button\"\n onClick={() => setPaused((current) => !current)}\n className={`rounded p-1 transition-colors hover:bg-bg-sunken ${paused ? 'text-status-warning-text' : 'text-text-tertiary'}`}\n title={paused ? tr('logs.resume', 'Resume') : tr('logs.pause', 'Pause')}\n >\n {paused ? <Play size={14} /> : <Pause size={14} />}\n </button>\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken\"\n title={tr('logs.clear', 'Clear')}\n >\n <Trash2 size={14} />\n </button>\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken\"\n title={tr('logs.export', 'Export')}\n >\n <Download size={14} />\n </button>\n </div>\n </div>\n\n {errorMessage ? (\n <div\n role=\"alert\"\n className=\"border-b border-border-default bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\"\n >\n {errorMessage}\n </div>\n ) : null}\n\n <div\n ref={containerRef}\n onScroll={handleScroll}\n className={`overflow-y-auto font-mono text-xs leading-relaxed ${compact ? 'max-h-[300px]' : 'min-h-[400px] flex-1'}`}\n style={{ maxHeight: compact ? '300px' : undefined }}\n >\n {filteredEntries.length === 0 ? (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-text-tertiary\">\n {!agentId\n ? tr('logs.connectToAgent', 'Connect to an agent to view logs')\n : errorMessage\n ? `${tr('logs.subscriptionError', 'Log stream error')}: ${errorMessage}`\n : streamLoading\n ? tr('logs.connecting', 'Connecting to log stream…')\n : tr('logs.waitingForEntries', 'Waiting for log entries…')}\n </div>\n ) : (\n filteredEntries.map((entry, index) => (\n <div\n key={`${entry.timestamp}-${entry.source}-${index}`}\n className={`flex gap-2 border-b border-border-default/30 px-3 py-0.5 hover:bg-bg-sunken ${LEVEL_BG[entry.level] ?? ''}`}\n >\n <span className=\"w-[85px] shrink-0 text-text-tertiary\">\n {formatTime(entry.timestamp)}\n </span>\n <span\n className={`w-[42px] shrink-0 uppercase font-semibold ${LEVEL_COLORS[entry.level] ?? 'text-text-primary'}`}\n >\n {entry.level}\n </span>\n <span\n className=\"w-[100px] shrink-0 truncate text-text-secondary\"\n title={entry.source}\n >\n [{entry.source}]\n </span>\n <span className=\"flex-1 break-all text-text-primary\">\n {entry.message}\n {entry.metadata && Object.keys(entry.metadata).length > 0 ? (\n <span className=\"ml-2 text-text-tertiary\">{JSON.stringify(entry.metadata)}</span>\n ) : null}\n </span>\n </div>\n ))\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -156,7 +156,7 @@ function g({ note: g, onOpen: _, onEdit: v, onDelete: y, onPin: b, onUnpin: x, o
|
|
|
156
156
|
g.tagAssignments && g.tagAssignments.length > 0 ? /* @__PURE__ */ h("div", {
|
|
157
157
|
className: "mt-3 flex flex-wrap gap-1",
|
|
158
158
|
children: [g.tagAssignments.slice(0, 3).map((e) => /* @__PURE__ */ h("span", {
|
|
159
|
-
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-
|
|
159
|
+
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg rounded",
|
|
160
160
|
style: e.tag?.color ? { borderLeft: `3px solid ${e.tag.color}` } : void 0,
|
|
161
161
|
children: [/* @__PURE__ */ m(u, { className: "size-3" }), e.tag?.label ?? e.tagId.slice(0, 8)]
|
|
162
162
|
}, e.id)), g.tagAssignments.length > 3 && /* @__PURE__ */ h("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NoteCard.js","names":[],"sources":["../../../src/components/notes/NoteCard.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n FileText,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport type { Note } from './types';\n\ninterface NoteCardProps {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /**\n * Generic-sharing entry point: opens the full Share dialog (users / link /\n * vibe). Distinct from `onShare`, which only toggles PRIVATE/WORKSPACE\n * visibility. Provided only when the caller has share permission + the\n * `vibecontrols.sharing.note` flag is on.\n */\n onShareAccess?: (note: Note) => void;\n}\n\nexport function NoteCard({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: NoteCardProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n // Remove markdown headers and get first 100 chars\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 100 ? text.slice(0, 100) + '...' : text;\n };\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className=\"bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer\"\n onClick={() => onOpen?.(note)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-status-warning-bg flex items-center justify-center\">\n <FileText className=\"size-5 text-status-warning-text\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{note.title}</h3>\n {note.isPinned && <Pin className=\"size-4 text-status-warning-text flex-shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private note\">\n <Lock className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace note\">\n <Globe className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">{formatDate(note.updatedAt)}</p>\n </div>\n </div>\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-36 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </motion.button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </motion.button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </motion.button>\n )}\n {onShareAccess && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </motion.button>\n )}\n {note.isPinned && onUnpin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </motion.button>\n )}\n {!note.isPinned && onPin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </motion.button>\n )}\n {onDelete && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">{getPreview(note.content)}</p>\n\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 3).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-action-primary-bg/10 text-action-primary-bg rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `3px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-3\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 3}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 3}\n </span>\n )}\n </div>\n ) : null}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NoteCard.js","names":[],"sources":["../../../src/components/notes/NoteCard.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n FileText,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport type { Note } from './types';\n\ninterface NoteCardProps {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /**\n * Generic-sharing entry point: opens the full Share dialog (users / link /\n * vibe). Distinct from `onShare`, which only toggles PRIVATE/WORKSPACE\n * visibility. Provided only when the caller has share permission + the\n * `vibecontrols.sharing.note` flag is on.\n */\n onShareAccess?: (note: Note) => void;\n}\n\nexport function NoteCard({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: NoteCardProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n // Remove markdown headers and get first 100 chars\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 100 ? text.slice(0, 100) + '...' : text;\n };\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className=\"bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer\"\n onClick={() => onOpen?.(note)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-status-warning-bg flex items-center justify-center\">\n <FileText className=\"size-5 text-status-warning-text\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{note.title}</h3>\n {note.isPinned && <Pin className=\"size-4 text-status-warning-text flex-shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private note\">\n <Lock className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace note\">\n <Globe className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">{formatDate(note.updatedAt)}</p>\n </div>\n </div>\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-36 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </motion.button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </motion.button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </motion.button>\n )}\n {onShareAccess && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </motion.button>\n )}\n {note.isPinned && onUnpin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </motion.button>\n )}\n {!note.isPinned && onPin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </motion.button>\n )}\n {onDelete && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">{getPreview(note.content)}</p>\n\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 3).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `3px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-3\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 3}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 3}\n </span>\n )}\n </div>\n ) : null}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -186,7 +186,7 @@ function v({ note: v, vibeOptions: y = [], defaultVibeId: b, hideVibeSelect: x,
|
|
|
186
186
|
/* @__PURE__ */ g("div", {
|
|
187
187
|
className: "flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md",
|
|
188
188
|
children: [z.filter((e) => j.has(e.id)).map((e) => /* @__PURE__ */ g("span", {
|
|
189
|
-
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-
|
|
189
|
+
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[var(--color-accent-soft)] text-action-primary-bg rounded",
|
|
190
190
|
children: [
|
|
191
191
|
/* @__PURE__ */ h(p, { className: "size-3" }),
|
|
192
192
|
e.label,
|
|
@@ -231,7 +231,7 @@ function v({ note: v, vibeOptions: y = [], defaultVibeId: b, hideVibeSelect: x,
|
|
|
231
231
|
type: "button",
|
|
232
232
|
onClick: q,
|
|
233
233
|
disabled: !V.trim() || G,
|
|
234
|
-
className: "p-1 text-action-primary-bg hover:bg-
|
|
234
|
+
className: "p-1 text-action-primary-bg hover:bg-[var(--color-accent-soft)] rounded transition-colors disabled:opacity-30",
|
|
235
235
|
children: /* @__PURE__ */ h(d, { className: "size-4" })
|
|
236
236
|
})
|
|
237
237
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NoteEditor.js","names":[],"sources":["../../../src/components/notes/NoteEditor.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { X, Save, Eye, Edit, Tag, Check, Plus, Lock, Globe } from 'lucide-react';\nimport type { CreateNoteInput, Note, NoteTagAssignment, NoteVisibility } from './types';\nimport { useAvailableNoteTagsQuery, useCreateNoteTagMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeControls } from '../../providers/VibeControlsProvider';\n\n/** Tag selected in the editor */\nexport interface SelectedTag {\n id: string;\n key: string;\n label: string;\n color: string | null;\n}\n\n/** Note data submitted from the editor — includes `id` and selected tag IDs */\nexport type NoteEditorSubmitData = CreateNoteInput & {\n id?: string;\n selectedTagIds?: string[];\n visibility?: NoteVisibility;\n};\n\nconst TAG_COLORS = [\n 'var(--color-status-error-text)',\n 'var(--color-status-warning-text)',\n 'var(--color-status-success-text)',\n 'var(--color-accent-teal)',\n 'var(--color-text-link)',\n 'var(--color-accent-purple)',\n 'var(--color-text-tertiary)',\n];\n\ninterface NoteEditorProps {\n note?: Note;\n vibeOptions?: { id: string; name: string }[];\n /** Pre-set vibeId when creating a new note (used in vibe-scoped context) */\n defaultVibeId?: string;\n /** Hide the vibe dropdown (used when notes are scoped to a single vibe) */\n hideVibeSelect?: boolean;\n onSubmit: (data: NoteEditorSubmitData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n}\n\nexport function NoteEditor({\n note,\n vibeOptions = [],\n defaultVibeId,\n hideVibeSelect,\n onSubmit,\n onCancel,\n isLoading,\n}: NoteEditorProps) {\n const tr = useTr();\n const { workspaceId } = useVibeControls();\n const [isPreview, setIsPreview] = useState(false);\n const [formData, setFormData] = useState({\n title: note?.title || '',\n content: note?.content || '',\n vibeId: note?.vibeId || defaultVibeId || '',\n isPinned: note?.isPinned ?? false,\n visibility: (note?.visibility ?? 'WORKSPACE') as NoteVisibility,\n });\n\n // Tag picker state\n const [selectedTagIds, setSelectedTagIds] = useState<Set<string>>(new Set());\n const [tagsInitialized, setTagsInitialized] = useState(false);\n const [showTagPicker, setShowTagPicker] = useState(false);\n\n // Fetch available tags from wspace-tags-svc\n const { data: tagsData, refetch: refetchTags } = useAvailableNoteTagsQuery();\n const availableTags: SelectedTag[] = (tagsData?.availableNoteTags ?? []).map(\n (t: { id: string; key: string; label: string; color?: string | null }) => ({\n id: t.id,\n key: t.key,\n label: t.label,\n color: t.color ?? null,\n })\n );\n\n // Initialize selected tags once available tags load\n useEffect(() => {\n if (tagsInitialized || availableTags.length === 0) return;\n const ids = new Set<string>();\n\n // First: pre-select from tagAssignments (tags service data)\n if (note?.tagAssignments && note.tagAssignments.length > 0) {\n note.tagAssignments.forEach((a: NoteTagAssignment) => ids.add(a.tagId));\n }\n // Fallback: match legacy string tags by label/key to available tags\n else if (note?.tags && note.tags.length > 0) {\n for (const legacyTag of note.tags) {\n const match = availableTags.find(\n (t) =>\n t.label.toLowerCase() === legacyTag.toLowerCase() || t.key === legacyTag.toLowerCase()\n );\n if (match) ids.add(match.id);\n }\n }\n\n if (ids.size > 0) setSelectedTagIds(ids);\n setTagsInitialized(true);\n }, [availableTags, note, tagsInitialized]);\n\n // Inline tag creation\n const [createNoteTag] = useCreateNoteTagMutation();\n const [newTagLabel, setNewTagLabel] = useState('');\n const [newTagColor, setNewTagColor] = useState('var(--color-accent-purple)');\n const [isCreatingTag, setIsCreatingTag] = useState(false);\n\n const handleCreateTag = async () => {\n const label = newTagLabel.trim();\n if (!label || isCreatingTag) return;\n setIsCreatingTag(true);\n try {\n const result = await createNoteTag({\n variables: { input: { label, color: newTagColor } },\n });\n const newTag = result.data?.createNoteTag;\n if (newTag) {\n setSelectedTagIds((prev) => new Set(prev).add(newTag.id));\n await refetchTags();\n }\n setNewTagLabel('');\n } catch (err) {\n console.error('Failed to create tag:', err);\n } finally {\n setIsCreatingTag(false);\n }\n };\n\n const toggleTag = (tagId: string) => {\n setSelectedTagIds((prev) => {\n const next = new Set(prev);\n if (next.has(tagId)) {\n next.delete(tagId);\n } else {\n next.add(tagId);\n }\n return next;\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const data: NoteEditorSubmitData = {\n ...(note?.id ? { id: note.id } : {}),\n workspaceId: workspaceId ?? '',\n title: formData.title,\n content: formData.content,\n vibeId: formData.vibeId || undefined,\n isPinned: formData.isPinned,\n visibility: formData.visibility,\n selectedTagIds: Array.from(selectedTagIds),\n };\n onSubmit(data);\n };\n\n // Enhanced markdown preview with inline formatting support\n const parseInlineFormatting = (text: string): React.ReactNode[] => {\n const parts: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n\n while (remaining.length > 0) {\n // Bold: **text** or __text__\n const boldMatch = remaining.match(/^(\\*\\*|__)(.+?)\\1/);\n if (boldMatch) {\n parts.push(<strong key={key++}>{parseInlineFormatting(boldMatch[2])}</strong>);\n remaining = remaining.slice(boldMatch[0].length);\n continue;\n }\n\n // Italic: *text* or _text_\n const italicMatch = remaining.match(/^(\\*|_)([^*_]+?)\\1/);\n if (italicMatch) {\n parts.push(<em key={key++}>{parseInlineFormatting(italicMatch[2])}</em>);\n remaining = remaining.slice(italicMatch[0].length);\n continue;\n }\n\n // Strikethrough: ~~text~~\n const strikeMatch = remaining.match(/^~~(.+?)~~/);\n if (strikeMatch) {\n parts.push(<del key={key++}>{parseInlineFormatting(strikeMatch[1])}</del>);\n remaining = remaining.slice(strikeMatch[0].length);\n continue;\n }\n\n // Inline code: `code`\n const codeMatch = remaining.match(/^`([^`]+)`/);\n if (codeMatch) {\n parts.push(\n <code\n key={key++}\n className=\"bg-bg-elevated px-1.5 py-0.5 rounded text-sm font-mono text-text-primary\"\n >\n {codeMatch[1]}\n </code>\n );\n remaining = remaining.slice(codeMatch[0].length);\n continue;\n }\n\n // Links: [text](url) - only allow safe URL schemes to prevent XSS\n const linkMatch = remaining.match(/^\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n if (linkMatch) {\n const url = linkMatch[2];\n // Only allow http, https, mailto, relative paths, and hash links to prevent XSS via javascript: URLs\n const isSafeUrl = /^(https?:\\/\\/|mailto:|\\/|#)/i.test(url);\n if (isSafeUrl) {\n parts.push(\n <a\n key={key++}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-action-primary-bg hover:underline\"\n >\n {linkMatch[1]}\n </a>\n );\n } else {\n // Render unsafe links as plain text with muted styling\n parts.push(\n <span key={key++} className=\"text-text-secondary\">\n {linkMatch[1]}\n </span>\n );\n }\n remaining = remaining.slice(linkMatch[0].length);\n continue;\n }\n\n // No match - take the next character\n parts.push(remaining[0]);\n remaining = remaining.slice(1);\n }\n\n return parts;\n };\n\n // Parse markdown content into React elements\n const renderMarkdown = (text: string) => {\n const lines = text.split('\\n');\n const elements: React.ReactNode[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Code blocks: ```\n if (line.startsWith('```')) {\n const language = line.slice(3).trim();\n const codeLines: string[] = [];\n i++;\n while (i < lines.length && !lines[i].startsWith('```')) {\n codeLines.push(lines[i]);\n i++;\n }\n elements.push(\n <pre key={elements.length} className=\"bg-bg-elevated rounded-md p-3 my-2 overflow-x-auto\">\n <code className=\"text-sm font-mono text-text-primary\">\n {language && (\n <span className=\"text-xs text-text-secondary block mb-2\">{language}</span>\n )}\n {codeLines.join('\\n')}\n </code>\n </pre>\n );\n i++;\n continue;\n }\n\n // Headings\n if (line.startsWith('# ')) {\n elements.push(\n <h1 key={elements.length} className=\"text-2xl font-bold mb-2 mt-4\">\n {parseInlineFormatting(line.slice(2))}\n </h1>\n );\n i++;\n continue;\n }\n if (line.startsWith('## ')) {\n elements.push(\n <h2 key={elements.length} className=\"text-xl font-bold mb-2 mt-3\">\n {parseInlineFormatting(line.slice(3))}\n </h2>\n );\n i++;\n continue;\n }\n if (line.startsWith('### ')) {\n elements.push(\n <h3 key={elements.length} className=\"text-lg font-bold mb-2 mt-2\">\n {parseInlineFormatting(line.slice(4))}\n </h3>\n );\n i++;\n continue;\n }\n\n // Blockquotes: > text\n if (line.startsWith('> ')) {\n elements.push(\n <blockquote\n key={elements.length}\n className=\"border-l-4 border-border-default pl-4 my-2 italic text-text-secondary\"\n >\n {parseInlineFormatting(line.slice(2))}\n </blockquote>\n );\n i++;\n continue;\n }\n\n // Unordered list: - or * (collect consecutive items and wrap in <ul>)\n if (line.match(/^[-*] /)) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length && lines[i].match(/^[-*] /)) {\n listItems.push(\n <li key={listItems.length} className=\"list-disc\">\n {parseInlineFormatting(lines[i].slice(2))}\n </li>\n );\n i++;\n }\n elements.push(\n <ul key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ul>\n );\n continue;\n }\n\n // Ordered list: 1. 2. etc. (collect consecutive items and wrap in <ol>)\n const orderedMatch = line.match(/^(\\d+)\\. /);\n if (orderedMatch) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length) {\n const olMatch = lines[i].match(/^(\\d+)\\. /);\n if (!olMatch) break;\n listItems.push(\n <li key={listItems.length} className=\"list-decimal\">\n {parseInlineFormatting(lines[i].slice(olMatch[0].length))}\n </li>\n );\n i++;\n }\n elements.push(\n <ol key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ol>\n );\n continue;\n }\n\n // Horizontal rule: --- or ***\n if (line.match(/^(-{3,}|\\*{3,})$/)) {\n elements.push(<hr key={elements.length} className=\"my-4 border-border-default\" />);\n i++;\n continue;\n }\n\n // Table detection (simple): | col1 | col2 |\n if (line.startsWith('|') && line.endsWith('|')) {\n const tableLines: string[] = [line];\n i++;\n while (i < lines.length && lines[i].startsWith('|') && lines[i].endsWith('|')) {\n tableLines.push(lines[i]);\n i++;\n }\n\n // Parse table\n const rows = tableLines\n .filter((l) => !l.match(/^\\|[\\s\\-:|]+\\|$/)) // Skip separator rows (including alignment markers like :---:)\n .map((l) =>\n l\n .slice(1, -1)\n .split('|')\n .map((cell) => cell.trim())\n );\n\n if (rows.length > 0) {\n const [header, ...body] = rows;\n elements.push(\n <table key={elements.length} className=\"w-full my-2 border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-default\">\n {header.map((cell, ci) => (\n <th key={ci} className=\"text-left py-2 px-3 font-semibold text-text-primary\">\n {parseInlineFormatting(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {body.map((row, ri) => (\n <tr key={ri} className=\"border-b border-border-subtle\">\n {row.map((cell, ci) => (\n <td key={ci} className=\"py-2 px-3 text-text-primary\">\n {parseInlineFormatting(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n continue;\n }\n\n // Empty line\n if (line === '') {\n elements.push(<br key={elements.length} />);\n i++;\n continue;\n }\n\n // Regular paragraph with inline formatting\n elements.push(\n <p key={elements.length} className=\"mb-1\">\n {parseInlineFormatting(line)}\n </p>\n );\n i++;\n }\n\n return elements;\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-3xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {note\n ? tr('vibecontrols.notes.editNote', 'Edit Note')\n : tr('vibecontrols.notes.newNote', 'New Note')}\n </h2>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsPreview(!isPreview)}\n className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${\n isPreview\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n }`}\n >\n {isPreview ? <Edit className=\"size-4\" /> : <Eye className=\"size-4\" />}\n {isPreview\n ? tr('vibecontrols.notes.edit', 'Edit')\n : tr('vibecontrols.notes.preview', 'Preview')}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex-1 flex flex-col overflow-hidden\">\n <div className=\"p-6 space-y-4 flex-1 overflow-y-auto\">\n <div>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={formData.title}\n onChange={(e) => setFormData({ ...formData, title: e.target.value })}\n placeholder={tr('vibecontrols.notes.titlePlaceholder', 'Note title...')}\n required\n className=\"w-full px-0 py-2 text-lg sm:text-2xl font-bold bg-transparent border-0 border-b border-border-default text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-action-primary-bg\"\n />\n </div>\n\n <div className={`grid ${hideVibeSelect ? 'grid-cols-1' : 'grid-cols-2'} gap-4`}>\n {!hideVibeSelect && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.relatedVibe', 'Related Vibe (optional)')}\n </label>\n <select\n value={formData.vibeId}\n onChange={(e) => setFormData({ ...formData, vibeId: e.target.value })}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">No vibe</option>\n {vibeOptions.map((vibe) => (\n <option key={vibe.id} value={vibe.id}>\n {vibe.name}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.tags', 'Tags')}\n </label>\n {/* Selected tags */}\n <div className=\"flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md\">\n {availableTags\n .filter((t) => selectedTagIds.has(t.id))\n .map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-action-primary-bg rounded\"\n >\n <Tag className=\"size-3\" />\n {tag.label}\n <button\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"ml-0.5 hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowTagPicker(!showTagPicker)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs text-text-secondary hover:text-text-primary hover:bg-bg-elevated rounded transition-colors\"\n >\n <Tag className=\"size-3\" />\n {selectedTagIds.size === 0 ? 'Add tags...' : '+'}\n </button>\n </div>\n {/* Tag picker dropdown */}\n {showTagPicker && (\n <div className=\"mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg\">\n {/* Inline create: text input + color picker */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-subtle\">\n <span\n aria-hidden=\"true\"\n className=\"size-6 rounded-full border-2 border-border-default\"\n style={{ backgroundColor: newTagColor }}\n />\n <input\n aria-label=\"New tag name... (Enter to create)\"\n type=\"text\"\n value={newTagLabel}\n onChange={(e) => setNewTagLabel(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCreateTag();\n }\n }}\n placeholder=\"New tag name... (Enter to create)\"\n disabled={isCreatingTag}\n className=\"flex-1 px-2 py-1 text-sm bg-bg-sunken border border-border-default rounded text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={handleCreateTag}\n disabled={!newTagLabel.trim() || isCreatingTag}\n className=\"p-1 text-action-primary-bg hover:bg-action-primary-bg/10 rounded transition-colors disabled:opacity-30\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n {/* Quick color palette */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-border-subtle\">\n {TAG_COLORS.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setNewTagColor(c)}\n className={`size-4 rounded-full transition-transform ${newTagColor === c ? 'ring-2 ring-action-primary-bg scale-125' : 'hover:scale-110'}`}\n style={{ backgroundColor: c }}\n />\n ))}\n </div>\n {/* Existing tags list */}\n <div className=\"max-h-32 overflow-y-auto p-1\">\n {availableTags.length === 0 ? (\n <p className=\"text-xs text-text-muted px-2 py-1\">\n No tags yet — create one above\n </p>\n ) : (\n availableTags.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"flex items-center justify-between w-full px-2 py-1.5 text-sm text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n <span className=\"flex items-center gap-2\">\n {tag.color && (\n <span\n className=\"size-2.5 rounded-full\"\n style={{ backgroundColor: tag.color }}\n />\n )}\n {tag.label}\n </span>\n {selectedTagIds.has(tag.id) && (\n <Check className=\"size-4 text-action-primary-bg\" />\n )}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-h-[300px]\">\n {isPreview ? (\n <div className=\"h-full p-4 bg-bg-sunken rounded-md prose prose-sm dark:prose-invert max-w-none overflow-y-auto\">\n {renderMarkdown(formData.content)}\n </div>\n ) : (\n <textarea\n aria-label=\"control\"\n value={formData.content}\n onChange={(e) => setFormData({ ...formData, content: e.target.value })}\n placeholder={tr(\n 'vibecontrols.notes.contentPlaceholder',\n 'Write your note in Markdown...'\n )}\n className=\"w-full h-full min-h-[300px] px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none font-mono\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-4 flex-wrap\">\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"isPinned\"\n type=\"checkbox\"\n id=\"isPinned\"\n checked={formData.isPinned}\n onChange={(e) => setFormData({ ...formData, isPinned: e.target.checked })}\n className=\"size-4 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n <label htmlFor=\"isPinned\" className=\"text-sm text-text-primary\">\n {tr('vibecontrols.notes.pinThisNote', 'Pin this note')}\n </label>\n </div>\n\n {/* Visibility toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-lg p-0.5\">\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'WORKSPACE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'WORKSPACE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Globe className=\"size-3.5\" />\n Workspace\n </button>\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'PRIVATE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'PRIVATE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Lock className=\"size-3.5\" />\n Private\n </button>\n </div>\n {formData.visibility === 'PRIVATE' && (\n <p className=\"text-xs text-text-muted w-full -mt-2\">Only you can see this note.</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.notes.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <Save className=\"size-4\" />\n {isLoading\n ? tr('vibecontrols.notes.saving', 'Saving...')\n : tr('vibecontrols.notes.saveNote', 'Save Note')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NoteEditor.js","names":[],"sources":["../../../src/components/notes/NoteEditor.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { X, Save, Eye, Edit, Tag, Check, Plus, Lock, Globe } from 'lucide-react';\nimport type { CreateNoteInput, Note, NoteTagAssignment, NoteVisibility } from './types';\nimport { useAvailableNoteTagsQuery, useCreateNoteTagMutation } from '@/generated/wspace-operations';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useVibeControls } from '../../providers/VibeControlsProvider';\n\n/** Tag selected in the editor */\nexport interface SelectedTag {\n id: string;\n key: string;\n label: string;\n color: string | null;\n}\n\n/** Note data submitted from the editor — includes `id` and selected tag IDs */\nexport type NoteEditorSubmitData = CreateNoteInput & {\n id?: string;\n selectedTagIds?: string[];\n visibility?: NoteVisibility;\n};\n\nconst TAG_COLORS = [\n 'var(--color-status-error-text)',\n 'var(--color-status-warning-text)',\n 'var(--color-status-success-text)',\n 'var(--color-accent-teal)',\n 'var(--color-text-link)',\n 'var(--color-accent-purple)',\n 'var(--color-text-tertiary)',\n];\n\ninterface NoteEditorProps {\n note?: Note;\n vibeOptions?: { id: string; name: string }[];\n /** Pre-set vibeId when creating a new note (used in vibe-scoped context) */\n defaultVibeId?: string;\n /** Hide the vibe dropdown (used when notes are scoped to a single vibe) */\n hideVibeSelect?: boolean;\n onSubmit: (data: NoteEditorSubmitData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n}\n\nexport function NoteEditor({\n note,\n vibeOptions = [],\n defaultVibeId,\n hideVibeSelect,\n onSubmit,\n onCancel,\n isLoading,\n}: NoteEditorProps) {\n const tr = useTr();\n const { workspaceId } = useVibeControls();\n const [isPreview, setIsPreview] = useState(false);\n const [formData, setFormData] = useState({\n title: note?.title || '',\n content: note?.content || '',\n vibeId: note?.vibeId || defaultVibeId || '',\n isPinned: note?.isPinned ?? false,\n visibility: (note?.visibility ?? 'WORKSPACE') as NoteVisibility,\n });\n\n // Tag picker state\n const [selectedTagIds, setSelectedTagIds] = useState<Set<string>>(new Set());\n const [tagsInitialized, setTagsInitialized] = useState(false);\n const [showTagPicker, setShowTagPicker] = useState(false);\n\n // Fetch available tags from wspace-tags-svc\n const { data: tagsData, refetch: refetchTags } = useAvailableNoteTagsQuery();\n const availableTags: SelectedTag[] = (tagsData?.availableNoteTags ?? []).map(\n (t: { id: string; key: string; label: string; color?: string | null }) => ({\n id: t.id,\n key: t.key,\n label: t.label,\n color: t.color ?? null,\n })\n );\n\n // Initialize selected tags once available tags load\n useEffect(() => {\n if (tagsInitialized || availableTags.length === 0) return;\n const ids = new Set<string>();\n\n // First: pre-select from tagAssignments (tags service data)\n if (note?.tagAssignments && note.tagAssignments.length > 0) {\n note.tagAssignments.forEach((a: NoteTagAssignment) => ids.add(a.tagId));\n }\n // Fallback: match legacy string tags by label/key to available tags\n else if (note?.tags && note.tags.length > 0) {\n for (const legacyTag of note.tags) {\n const match = availableTags.find(\n (t) =>\n t.label.toLowerCase() === legacyTag.toLowerCase() || t.key === legacyTag.toLowerCase()\n );\n if (match) ids.add(match.id);\n }\n }\n\n if (ids.size > 0) setSelectedTagIds(ids);\n setTagsInitialized(true);\n }, [availableTags, note, tagsInitialized]);\n\n // Inline tag creation\n const [createNoteTag] = useCreateNoteTagMutation();\n const [newTagLabel, setNewTagLabel] = useState('');\n const [newTagColor, setNewTagColor] = useState('var(--color-accent-purple)');\n const [isCreatingTag, setIsCreatingTag] = useState(false);\n\n const handleCreateTag = async () => {\n const label = newTagLabel.trim();\n if (!label || isCreatingTag) return;\n setIsCreatingTag(true);\n try {\n const result = await createNoteTag({\n variables: { input: { label, color: newTagColor } },\n });\n const newTag = result.data?.createNoteTag;\n if (newTag) {\n setSelectedTagIds((prev) => new Set(prev).add(newTag.id));\n await refetchTags();\n }\n setNewTagLabel('');\n } catch (err) {\n console.error('Failed to create tag:', err);\n } finally {\n setIsCreatingTag(false);\n }\n };\n\n const toggleTag = (tagId: string) => {\n setSelectedTagIds((prev) => {\n const next = new Set(prev);\n if (next.has(tagId)) {\n next.delete(tagId);\n } else {\n next.add(tagId);\n }\n return next;\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const data: NoteEditorSubmitData = {\n ...(note?.id ? { id: note.id } : {}),\n workspaceId: workspaceId ?? '',\n title: formData.title,\n content: formData.content,\n vibeId: formData.vibeId || undefined,\n isPinned: formData.isPinned,\n visibility: formData.visibility,\n selectedTagIds: Array.from(selectedTagIds),\n };\n onSubmit(data);\n };\n\n // Enhanced markdown preview with inline formatting support\n const parseInlineFormatting = (text: string): React.ReactNode[] => {\n const parts: React.ReactNode[] = [];\n let remaining = text;\n let key = 0;\n\n while (remaining.length > 0) {\n // Bold: **text** or __text__\n const boldMatch = remaining.match(/^(\\*\\*|__)(.+?)\\1/);\n if (boldMatch) {\n parts.push(<strong key={key++}>{parseInlineFormatting(boldMatch[2])}</strong>);\n remaining = remaining.slice(boldMatch[0].length);\n continue;\n }\n\n // Italic: *text* or _text_\n const italicMatch = remaining.match(/^(\\*|_)([^*_]+?)\\1/);\n if (italicMatch) {\n parts.push(<em key={key++}>{parseInlineFormatting(italicMatch[2])}</em>);\n remaining = remaining.slice(italicMatch[0].length);\n continue;\n }\n\n // Strikethrough: ~~text~~\n const strikeMatch = remaining.match(/^~~(.+?)~~/);\n if (strikeMatch) {\n parts.push(<del key={key++}>{parseInlineFormatting(strikeMatch[1])}</del>);\n remaining = remaining.slice(strikeMatch[0].length);\n continue;\n }\n\n // Inline code: `code`\n const codeMatch = remaining.match(/^`([^`]+)`/);\n if (codeMatch) {\n parts.push(\n <code\n key={key++}\n className=\"bg-bg-elevated px-1.5 py-0.5 rounded text-sm font-mono text-text-primary\"\n >\n {codeMatch[1]}\n </code>\n );\n remaining = remaining.slice(codeMatch[0].length);\n continue;\n }\n\n // Links: [text](url) - only allow safe URL schemes to prevent XSS\n const linkMatch = remaining.match(/^\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n if (linkMatch) {\n const url = linkMatch[2];\n // Only allow http, https, mailto, relative paths, and hash links to prevent XSS via javascript: URLs\n const isSafeUrl = /^(https?:\\/\\/|mailto:|\\/|#)/i.test(url);\n if (isSafeUrl) {\n parts.push(\n <a\n key={key++}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-action-primary-bg hover:underline\"\n >\n {linkMatch[1]}\n </a>\n );\n } else {\n // Render unsafe links as plain text with muted styling\n parts.push(\n <span key={key++} className=\"text-text-secondary\">\n {linkMatch[1]}\n </span>\n );\n }\n remaining = remaining.slice(linkMatch[0].length);\n continue;\n }\n\n // No match - take the next character\n parts.push(remaining[0]);\n remaining = remaining.slice(1);\n }\n\n return parts;\n };\n\n // Parse markdown content into React elements\n const renderMarkdown = (text: string) => {\n const lines = text.split('\\n');\n const elements: React.ReactNode[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // Code blocks: ```\n if (line.startsWith('```')) {\n const language = line.slice(3).trim();\n const codeLines: string[] = [];\n i++;\n while (i < lines.length && !lines[i].startsWith('```')) {\n codeLines.push(lines[i]);\n i++;\n }\n elements.push(\n <pre key={elements.length} className=\"bg-bg-elevated rounded-md p-3 my-2 overflow-x-auto\">\n <code className=\"text-sm font-mono text-text-primary\">\n {language && (\n <span className=\"text-xs text-text-secondary block mb-2\">{language}</span>\n )}\n {codeLines.join('\\n')}\n </code>\n </pre>\n );\n i++;\n continue;\n }\n\n // Headings\n if (line.startsWith('# ')) {\n elements.push(\n <h1 key={elements.length} className=\"text-2xl font-bold mb-2 mt-4\">\n {parseInlineFormatting(line.slice(2))}\n </h1>\n );\n i++;\n continue;\n }\n if (line.startsWith('## ')) {\n elements.push(\n <h2 key={elements.length} className=\"text-xl font-bold mb-2 mt-3\">\n {parseInlineFormatting(line.slice(3))}\n </h2>\n );\n i++;\n continue;\n }\n if (line.startsWith('### ')) {\n elements.push(\n <h3 key={elements.length} className=\"text-lg font-bold mb-2 mt-2\">\n {parseInlineFormatting(line.slice(4))}\n </h3>\n );\n i++;\n continue;\n }\n\n // Blockquotes: > text\n if (line.startsWith('> ')) {\n elements.push(\n <blockquote\n key={elements.length}\n className=\"border-l-4 border-border-default pl-4 my-2 italic text-text-secondary\"\n >\n {parseInlineFormatting(line.slice(2))}\n </blockquote>\n );\n i++;\n continue;\n }\n\n // Unordered list: - or * (collect consecutive items and wrap in <ul>)\n if (line.match(/^[-*] /)) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length && lines[i].match(/^[-*] /)) {\n listItems.push(\n <li key={listItems.length} className=\"list-disc\">\n {parseInlineFormatting(lines[i].slice(2))}\n </li>\n );\n i++;\n }\n elements.push(\n <ul key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ul>\n );\n continue;\n }\n\n // Ordered list: 1. 2. etc. (collect consecutive items and wrap in <ol>)\n const orderedMatch = line.match(/^(\\d+)\\. /);\n if (orderedMatch) {\n const listItems: React.ReactNode[] = [];\n while (i < lines.length) {\n const olMatch = lines[i].match(/^(\\d+)\\. /);\n if (!olMatch) break;\n listItems.push(\n <li key={listItems.length} className=\"list-decimal\">\n {parseInlineFormatting(lines[i].slice(olMatch[0].length))}\n </li>\n );\n i++;\n }\n elements.push(\n <ol key={elements.length} className=\"ml-6 my-2\">\n {listItems}\n </ol>\n );\n continue;\n }\n\n // Horizontal rule: --- or ***\n if (line.match(/^(-{3,}|\\*{3,})$/)) {\n elements.push(<hr key={elements.length} className=\"my-4 border-border-default\" />);\n i++;\n continue;\n }\n\n // Table detection (simple): | col1 | col2 |\n if (line.startsWith('|') && line.endsWith('|')) {\n const tableLines: string[] = [line];\n i++;\n while (i < lines.length && lines[i].startsWith('|') && lines[i].endsWith('|')) {\n tableLines.push(lines[i]);\n i++;\n }\n\n // Parse table\n const rows = tableLines\n .filter((l) => !l.match(/^\\|[\\s\\-:|]+\\|$/)) // Skip separator rows (including alignment markers like :---:)\n .map((l) =>\n l\n .slice(1, -1)\n .split('|')\n .map((cell) => cell.trim())\n );\n\n if (rows.length > 0) {\n const [header, ...body] = rows;\n elements.push(\n <table key={elements.length} className=\"w-full my-2 border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-default\">\n {header.map((cell, ci) => (\n <th key={ci} className=\"text-left py-2 px-3 font-semibold text-text-primary\">\n {parseInlineFormatting(cell)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {body.map((row, ri) => (\n <tr key={ri} className=\"border-b border-border-subtle\">\n {row.map((cell, ci) => (\n <td key={ci} className=\"py-2 px-3 text-text-primary\">\n {parseInlineFormatting(cell)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n continue;\n }\n\n // Empty line\n if (line === '') {\n elements.push(<br key={elements.length} />);\n i++;\n continue;\n }\n\n // Regular paragraph with inline formatting\n elements.push(\n <p key={elements.length} className=\"mb-1\">\n {parseInlineFormatting(line)}\n </p>\n );\n i++;\n }\n\n return elements;\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <div className=\"w-full max-w-3xl bg-bg-surface rounded-lg shadow-xl max-h-[90vh] flex flex-col\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {note\n ? tr('vibecontrols.notes.editNote', 'Edit Note')\n : tr('vibecontrols.notes.newNote', 'New Note')}\n </h2>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsPreview(!isPreview)}\n className={`inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${\n isPreview\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-elevated'\n }`}\n >\n {isPreview ? <Edit className=\"size-4\" /> : <Eye className=\"size-4\" />}\n {isPreview\n ? tr('vibecontrols.notes.edit', 'Edit')\n : tr('vibecontrols.notes.preview', 'Preview')}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex-1 flex flex-col overflow-hidden\">\n <div className=\"p-6 space-y-4 flex-1 overflow-y-auto\">\n <div>\n <input\n aria-label=\"control\"\n type=\"text\"\n value={formData.title}\n onChange={(e) => setFormData({ ...formData, title: e.target.value })}\n placeholder={tr('vibecontrols.notes.titlePlaceholder', 'Note title...')}\n required\n className=\"w-full px-0 py-2 text-lg sm:text-2xl font-bold bg-transparent border-0 border-b border-border-default text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-action-primary-bg\"\n />\n </div>\n\n <div className={`grid ${hideVibeSelect ? 'grid-cols-1' : 'grid-cols-2'} gap-4`}>\n {!hideVibeSelect && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.relatedVibe', 'Related Vibe (optional)')}\n </label>\n <select\n value={formData.vibeId}\n onChange={(e) => setFormData({ ...formData, vibeId: e.target.value })}\n className=\"w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">No vibe</option>\n {vibeOptions.map((vibe) => (\n <option key={vibe.id} value={vibe.id}>\n {vibe.name}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('vibecontrols.notes.tags', 'Tags')}\n </label>\n {/* Selected tags */}\n <div className=\"flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md\">\n {availableTags\n .filter((t) => selectedTagIds.has(t.id))\n .map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[var(--color-accent-soft)] text-action-primary-bg rounded\"\n >\n <Tag className=\"size-3\" />\n {tag.label}\n <button\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"ml-0.5 hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowTagPicker(!showTagPicker)}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs text-text-secondary hover:text-text-primary hover:bg-bg-elevated rounded transition-colors\"\n >\n <Tag className=\"size-3\" />\n {selectedTagIds.size === 0 ? 'Add tags...' : '+'}\n </button>\n </div>\n {/* Tag picker dropdown */}\n {showTagPicker && (\n <div className=\"mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg\">\n {/* Inline create: text input + color picker */}\n <div className=\"flex items-center gap-2 p-2 border-b border-border-subtle\">\n <span\n aria-hidden=\"true\"\n className=\"size-6 rounded-full border-2 border-border-default\"\n style={{ backgroundColor: newTagColor }}\n />\n <input\n aria-label=\"New tag name... (Enter to create)\"\n type=\"text\"\n value={newTagLabel}\n onChange={(e) => setNewTagLabel(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCreateTag();\n }\n }}\n placeholder=\"New tag name... (Enter to create)\"\n disabled={isCreatingTag}\n className=\"flex-1 px-2 py-1 text-sm bg-bg-sunken border border-border-default rounded text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={handleCreateTag}\n disabled={!newTagLabel.trim() || isCreatingTag}\n className=\"p-1 text-action-primary-bg hover:bg-[var(--color-accent-soft)] rounded transition-colors disabled:opacity-30\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n {/* Quick color palette */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 border-b border-border-subtle\">\n {TAG_COLORS.map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setNewTagColor(c)}\n className={`size-4 rounded-full transition-transform ${newTagColor === c ? 'ring-2 ring-action-primary-bg scale-125' : 'hover:scale-110'}`}\n style={{ backgroundColor: c }}\n />\n ))}\n </div>\n {/* Existing tags list */}\n <div className=\"max-h-32 overflow-y-auto p-1\">\n {availableTags.length === 0 ? (\n <p className=\"text-xs text-text-muted px-2 py-1\">\n No tags yet — create one above\n </p>\n ) : (\n availableTags.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => toggleTag(tag.id)}\n className=\"flex items-center justify-between w-full px-2 py-1.5 text-sm text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n <span className=\"flex items-center gap-2\">\n {tag.color && (\n <span\n className=\"size-2.5 rounded-full\"\n style={{ backgroundColor: tag.color }}\n />\n )}\n {tag.label}\n </span>\n {selectedTagIds.has(tag.id) && (\n <Check className=\"size-4 text-action-primary-bg\" />\n )}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-h-[300px]\">\n {isPreview ? (\n <div className=\"h-full p-4 bg-bg-sunken rounded-md prose prose-sm dark:prose-invert max-w-none overflow-y-auto\">\n {renderMarkdown(formData.content)}\n </div>\n ) : (\n <textarea\n aria-label=\"control\"\n value={formData.content}\n onChange={(e) => setFormData({ ...formData, content: e.target.value })}\n placeholder={tr(\n 'vibecontrols.notes.contentPlaceholder',\n 'Write your note in Markdown...'\n )}\n className=\"w-full h-full min-h-[300px] px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none font-mono\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-4 flex-wrap\">\n <div className=\"flex items-center gap-2\">\n <input\n aria-label=\"isPinned\"\n type=\"checkbox\"\n id=\"isPinned\"\n checked={formData.isPinned}\n onChange={(e) => setFormData({ ...formData, isPinned: e.target.checked })}\n className=\"size-4 rounded border-border-default text-action-primary-bg focus:ring-action-primary-bg\"\n />\n <label htmlFor=\"isPinned\" className=\"text-sm text-text-primary\">\n {tr('vibecontrols.notes.pinThisNote', 'Pin this note')}\n </label>\n </div>\n\n {/* Visibility toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-lg p-0.5\">\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'WORKSPACE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'WORKSPACE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Globe className=\"size-3.5\" />\n Workspace\n </button>\n <button\n type=\"button\"\n onClick={() => setFormData({ ...formData, visibility: 'PRIVATE' })}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${\n formData.visibility === 'PRIVATE'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-secondary'\n }`}\n >\n <Lock className=\"size-3.5\" />\n Private\n </button>\n </div>\n {formData.visibility === 'PRIVATE' && (\n <p className=\"text-xs text-text-muted w-full -mt-2\">Only you can see this note.</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-default bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.notes.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n <Save className=\"size-4\" />\n {isLoading\n ? tr('vibecontrols.notes.saving', 'Saving...')\n : tr('vibecontrols.notes.saveNote', 'Save Note')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -54,7 +54,7 @@ function g({ note: e, onOpen: t, onEdit: u, onDelete: g, onPin: _, onUnpin: v, o
|
|
|
54
54
|
children: e.tagAssignments && e.tagAssignments.length > 0 ? /* @__PURE__ */ h("div", {
|
|
55
55
|
className: "flex flex-wrap gap-1",
|
|
56
56
|
children: [e.tagAssignments.slice(0, 2).map((e) => /* @__PURE__ */ h("span", {
|
|
57
|
-
className: "inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-
|
|
57
|
+
className: "inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg rounded",
|
|
58
58
|
style: e.tag?.color ? { borderLeft: `2px solid ${e.tag.color}` } : void 0,
|
|
59
59
|
children: [/* @__PURE__ */ m(f, { className: "size-2.5" }), e.tag?.label ?? e.tagId.slice(0, 8)]
|
|
60
60
|
}, e.id)), e.tagAssignments.length > 2 && /* @__PURE__ */ h("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NoteList.js","names":[],"sources":["../../../src/components/notes/NoteList.tsx"],"sourcesContent":["import type { Note } from './types';\nimport { EmptyState } from '../shared/EmptyState';\nimport {\n FileText,\n Plus,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface NoteListProps {\n notes: Note[];\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /** Generic-sharing entry point (full Share dialog); see NoteCard.onShareAccess. */\n onShareAccess?: (note: Note) => void;\n onCreateNew?: () => void;\n isLoading?: boolean;\n}\n\nfunction NoteRow({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n onShareAccess?: (note: Note) => void;\n}) {\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 80 ? text.slice(0, 80) + '...' : text;\n };\n\n return (\n <tr\n className=\"border-b border-border-subtle hover:bg-bg-sunken/50 cursor-pointer transition-colors\"\n onClick={() => onOpen?.(note)}\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileText className=\"size-4 text-status-warning-text shrink-0\" />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{note.title}</p>\n {note.isPinned && <Pin className=\"size-3 text-status-warning-text shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private\">\n <Lock className=\"size-3 text-text-muted shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace\">\n <Globe className=\"size-3 text-text-muted shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-muted truncate max-w-[300px]\">\n {getPreview(note.content)}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary truncate block max-w-[100px]\">\n {note.vibe?.name || '-'}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 2).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-action-primary-bg/10 text-action-primary-bg rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `2px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-2.5\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 2}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tags.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-2.5\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 2}\n </span>\n )}\n </div>\n ) : (\n <span className=\"text-xs text-text-muted\">-</span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-xs text-text-muted\">{formatDate(note.updatedAt)}</span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1 justify-end\">\n {(onEdit || onDelete || onPin || onUnpin || onShare || onShareAccess) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-3.5 text-text-secondary\" />\n </button>\n {showMenu && (\n <div className=\"absolute right-0 mt-1 w-44 bg-bg-surface border border-border-default rounded-lg shadow-lg z-50\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </button>\n )}\n {onShareAccess && (\n <button\n type=\"button\"\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </button>\n )}\n {note.isPinned && onUnpin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </button>\n )}\n {!note.isPinned && onPin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </button>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </td>\n </tr>\n );\n}\n\nexport function NoteList({\n notes,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n onCreateNew,\n isLoading,\n}: NoteListProps) {\n const tr = useTr();\n if (!isLoading && notes.length === 0) {\n return (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.notes.emptyTitle', 'No notes yet')}\n description={tr(\n 'vibecontrols.notes.emptyDescription',\n 'Notes help you document your development process. Create your first note to get started.'\n )}\n action={\n onCreateNew && (\n <button\n type=\"button\"\n onClick={onCreateNew}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.notes.createNote', 'Create Note')}\n </button>\n )\n }\n />\n );\n }\n\n // Sort: pinned first, then by updated date\n const sortedNotes = [...notes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n\n return (\n <div className=\"border border-border-default rounded-lg\">\n <div className=\"overflow-x-auto rounded-lg\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tl-lg\">\n {tr('vibecontrols.notes.columnTitle', 'Title')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnVibe', 'Vibe')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnTags', 'Tags')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnUpdated', 'Updated')}\n </th>\n <th className=\"px-4 py-2.5 text-right text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tr-lg\">\n {tr('vibecontrols.notes.columnActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {sortedNotes.map((note) => (\n <NoteRow\n key={note.id}\n note={note}\n onOpen={onOpen}\n onEdit={onEdit}\n onDelete={onDelete}\n onPin={onPin}\n onUnpin={onUnpin}\n onShare={onShare}\n onShareAccess={onShareAccess}\n />\n ))}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAgCA,SAAS,EAAQ,EACf,SACA,WACA,WACA,aACA,UACA,YACA,YACA,oBAUC;CACD,IAAM,CAAC,GAAU,KAAe,EAAS,GAAM;AAkB/C,QACE,kBAAC,MAAD;EACE,WAAU;EACV,eAAe,IAAS,EAAK;YAF/B;GAIE,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,WAAU,4CAA6C,CAAA,EACjE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,KAAD;SAAG,WAAU;mBAAkD,EAAK;SAAU,CAAA;QAC7E,EAAK,YAAY,kBAAC,GAAD,EAAK,WAAU,4CAA6C,CAAA;QAC7E,EAAK,eAAe,YACnB,kBAAC,QAAD;SAAM,OAAM;mBACV,kBAAC,GAAD,EAAM,WAAU,mCAAoC,CAAA;SAC/C,CAAA,GAEP,kBAAC,QAAD;SAAM,OAAM;mBACV,kBAAC,GAAD,EAAO,WAAU,mCAAoC,CAAA;SAChD,CAAA;QAEL;UACN,kBAAC,KAAD;OAAG,WAAU;mBA9BH,MAAoB;QACtC,IAAM,IAAO,EACV,QAAQ,YAAY,GAAG,CACvB,QAAQ,OAAO,IAAI,CACnB,MAAM;AACT,eAAO,EAAK,SAAS,KAAK,EAAK,MAAM,GAAG,GAAG,GAAG,QAAQ;UA0BhC,EAAK,QAAQ;OACvB,CAAA,CACA;QACF;;IACH,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,QAAD;KAAM,WAAU;eACb,EAAK,MAAM,QAAQ;KACf,CAAA;IACJ,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cAEX,EAAK,kBAAkB,EAAK,eAAe,SAAS,IACnD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAK,eAAe,MAAM,GAAG,EAAE,CAAC,KAAK,MACpC,kBAAC,QAAD;MAEE,WAAU;MACV,OACE,EAAW,KAAK,QACZ,EAAE,YAAY,aAAa,EAAW,IAAI,SAAS,GACnD,KAAA;gBANR,CASE,kBAAC,GAAD,EAAK,WAAU,YAAa,CAAA,EAC3B,EAAW,KAAK,SAAS,EAAW,MAAM,MAAM,GAAG,EAAE,CACjD;QAVA,EAAW,GAUX,CACP,EACD,EAAK,eAAe,SAAS,KAC5B,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CAAiF,KAC7E,EAAK,eAAe,SAAS,EAC1B;QAEL;SACJ,EAAK,QAAQ,EAAK,KAAK,SAAS,IAClC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAK,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC1B,kBAAC,QAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,GAAD,EAAK,WAAU,YAAa,CAAA,EAC3B,EACI;QALA,EAKA,CACP,EACD,EAAK,KAAK,SAAS,KAClB,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CAAiF,KAC7E,EAAK,KAAK,SAAS,EAChB;QAEL;SAEN,kBAAC,QAAD;KAAM,WAAU;eAA0B;KAAQ,CAAA;IAEjD,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,QAAD;KAAM,WAAU;iBA/FF,MACX,IAAI,KAAK,EAAQ,CAAC,mBAAmB,SAAS;MACnD,OAAO;MACP,KAAK;MACL,MAAM;MACP,CAAC,EA0FwD,EAAK,UAAU;KAAQ,CAAA;IAC1E,CAAA;GACL,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,OAAD;KAAK,WAAU;gBACX,KAAU,KAAY,KAAS,KAAW,KAAW,MACrD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAM;AAEd,QADA,EAAE,iBAAiB,EACnB,EAAY,CAAC,EAAS;;OAExB,WAAU;iBAEV,kBAAC,GAAD,EAAc,WAAU,gCAAiC,CAAA;OAClD,CAAA,EACR,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,KACC,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAO,EAAK,EACZ,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,OAEpB;;QAEV,KAAW,EAAK,eAAe,aAC9B,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAK,EACb,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,uBAEtB;;QAEV,KAAW,EAAK,eAAe,eAC9B,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAK,EACb,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,eAEpB;;QAEV,KACC,kBAAC,UAAD;SACE,MAAK;SACL,eAAY;SACZ,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAc,EAAK,EACnB,EAAY,GAAM;;SAEpB,WAAU;mBARZ,CAUE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,gBAEtB;;QAEV,EAAK,YAAY,KAChB,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAQ,EAAK,EACb,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,QAEtB;;QAEV,CAAC,EAAK,YAAY,KACjB,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAM,EAAK,EACX,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA,EAAA,MAEnB;;QAEV,KACC,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,MAAM;AAGd,UAFA,EAAE,iBAAiB,EACnB,EAAS,EAAK,EACd,EAAY,GAAM;;SAEpB,WAAU;mBAPZ,CASE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,EAAA,SAEtB;;QAEP;SAEJ;;KAEJ,CAAA;IACH,CAAA;GACF;;;AAIT,SAAgB,EAAS,EACvB,UACA,WACA,WACA,aACA,UACA,YACA,YACA,kBACA,gBACA,gBACgB;CAChB,IAAM,IAAK,GAAO;AAClB,KAAI,CAAC,KAAa,EAAM,WAAW,EACjC,QACE,kBAAC,GAAD;EACE,MAAM,kBAAC,GAAD,EAAU,WAAU,8BAA+B,CAAA;EACzD,OAAO,EAAG,iCAAiC,eAAe;EAC1D,aAAa,EACX,uCACA,2FACD;EACD,QACE,KACE,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,iCAAiC,cAAc,CAC5C;;EAGb,CAAA;CAKN,IAAM,IAAc,CAAC,GAAG,EAAM,CAAC,MAAM,GAAG,MAClC,EAAE,YAAY,CAAC,EAAE,WAAiB,KAClC,CAAC,EAAE,YAAY,EAAE,WAAiB,IAC/B,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CACxE;AAEF,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;KAAI,WAAU;eAAd;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,kCAAkC,QAAQ;OAC3C,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,iCAAiC,OAAO;OACzC,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,iCAAiC,OAAO;OACzC,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,oCAAoC,UAAU;OAC/C,CAAA;MACL,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,oCAAoC,UAAU;OAC/C,CAAA;MACF;QACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAY,KAAK,MAChB,kBAAC,GAAD;KAEQ;KACE;KACA;KACE;KACH;KACE;KACA;KACM;KACf,EATK,EAAK,GASV,CACF,EACI,CAAA,CACF;;GACJ,CAAA;EACF,CAAA"}
|
|
1
|
+
{"version":3,"file":"NoteList.js","names":[],"sources":["../../../src/components/notes/NoteList.tsx"],"sourcesContent":["import type { Note } from './types';\nimport { EmptyState } from '../shared/EmptyState';\nimport {\n FileText,\n Plus,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface NoteListProps {\n notes: Note[];\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /** Generic-sharing entry point (full Share dialog); see NoteCard.onShareAccess. */\n onShareAccess?: (note: Note) => void;\n onCreateNew?: () => void;\n isLoading?: boolean;\n}\n\nfunction NoteRow({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n onShareAccess?: (note: Note) => void;\n}) {\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 80 ? text.slice(0, 80) + '...' : text;\n };\n\n return (\n <tr\n className=\"border-b border-border-subtle hover:bg-bg-sunken/50 cursor-pointer transition-colors\"\n onClick={() => onOpen?.(note)}\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <FileText className=\"size-4 text-status-warning-text shrink-0\" />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{note.title}</p>\n {note.isPinned && <Pin className=\"size-3 text-status-warning-text shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private\">\n <Lock className=\"size-3 text-text-muted shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace\">\n <Globe className=\"size-3 text-text-muted shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-muted truncate max-w-[300px]\">\n {getPreview(note.content)}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-sm text-text-secondary truncate block max-w-[100px]\">\n {note.vibe?.name || '-'}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 2).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-[var(--color-accent-soft)] text-action-primary-bg rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `2px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-2.5\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 2}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"flex flex-wrap gap-1\">\n {note.tags.slice(0, 2).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-2.5\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 2 && (\n <span className=\"px-1.5 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 2}\n </span>\n )}\n </div>\n ) : (\n <span className=\"text-xs text-text-muted\">-</span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-xs text-text-muted\">{formatDate(note.updatedAt)}</span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-1 justify-end\">\n {(onEdit || onDelete || onPin || onUnpin || onShare || onShareAccess) && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-3.5 text-text-secondary\" />\n </button>\n {showMenu && (\n <div className=\"absolute right-0 mt-1 w-44 bg-bg-surface border border-border-default rounded-lg shadow-lg z-50\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </button>\n )}\n {onShareAccess && (\n <button\n type=\"button\"\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </button>\n )}\n {note.isPinned && onUnpin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </button>\n )}\n {!note.isPinned && onPin && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </button>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </td>\n </tr>\n );\n}\n\nexport function NoteList({\n notes,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n onCreateNew,\n isLoading,\n}: NoteListProps) {\n const tr = useTr();\n if (!isLoading && notes.length === 0) {\n return (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={tr('vibecontrols.notes.emptyTitle', 'No notes yet')}\n description={tr(\n 'vibecontrols.notes.emptyDescription',\n 'Notes help you document your development process. Create your first note to get started.'\n )}\n action={\n onCreateNew && (\n <button\n type=\"button\"\n onClick={onCreateNew}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('vibecontrols.notes.createNote', 'Create Note')}\n </button>\n )\n }\n />\n );\n }\n\n // Sort: pinned first, then by updated date\n const sortedNotes = [...notes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n\n return (\n <div className=\"border border-border-default rounded-lg\">\n <div className=\"overflow-x-auto rounded-lg\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tl-lg\">\n {tr('vibecontrols.notes.columnTitle', 'Title')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnVibe', 'Vibe')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnTags', 'Tags')}\n </th>\n <th className=\"px-4 py-2.5 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('vibecontrols.notes.columnUpdated', 'Updated')}\n </th>\n <th className=\"px-4 py-2.5 text-right text-xs font-medium text-text-secondary uppercase tracking-wider rounded-tr-lg\">\n {tr('vibecontrols.notes.columnActions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {sortedNotes.map((note) => (\n <NoteRow\n key={note.id}\n note={note}\n onOpen={onOpen}\n onEdit={onEdit}\n onDelete={onDelete}\n onPin={onPin}\n onUnpin={onUnpin}\n onShare={onShare}\n onShareAccess={onShareAccess}\n />\n ))}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -372,7 +372,7 @@ function h({ context: h, vibeId: g, sessionId: _, agentVibes: v = [] }) {
|
|
|
372
372
|
children: /* @__PURE__ */ m("button", {
|
|
373
373
|
type: "button",
|
|
374
374
|
onClick: () => He(!A),
|
|
375
|
-
className: `flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${A ? "bg-status-warning-bg text-status-warning-text" : "bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80"}`,
|
|
375
|
+
className: `flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${A ? "bg-status-warning-bg-subtle text-status-warning-text" : "bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80"}`,
|
|
376
376
|
children: [
|
|
377
377
|
/* @__PURE__ */ p(xe, { className: "size-3" }),
|
|
378
378
|
"Pinned (",
|