@nocturnium/svelte-ide 1.0.0-rc.1
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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/components/agents/AgentActivityPanel.svelte +565 -0
- package/dist/components/agents/AgentActivityPanel.svelte.d.ts +24 -0
- package/dist/components/agents/AgentAvatar.svelte +417 -0
- package/dist/components/agents/AgentAvatar.svelte.d.ts +23 -0
- package/dist/components/agents/AgentCursor.svelte +224 -0
- package/dist/components/agents/AgentCursor.svelte.d.ts +35 -0
- package/dist/components/agents/AgentPresenceBar.svelte +261 -0
- package/dist/components/agents/AgentPresenceBar.svelte.d.ts +20 -0
- package/dist/components/agents/index.d.ts +4 -0
- package/dist/components/agents/index.js +5 -0
- package/dist/components/ai/AIConversationList.svelte +524 -0
- package/dist/components/ai/AIConversationList.svelte.d.ts +17 -0
- package/dist/components/ai/AIEditPreview.svelte +132 -0
- package/dist/components/ai/AIEditPreview.svelte.d.ts +8 -0
- package/dist/components/ai/AIInlineEdit.svelte +155 -0
- package/dist/components/ai/AIInlineEdit.svelte.d.ts +10 -0
- package/dist/components/ai/AIMessage.svelte +239 -0
- package/dist/components/ai/AIMessage.svelte.d.ts +13 -0
- package/dist/components/ai/AIMessageActions.svelte +176 -0
- package/dist/components/ai/AIMessageActions.svelte.d.ts +12 -0
- package/dist/components/ai/AIMessageContent.svelte +355 -0
- package/dist/components/ai/AIMessageContent.svelte.d.ts +7 -0
- package/dist/components/ai/AIPanel.svelte +561 -0
- package/dist/components/ai/AIPanel.svelte.d.ts +7 -0
- package/dist/components/ai/AISuggestionWidget.svelte +132 -0
- package/dist/components/ai/AISuggestionWidget.svelte.d.ts +10 -0
- package/dist/components/ai/AIToolCallDisplay.svelte +317 -0
- package/dist/components/ai/AIToolCallDisplay.svelte.d.ts +12 -0
- package/dist/components/ai/index.d.ts +9 -0
- package/dist/components/ai/index.js +10 -0
- package/dist/components/core/Avatar.svelte +110 -0
- package/dist/components/core/Avatar.svelte.d.ts +12 -0
- package/dist/components/core/Badge.svelte +98 -0
- package/dist/components/core/Badge.svelte.d.ts +11 -0
- package/dist/components/core/Button.svelte +175 -0
- package/dist/components/core/Button.svelte.d.ts +18 -0
- package/dist/components/core/ConnectionStatus.svelte +294 -0
- package/dist/components/core/ConnectionStatus.svelte.d.ts +20 -0
- package/dist/components/core/ContextMenu.svelte +176 -0
- package/dist/components/core/ContextMenu.svelte.d.ts +19 -0
- package/dist/components/core/ErrorBoundary.svelte +277 -0
- package/dist/components/core/ErrorBoundary.svelte.d.ts +23 -0
- package/dist/components/core/Icon.svelte +107 -0
- package/dist/components/core/Icon.svelte.d.ts +8 -0
- package/dist/components/core/Input.svelte +138 -0
- package/dist/components/core/Input.svelte.d.ts +20 -0
- package/dist/components/core/Kbd.svelte +34 -0
- package/dist/components/core/Kbd.svelte.d.ts +7 -0
- package/dist/components/core/ResizeHandle.svelte +200 -0
- package/dist/components/core/ResizeHandle.svelte.d.ts +23 -0
- package/dist/components/core/Spinner.svelte +35 -0
- package/dist/components/core/Spinner.svelte.d.ts +7 -0
- package/dist/components/core/Textarea.svelte +112 -0
- package/dist/components/core/Textarea.svelte.d.ts +18 -0
- package/dist/components/core/Tooltip.svelte +103 -0
- package/dist/components/core/Tooltip.svelte.d.ts +11 -0
- package/dist/components/core/index.d.ts +13 -0
- package/dist/components/core/index.js +14 -0
- package/dist/components/editor/AIFocusLayer.svelte +430 -0
- package/dist/components/editor/AIFocusLayer.svelte.d.ts +32 -0
- package/dist/components/editor/Breadcrumbs.svelte +435 -0
- package/dist/components/editor/Breadcrumbs.svelte.d.ts +33 -0
- package/dist/components/editor/BreakpointLayer.svelte +642 -0
- package/dist/components/editor/BreakpointLayer.svelte.d.ts +20 -0
- package/dist/components/editor/CognitiveLoadMeter.svelte +324 -0
- package/dist/components/editor/CognitiveLoadMeter.svelte.d.ts +18 -0
- package/dist/components/editor/CollaborativeEditor.svelte +218 -0
- package/dist/components/editor/CollaborativeEditor.svelte.d.ts +32 -0
- package/dist/components/editor/CommandPalette.svelte +434 -0
- package/dist/components/editor/CommandPalette.svelte.d.ts +11 -0
- package/dist/components/editor/ComplexityLayer.svelte +293 -0
- package/dist/components/editor/ComplexityLayer.svelte.d.ts +23 -0
- package/dist/components/editor/ConflictZoneLayer.svelte +441 -0
- package/dist/components/editor/ConflictZoneLayer.svelte.d.ts +25 -0
- package/dist/components/editor/ContextLens.svelte +262 -0
- package/dist/components/editor/ContextLens.svelte.d.ts +27 -0
- package/dist/components/editor/CustomEditor.svelte +1242 -0
- package/dist/components/editor/CustomEditor.svelte.d.ts +37 -0
- package/dist/components/editor/DebugConsole.svelte +646 -0
- package/dist/components/editor/DebugConsole.svelte.d.ts +41 -0
- package/dist/components/editor/EchoCursorLayer.svelte +363 -0
- package/dist/components/editor/EchoCursorLayer.svelte.d.ts +24 -0
- package/dist/components/editor/Editor.svelte +61 -0
- package/dist/components/editor/Editor.svelte.d.ts +22 -0
- package/dist/components/editor/EditorGutter.svelte +119 -0
- package/dist/components/editor/EditorGutter.svelte.d.ts +19 -0
- package/dist/components/editor/EditorLines.svelte +182 -0
- package/dist/components/editor/EditorLines.svelte.d.ts +43 -0
- package/dist/components/editor/EditorPane.svelte +134 -0
- package/dist/components/editor/EditorPane.svelte.d.ts +9 -0
- package/dist/components/editor/EditorSelections.svelte +186 -0
- package/dist/components/editor/EditorSelections.svelte.d.ts +25 -0
- package/dist/components/editor/EditorTabs.svelte +170 -0
- package/dist/components/editor/EditorTabs.svelte.d.ts +12 -0
- package/dist/components/editor/FileExplorer.svelte +811 -0
- package/dist/components/editor/FileExplorer.svelte.d.ts +67 -0
- package/dist/components/editor/FileIcon.svelte +110 -0
- package/dist/components/editor/FileIcon.svelte.d.ts +10 -0
- package/dist/components/editor/FindReplace.svelte +448 -0
- package/dist/components/editor/FindReplace.svelte.d.ts +40 -0
- package/dist/components/editor/GhostBracketLayer.svelte +391 -0
- package/dist/components/editor/GhostBracketLayer.svelte.d.ts +24 -0
- package/dist/components/editor/GitBlameLayer.svelte +436 -0
- package/dist/components/editor/GitBlameLayer.svelte.d.ts +18 -0
- package/dist/components/editor/InlineDiagnosticsLayer.svelte +540 -0
- package/dist/components/editor/InlineDiagnosticsLayer.svelte.d.ts +35 -0
- package/dist/components/editor/InlineDiffLayer.svelte +337 -0
- package/dist/components/editor/InlineDiffLayer.svelte.d.ts +31 -0
- package/dist/components/editor/MinimalEditor.svelte +75 -0
- package/dist/components/editor/MinimalEditor.svelte.d.ts +6 -0
- package/dist/components/editor/MinimalEditor2.svelte +84 -0
- package/dist/components/editor/MinimalEditor2.svelte.d.ts +6 -0
- package/dist/components/editor/Minimap.svelte +327 -0
- package/dist/components/editor/Minimap.svelte.d.ts +34 -0
- package/dist/components/editor/PluginPreviewSandbox.svelte +793 -0
- package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +49 -0
- package/dist/components/editor/ProblemsPanel.svelte +628 -0
- package/dist/components/editor/ProblemsPanel.svelte.d.ts +25 -0
- package/dist/components/editor/QuickActionsMenu.svelte +403 -0
- package/dist/components/editor/QuickActionsMenu.svelte.d.ts +18 -0
- package/dist/components/editor/SnippetPalette.svelte +530 -0
- package/dist/components/editor/SnippetPalette.svelte.d.ts +16 -0
- package/dist/components/editor/StructureMap.svelte +431 -0
- package/dist/components/editor/StructureMap.svelte.d.ts +37 -0
- package/dist/components/editor/SymbolOutline.svelte +722 -0
- package/dist/components/editor/SymbolOutline.svelte.d.ts +44 -0
- package/dist/components/editor/TimelineScrubber.svelte +470 -0
- package/dist/components/editor/TimelineScrubber.svelte.d.ts +40 -0
- package/dist/components/editor/TokenRenderer.svelte +69 -0
- package/dist/components/editor/TokenRenderer.svelte.d.ts +15 -0
- package/dist/components/editor/constants.d.ts +32 -0
- package/dist/components/editor/constants.js +36 -0
- package/dist/components/editor/core/ai-awareness.d.ts +176 -0
- package/dist/components/editor/core/ai-awareness.js +210 -0
- package/dist/components/editor/core/bracket-healer.d.ts +189 -0
- package/dist/components/editor/core/bracket-healer.js +406 -0
- package/dist/components/editor/core/breakpoints.d.ts +203 -0
- package/dist/components/editor/core/breakpoints.js +414 -0
- package/dist/components/editor/core/commands.d.ts +108 -0
- package/dist/components/editor/core/commands.js +246 -0
- package/dist/components/editor/core/complexity-analyzer.d.ts +123 -0
- package/dist/components/editor/core/complexity-analyzer.js +376 -0
- package/dist/components/editor/core/conflict-predictor.d.ts +135 -0
- package/dist/components/editor/core/conflict-predictor.js +316 -0
- package/dist/components/editor/core/crdt-binding.d.ts +118 -0
- package/dist/components/editor/core/crdt-binding.js +286 -0
- package/dist/components/editor/core/diagnostics.d.ts +210 -0
- package/dist/components/editor/core/diagnostics.js +335 -0
- package/dist/components/editor/core/echo-cursor.d.ts +201 -0
- package/dist/components/editor/core/echo-cursor.js +267 -0
- package/dist/components/editor/core/folding.d.ts +124 -0
- package/dist/components/editor/core/folding.js +672 -0
- package/dist/components/editor/core/ghost-pair.d.ts +122 -0
- package/dist/components/editor/core/ghost-pair.js +221 -0
- package/dist/components/editor/core/git-blame.d.ts +170 -0
- package/dist/components/editor/core/git-blame.js +324 -0
- package/dist/components/editor/core/index.d.ts +26 -0
- package/dist/components/editor/core/index.js +24 -0
- package/dist/components/editor/core/keybindings.d.ts +79 -0
- package/dist/components/editor/core/keybindings.js +357 -0
- package/dist/components/editor/core/multi-cursor.d.ts +196 -0
- package/dist/components/editor/core/multi-cursor.js +521 -0
- package/dist/components/editor/core/navigation.d.ts +107 -0
- package/dist/components/editor/core/navigation.js +408 -0
- package/dist/components/editor/core/quick-actions.d.ts +189 -0
- package/dist/components/editor/core/quick-actions.js +427 -0
- package/dist/components/editor/core/search.d.ts +88 -0
- package/dist/components/editor/core/search.js +192 -0
- package/dist/components/editor/core/semantic-analyzer.d.ts +77 -0
- package/dist/components/editor/core/semantic-analyzer.js +424 -0
- package/dist/components/editor/core/snippet-manager.d.ts +202 -0
- package/dist/components/editor/core/snippet-manager.js +565 -0
- package/dist/components/editor/core/state.d.ts +367 -0
- package/dist/components/editor/core/state.js +900 -0
- package/dist/components/editor/core/timeline.d.ts +204 -0
- package/dist/components/editor/core/timeline.js +349 -0
- package/dist/components/editor/editor-find.d.ts +56 -0
- package/dist/components/editor/editor-find.js +148 -0
- package/dist/components/editor/editor-input.d.ts +77 -0
- package/dist/components/editor/editor-input.js +445 -0
- package/dist/components/editor/editor-multicursor.d.ts +21 -0
- package/dist/components/editor/editor-multicursor.js +196 -0
- package/dist/components/editor/editor-scroll.d.ts +14 -0
- package/dist/components/editor/editor-scroll.js +34 -0
- package/dist/components/editor/index.d.ts +15 -0
- package/dist/components/editor/index.js +21 -0
- package/dist/components/editor/languages.d.ts +62 -0
- package/dist/components/editor/languages.js +285 -0
- package/dist/components/editor/theme.d.ts +88 -0
- package/dist/components/editor/theme.js +139 -0
- package/dist/components/editor/tokenizer/base.d.ts +40 -0
- package/dist/components/editor/tokenizer/base.js +203 -0
- package/dist/components/editor/tokenizer/index.d.ts +56 -0
- package/dist/components/editor/tokenizer/index.js +215 -0
- package/dist/components/editor/tokenizer/languages/css.d.ts +17 -0
- package/dist/components/editor/tokenizer/languages/css.js +194 -0
- package/dist/components/editor/tokenizer/languages/go.d.ts +17 -0
- package/dist/components/editor/tokenizer/languages/go.js +220 -0
- package/dist/components/editor/tokenizer/languages/html.d.ts +24 -0
- package/dist/components/editor/tokenizer/languages/html.js +145 -0
- package/dist/components/editor/tokenizer/languages/javascript.d.ts +56 -0
- package/dist/components/editor/tokenizer/languages/javascript.js +452 -0
- package/dist/components/editor/tokenizer/languages/json.d.ts +12 -0
- package/dist/components/editor/tokenizer/languages/json.js +91 -0
- package/dist/components/editor/tokenizer/languages/markdown.d.ts +16 -0
- package/dist/components/editor/tokenizer/languages/markdown.js +156 -0
- package/dist/components/editor/tokenizer/languages/python.d.ts +20 -0
- package/dist/components/editor/tokenizer/languages/python.js +227 -0
- package/dist/components/editor/tokenizer/languages/svelte.d.ts +40 -0
- package/dist/components/editor/tokenizer/languages/svelte.js +326 -0
- package/dist/components/editor/tokenizer/types.d.ts +86 -0
- package/dist/components/editor/tokenizer/types.js +4 -0
- package/dist/components/layout/IDELayout.svelte +274 -0
- package/dist/components/layout/IDELayout.svelte.d.ts +29 -0
- package/dist/components/layout/StatusBar.svelte +511 -0
- package/dist/components/layout/StatusBar.svelte.d.ts +47 -0
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.js +3 -0
- package/dist/components/lsp/AutocompleteWidget.svelte +364 -0
- package/dist/components/lsp/AutocompleteWidget.svelte.d.ts +33 -0
- package/dist/components/lsp/DiagnosticMarker.svelte +166 -0
- package/dist/components/lsp/DiagnosticMarker.svelte.d.ts +19 -0
- package/dist/components/lsp/DiagnosticsPanel.svelte +388 -0
- package/dist/components/lsp/DiagnosticsPanel.svelte.d.ts +21 -0
- package/dist/components/lsp/HoverTooltip.svelte +274 -0
- package/dist/components/lsp/HoverTooltip.svelte.d.ts +24 -0
- package/dist/components/lsp/LSPEditor.svelte +486 -0
- package/dist/components/lsp/LSPEditor.svelte.d.ts +39 -0
- package/dist/components/lsp/SignatureHelpWidget.svelte +216 -0
- package/dist/components/lsp/SignatureHelpWidget.svelte.d.ts +22 -0
- package/dist/components/lsp/index.d.ts +6 -0
- package/dist/components/lsp/index.js +7 -0
- package/dist/components/plugins/PluginCard.svelte +153 -0
- package/dist/components/plugins/PluginCard.svelte.d.ts +19 -0
- package/dist/components/plugins/PluginPanel.svelte +280 -0
- package/dist/components/plugins/PluginPanel.svelte.d.ts +8 -0
- package/dist/components/plugins/PluginProposalForm.svelte +250 -0
- package/dist/components/plugins/PluginProposalForm.svelte.d.ts +6 -0
- package/dist/components/plugins/PluginStatusBadge.svelte +14 -0
- package/dist/components/plugins/PluginStatusBadge.svelte.d.ts +8 -0
- package/dist/components/plugins/index.d.ts +4 -0
- package/dist/components/plugins/index.js +5 -0
- package/dist/components/vfs/LockConflictDialog.svelte +705 -0
- package/dist/components/vfs/LockConflictDialog.svelte.d.ts +21 -0
- package/dist/components/vfs/LockIndicator.svelte +194 -0
- package/dist/components/vfs/LockIndicator.svelte.d.ts +29 -0
- package/dist/components/vfs/LockOverlay.svelte +344 -0
- package/dist/components/vfs/LockOverlay.svelte.d.ts +17 -0
- package/dist/components/vfs/VersionConflictDialog.svelte +549 -0
- package/dist/components/vfs/VersionConflictDialog.svelte.d.ts +24 -0
- package/dist/components/vfs/index.d.ts +4 -0
- package/dist/components/vfs/index.js +5 -0
- package/dist/crdt/awareness.d.ts +42 -0
- package/dist/crdt/awareness.js +109 -0
- package/dist/crdt/document.d.ts +101 -0
- package/dist/crdt/document.js +187 -0
- package/dist/crdt/index.d.ts +9 -0
- package/dist/crdt/index.js +8 -0
- package/dist/crdt/provider.d.ts +85 -0
- package/dist/crdt/provider.js +150 -0
- package/dist/crdt/types.d.ts +61 -0
- package/dist/crdt/types.js +4 -0
- package/dist/crdt/undo.d.ts +34 -0
- package/dist/crdt/undo.js +70 -0
- package/dist/index.d.ts +277 -0
- package/dist/index.js +280 -0
- package/dist/plugins/index.d.ts +103 -0
- package/dist/plugins/index.js +153 -0
- package/dist/services/error-handling.d.ts +95 -0
- package/dist/services/error-handling.js +413 -0
- package/dist/services/ide-integration.d.ts +83 -0
- package/dist/services/ide-integration.js +367 -0
- package/dist/services/lsp-client.d.ts +69 -0
- package/dist/services/lsp-client.js +667 -0
- package/dist/services/mock-ai.d.ts +37 -0
- package/dist/services/mock-ai.js +318 -0
- package/dist/services/optimistic.d.ts +141 -0
- package/dist/services/optimistic.js +367 -0
- package/dist/services/vfs-client.d.ts +81 -0
- package/dist/services/vfs-client.js +348 -0
- package/dist/stores/agents.svelte.d.ts +85 -0
- package/dist/stores/agents.svelte.js +459 -0
- package/dist/stores/ai-persistence.svelte.d.ts +76 -0
- package/dist/stores/ai-persistence.svelte.js +334 -0
- package/dist/stores/ai.svelte.d.ts +140 -0
- package/dist/stores/ai.svelte.js +383 -0
- package/dist/stores/collaboration.svelte.d.ts +164 -0
- package/dist/stores/collaboration.svelte.js +334 -0
- package/dist/stores/editor.svelte.d.ts +131 -0
- package/dist/stores/editor.svelte.js +250 -0
- package/dist/stores/index.d.ts +10 -0
- package/dist/stores/index.js +29 -0
- package/dist/stores/layout.svelte.d.ts +171 -0
- package/dist/stores/layout.svelte.js +351 -0
- package/dist/stores/plugin.svelte.d.ts +121 -0
- package/dist/stores/plugin.svelte.js +410 -0
- package/dist/stores/vfs.svelte.d.ts +123 -0
- package/dist/stores/vfs.svelte.js +680 -0
- package/dist/styles/theme.css +623 -0
- package/dist/types/agents.d.ts +127 -0
- package/dist/types/agents.js +5 -0
- package/dist/types/ai.d.ts +137 -0
- package/dist/types/ai.js +4 -0
- package/dist/types/crdt.d.ts +222 -0
- package/dist/types/crdt.js +5 -0
- package/dist/types/editor.d.ts +52 -0
- package/dist/types/editor.js +18 -0
- package/dist/types/events.d.ts +133 -0
- package/dist/types/events.js +4 -0
- package/dist/types/filesystem.d.ts +77 -0
- package/dist/types/filesystem.js +4 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +12 -0
- package/dist/types/lsp.d.ts +691 -0
- package/dist/types/lsp.js +108 -0
- package/dist/types/plugin.d.ts +239 -0
- package/dist/types/plugin.js +5 -0
- package/dist/types/vfs.d.ts +191 -0
- package/dist/types/vfs.js +18 -0
- package/dist/utils/format.d.ts +55 -0
- package/dist/utils/format.js +152 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/keybindings.d.ts +33 -0
- package/dist/utils/keybindings.js +171 -0
- package/dist/utils/language.d.ts +27 -0
- package/dist/utils/language.js +222 -0
- package/package.json +178 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type SymbolKind = 'file' | 'module' | 'namespace' | 'class' | 'interface' | 'type' | 'enum' | 'function' | 'method' | 'property' | 'variable' | 'constant' | 'parameter' | 'import' | 'export';
|
|
2
|
+
export interface DocumentSymbol {
|
|
3
|
+
/** Unique identifier */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Symbol name */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Symbol kind */
|
|
8
|
+
kind: SymbolKind;
|
|
9
|
+
/** Detail text (e.g., type annotation, parameters) */
|
|
10
|
+
detail?: string;
|
|
11
|
+
/** Line number (0-based) */
|
|
12
|
+
line: number;
|
|
13
|
+
/** End line */
|
|
14
|
+
endLine?: number;
|
|
15
|
+
/** Column */
|
|
16
|
+
column?: number;
|
|
17
|
+
/** Children symbols */
|
|
18
|
+
children?: DocumentSymbol[];
|
|
19
|
+
/** Whether symbol is deprecated */
|
|
20
|
+
deprecated?: boolean;
|
|
21
|
+
/** Visibility modifier */
|
|
22
|
+
visibility?: 'public' | 'private' | 'protected';
|
|
23
|
+
}
|
|
24
|
+
interface Props {
|
|
25
|
+
/** Symbols to display */
|
|
26
|
+
symbols: DocumentSymbol[];
|
|
27
|
+
/** Currently active symbol (at cursor) */
|
|
28
|
+
activeSymbolId?: string | null;
|
|
29
|
+
/** Whether outline is enabled */
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
/** Filter text */
|
|
32
|
+
filter?: string;
|
|
33
|
+
/** Sort order */
|
|
34
|
+
sortBy?: 'position' | 'name' | 'kind';
|
|
35
|
+
/** Whether to show collapsed by default */
|
|
36
|
+
defaultCollapsed?: boolean;
|
|
37
|
+
/** Callback when symbol is clicked */
|
|
38
|
+
onNavigate?: (symbol: DocumentSymbol) => void;
|
|
39
|
+
/** Panel width */
|
|
40
|
+
width?: number;
|
|
41
|
+
}
|
|
42
|
+
declare const SymbolOutline: import("svelte").Component<Props, {}, "">;
|
|
43
|
+
type SymbolOutline = ReturnType<typeof SymbolOutline>;
|
|
44
|
+
export default SymbolOutline;
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Timeline Scrubber
|
|
4
|
+
*
|
|
5
|
+
* A visual timeline for scrubbing through document history.
|
|
6
|
+
* Shows colored markers for different authors and change types.
|
|
7
|
+
* Supports playback mode with smooth transitions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
type TimelineSnapshot,
|
|
12
|
+
type SnapshotMetadata,
|
|
13
|
+
formatTimestamp,
|
|
14
|
+
formatDuration
|
|
15
|
+
} from './core/timeline';
|
|
16
|
+
|
|
17
|
+
interface TimelineMarker {
|
|
18
|
+
position: number;
|
|
19
|
+
color: string;
|
|
20
|
+
type: SnapshotMetadata['changeType'];
|
|
21
|
+
label: string;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
/** Timeline markers to display */
|
|
27
|
+
markers: TimelineMarker[];
|
|
28
|
+
/** Current position (0-1, where 1 is live) */
|
|
29
|
+
position?: number;
|
|
30
|
+
/** Whether in playback mode */
|
|
31
|
+
isPlayback?: boolean;
|
|
32
|
+
/** Whether auto-playing */
|
|
33
|
+
isPlaying?: boolean;
|
|
34
|
+
/** Timeline duration in ms */
|
|
35
|
+
duration?: number;
|
|
36
|
+
/** Callback when position changes */
|
|
37
|
+
onPositionChange?: (position: number) => void;
|
|
38
|
+
/** Callback when playback starts */
|
|
39
|
+
onPlay?: () => void;
|
|
40
|
+
/** Callback when playback pauses */
|
|
41
|
+
onPause?: () => void;
|
|
42
|
+
/** Callback to go live */
|
|
43
|
+
onGoLive?: () => void;
|
|
44
|
+
/** Whether the scrubber is enabled */
|
|
45
|
+
enabled?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
markers = [],
|
|
50
|
+
position = $bindable(1),
|
|
51
|
+
isPlayback = false,
|
|
52
|
+
isPlaying = false,
|
|
53
|
+
duration = 0,
|
|
54
|
+
onPositionChange,
|
|
55
|
+
onPlay,
|
|
56
|
+
onPause,
|
|
57
|
+
onGoLive,
|
|
58
|
+
enabled = true
|
|
59
|
+
}: Props = $props();
|
|
60
|
+
|
|
61
|
+
let trackElement: HTMLDivElement | null = $state(null);
|
|
62
|
+
let isDragging = $state(false);
|
|
63
|
+
let hoveredMarker = $state<TimelineMarker | null>(null);
|
|
64
|
+
let tooltipPosition = $state({ x: 0, y: 0 });
|
|
65
|
+
|
|
66
|
+
// Format current position as time
|
|
67
|
+
let positionTime = $derived(() => {
|
|
68
|
+
if (markers.length === 0) return '';
|
|
69
|
+
if (position >= 1) return 'Live';
|
|
70
|
+
|
|
71
|
+
const startTime = markers[0]?.timestamp ?? 0;
|
|
72
|
+
const currentTime = startTime + position * duration;
|
|
73
|
+
return formatTimestamp(currentTime);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function handlePointerDown(e: PointerEvent) {
|
|
77
|
+
if (!enabled || !trackElement) return;
|
|
78
|
+
|
|
79
|
+
isDragging = true;
|
|
80
|
+
(e.target as HTMLElement).setPointerCapture(e.pointerId);
|
|
81
|
+
updatePosition(e);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handlePointerMove(e: PointerEvent) {
|
|
85
|
+
if (!isDragging || !trackElement) return;
|
|
86
|
+
updatePosition(e);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function handlePointerUp(e: PointerEvent) {
|
|
90
|
+
isDragging = false;
|
|
91
|
+
(e.target as HTMLElement).releasePointerCapture(e.pointerId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function updatePosition(e: PointerEvent) {
|
|
95
|
+
if (!trackElement) return;
|
|
96
|
+
|
|
97
|
+
const rect = trackElement.getBoundingClientRect();
|
|
98
|
+
const x = e.clientX - rect.left;
|
|
99
|
+
const newPosition = Math.max(0, Math.min(1, x / rect.width));
|
|
100
|
+
|
|
101
|
+
position = newPosition;
|
|
102
|
+
onPositionChange?.(newPosition);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handleMarkerHover(marker: TimelineMarker, e: MouseEvent) {
|
|
106
|
+
hoveredMarker = marker;
|
|
107
|
+
tooltipPosition = { x: e.clientX, y: e.clientY - 40 };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleMarkerLeave() {
|
|
111
|
+
hoveredMarker = null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
115
|
+
if (!enabled) return;
|
|
116
|
+
|
|
117
|
+
const step = e.shiftKey ? 0.1 : 0.01;
|
|
118
|
+
|
|
119
|
+
switch (e.key) {
|
|
120
|
+
case 'ArrowLeft':
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
position = Math.max(0, position - step);
|
|
123
|
+
onPositionChange?.(position);
|
|
124
|
+
break;
|
|
125
|
+
case 'ArrowRight':
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
position = Math.min(1, position + step);
|
|
128
|
+
onPositionChange?.(position);
|
|
129
|
+
break;
|
|
130
|
+
case 'Home':
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
position = 0;
|
|
133
|
+
onPositionChange?.(position);
|
|
134
|
+
break;
|
|
135
|
+
case 'End':
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
position = 1;
|
|
138
|
+
onPositionChange?.(position);
|
|
139
|
+
onGoLive?.();
|
|
140
|
+
break;
|
|
141
|
+
case ' ':
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
if (isPlaying) {
|
|
144
|
+
onPause?.();
|
|
145
|
+
} else {
|
|
146
|
+
onPlay?.();
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getMarkerIcon(type: SnapshotMetadata['changeType']): string {
|
|
153
|
+
switch (type) {
|
|
154
|
+
case 'commit':
|
|
155
|
+
return '\u{1F4BE}';
|
|
156
|
+
case 'save':
|
|
157
|
+
return '\u{1F4BE}';
|
|
158
|
+
case 'ai-suggestion':
|
|
159
|
+
return '🤖';
|
|
160
|
+
case 'checkpoint':
|
|
161
|
+
return '\u{23F8}';
|
|
162
|
+
default:
|
|
163
|
+
return '\u{270F}';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
{#if enabled}
|
|
169
|
+
<div
|
|
170
|
+
class="timeline-scrubber"
|
|
171
|
+
class:timeline-scrubber--playback={isPlayback}
|
|
172
|
+
class:timeline-scrubber--dragging={isDragging}
|
|
173
|
+
role="slider"
|
|
174
|
+
aria-label="Timeline scrubber"
|
|
175
|
+
aria-valuemin={0}
|
|
176
|
+
aria-valuemax={100}
|
|
177
|
+
aria-valuenow={Math.round(position * 100)}
|
|
178
|
+
tabindex={0}
|
|
179
|
+
onkeydown={handleKeyDown}
|
|
180
|
+
>
|
|
181
|
+
<!-- Playback controls -->
|
|
182
|
+
<div class="timeline-scrubber__controls">
|
|
183
|
+
{#if isPlayback}
|
|
184
|
+
<button
|
|
185
|
+
class="timeline-scrubber__btn"
|
|
186
|
+
onclick={() => (isPlaying ? onPause?.() : onPlay?.())}
|
|
187
|
+
aria-label={isPlaying ? 'Pause' : 'Play'}
|
|
188
|
+
>
|
|
189
|
+
{#if isPlaying}
|
|
190
|
+
<span class="icon">⏸</span>
|
|
191
|
+
{:else}
|
|
192
|
+
<span class="icon">▶</span>
|
|
193
|
+
{/if}
|
|
194
|
+
</button>
|
|
195
|
+
{/if}
|
|
196
|
+
|
|
197
|
+
<button
|
|
198
|
+
class="timeline-scrubber__btn timeline-scrubber__btn--rewind"
|
|
199
|
+
onclick={() => {
|
|
200
|
+
position = 0;
|
|
201
|
+
onPositionChange?.(0);
|
|
202
|
+
}}
|
|
203
|
+
aria-label="Go to start"
|
|
204
|
+
>
|
|
205
|
+
<span class="icon">⏮</span>
|
|
206
|
+
</button>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<!-- Track -->
|
|
210
|
+
<div
|
|
211
|
+
class="timeline-scrubber__track"
|
|
212
|
+
bind:this={trackElement}
|
|
213
|
+
onpointerdown={handlePointerDown}
|
|
214
|
+
onpointermove={handlePointerMove}
|
|
215
|
+
onpointerup={handlePointerUp}
|
|
216
|
+
>
|
|
217
|
+
<!-- Progress fill -->
|
|
218
|
+
<div class="timeline-scrubber__fill" style="width: {position * 100}%;"></div>
|
|
219
|
+
|
|
220
|
+
<!-- Markers -->
|
|
221
|
+
{#each markers as marker (marker.timestamp)}
|
|
222
|
+
<div
|
|
223
|
+
class="timeline-scrubber__marker timeline-scrubber__marker--{marker.type}"
|
|
224
|
+
style="left: {marker.position * 100}%; --marker-color: {marker.color};"
|
|
225
|
+
onmouseenter={(e) => handleMarkerHover(marker, e)}
|
|
226
|
+
onmouseleave={handleMarkerLeave}
|
|
227
|
+
role="button"
|
|
228
|
+
tabindex={-1}
|
|
229
|
+
>
|
|
230
|
+
<span class="timeline-scrubber__marker-dot"></span>
|
|
231
|
+
</div>
|
|
232
|
+
{/each}
|
|
233
|
+
|
|
234
|
+
<!-- Thumb -->
|
|
235
|
+
<div class="timeline-scrubber__thumb" style="left: {position * 100}%;">
|
|
236
|
+
<div class="timeline-scrubber__thumb-handle"></div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<!-- Time display -->
|
|
241
|
+
<div class="timeline-scrubber__time">
|
|
242
|
+
{#if position >= 1}
|
|
243
|
+
<span class="timeline-scrubber__live">LIVE</span>
|
|
244
|
+
{:else}
|
|
245
|
+
<span class="timeline-scrubber__position">{positionTime()}</span>
|
|
246
|
+
{/if}
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<!-- Go Live button -->
|
|
250
|
+
{#if isPlayback || position < 1}
|
|
251
|
+
<button class="timeline-scrubber__btn timeline-scrubber__btn--live" onclick={onGoLive}>
|
|
252
|
+
<span class="icon">●</span>
|
|
253
|
+
<span>Live</span>
|
|
254
|
+
</button>
|
|
255
|
+
{/if}
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<!-- Marker tooltip -->
|
|
259
|
+
{#if hoveredMarker}
|
|
260
|
+
<div
|
|
261
|
+
class="timeline-scrubber__tooltip"
|
|
262
|
+
style="left: {tooltipPosition.x}px; top: {tooltipPosition.y}px;"
|
|
263
|
+
>
|
|
264
|
+
<div class="timeline-scrubber__tooltip-icon" style="color: {hoveredMarker.color}">
|
|
265
|
+
{getMarkerIcon(hoveredMarker.type)}
|
|
266
|
+
</div>
|
|
267
|
+
<div class="timeline-scrubber__tooltip-content">
|
|
268
|
+
<div class="timeline-scrubber__tooltip-label">{hoveredMarker.label}</div>
|
|
269
|
+
<div class="timeline-scrubber__tooltip-time">{formatTimestamp(hoveredMarker.timestamp)}</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
{/if}
|
|
273
|
+
{/if}
|
|
274
|
+
|
|
275
|
+
<style>
|
|
276
|
+
.timeline-scrubber {
|
|
277
|
+
display: flex;
|
|
278
|
+
align-items: center;
|
|
279
|
+
gap: 8px;
|
|
280
|
+
height: 32px;
|
|
281
|
+
padding: 0 12px;
|
|
282
|
+
background: var(--color-surface, #1e1e2e);
|
|
283
|
+
border-top: 1px solid var(--color-border, #333);
|
|
284
|
+
user-select: none;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.timeline-scrubber--playback {
|
|
288
|
+
background: var(--color-surface-elevated, #252535);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.timeline-scrubber__controls {
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
gap: 4px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.timeline-scrubber__btn {
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
justify-content: center;
|
|
301
|
+
gap: 4px;
|
|
302
|
+
width: 28px;
|
|
303
|
+
height: 28px;
|
|
304
|
+
padding: 0;
|
|
305
|
+
background: transparent;
|
|
306
|
+
border: none;
|
|
307
|
+
border-radius: 4px;
|
|
308
|
+
color: var(--color-text-muted, #888);
|
|
309
|
+
cursor: pointer;
|
|
310
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.timeline-scrubber__btn:hover {
|
|
314
|
+
background: rgba(255, 255, 255, 0.1);
|
|
315
|
+
color: var(--color-text, #e8e8f0);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.timeline-scrubber__btn--live {
|
|
319
|
+
width: auto;
|
|
320
|
+
padding: 0 8px;
|
|
321
|
+
font-size: 11px;
|
|
322
|
+
font-weight: 600;
|
|
323
|
+
text-transform: uppercase;
|
|
324
|
+
letter-spacing: 0.05em;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.timeline-scrubber__btn--live .icon {
|
|
328
|
+
color: #ef4444;
|
|
329
|
+
font-size: 8px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.timeline-scrubber__btn .icon {
|
|
333
|
+
font-size: 14px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.timeline-scrubber__track {
|
|
337
|
+
flex: 1;
|
|
338
|
+
position: relative;
|
|
339
|
+
height: 8px;
|
|
340
|
+
background: rgba(255, 255, 255, 0.1);
|
|
341
|
+
border-radius: 4px;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.timeline-scrubber__fill {
|
|
346
|
+
position: absolute;
|
|
347
|
+
top: 0;
|
|
348
|
+
left: 0;
|
|
349
|
+
height: 100%;
|
|
350
|
+
background: linear-gradient(90deg, rgba(74, 158, 255, 0.3), rgba(74, 158, 255, 0.5));
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
pointer-events: none;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.timeline-scrubber__marker {
|
|
356
|
+
position: absolute;
|
|
357
|
+
top: 50%;
|
|
358
|
+
transform: translate(-50%, -50%);
|
|
359
|
+
z-index: 1;
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.timeline-scrubber__marker-dot {
|
|
364
|
+
display: block;
|
|
365
|
+
width: 6px;
|
|
366
|
+
height: 6px;
|
|
367
|
+
background: var(--marker-color, #4a9eff);
|
|
368
|
+
border-radius: 50%;
|
|
369
|
+
transition: transform 0.15s ease;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.timeline-scrubber__marker:hover .timeline-scrubber__marker-dot {
|
|
373
|
+
transform: scale(1.5);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.timeline-scrubber__marker--ai-suggestion .timeline-scrubber__marker-dot {
|
|
377
|
+
width: 8px;
|
|
378
|
+
height: 8px;
|
|
379
|
+
box-shadow: 0 0 4px var(--marker-color);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.timeline-scrubber__marker--commit .timeline-scrubber__marker-dot,
|
|
383
|
+
.timeline-scrubber__marker--save .timeline-scrubber__marker-dot {
|
|
384
|
+
width: 8px;
|
|
385
|
+
height: 8px;
|
|
386
|
+
border-radius: 2px;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.timeline-scrubber__thumb {
|
|
390
|
+
position: absolute;
|
|
391
|
+
top: 50%;
|
|
392
|
+
transform: translate(-50%, -50%);
|
|
393
|
+
z-index: 2;
|
|
394
|
+
pointer-events: none;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.timeline-scrubber__thumb-handle {
|
|
398
|
+
width: 14px;
|
|
399
|
+
height: 14px;
|
|
400
|
+
background: var(--color-primary, #4a9eff);
|
|
401
|
+
border: 2px solid #fff;
|
|
402
|
+
border-radius: 50%;
|
|
403
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
404
|
+
transition: transform 0.1s ease;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.timeline-scrubber--dragging .timeline-scrubber__thumb-handle {
|
|
408
|
+
transform: scale(1.2);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.timeline-scrubber__time {
|
|
412
|
+
min-width: 50px;
|
|
413
|
+
text-align: center;
|
|
414
|
+
font-size: 11px;
|
|
415
|
+
font-family: var(--font-mono, monospace);
|
|
416
|
+
color: var(--color-text-muted, #888);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.timeline-scrubber__live {
|
|
420
|
+
color: #ef4444;
|
|
421
|
+
font-weight: 600;
|
|
422
|
+
animation: live-pulse 2s ease-in-out infinite;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@keyframes live-pulse {
|
|
426
|
+
0%,
|
|
427
|
+
100% {
|
|
428
|
+
opacity: 1;
|
|
429
|
+
}
|
|
430
|
+
50% {
|
|
431
|
+
opacity: 0.6;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.timeline-scrubber__tooltip {
|
|
436
|
+
position: fixed;
|
|
437
|
+
z-index: 1000;
|
|
438
|
+
display: flex;
|
|
439
|
+
align-items: center;
|
|
440
|
+
gap: 8px;
|
|
441
|
+
padding: 8px 12px;
|
|
442
|
+
background: var(--color-surface, #1e1e2e);
|
|
443
|
+
border: 1px solid var(--color-border, #333);
|
|
444
|
+
border-radius: 6px;
|
|
445
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
446
|
+
pointer-events: none;
|
|
447
|
+
transform: translateX(-50%);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.timeline-scrubber__tooltip-icon {
|
|
451
|
+
font-size: 16px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.timeline-scrubber__tooltip-content {
|
|
455
|
+
display: flex;
|
|
456
|
+
flex-direction: column;
|
|
457
|
+
gap: 2px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.timeline-scrubber__tooltip-label {
|
|
461
|
+
font-size: 12px;
|
|
462
|
+
font-weight: 500;
|
|
463
|
+
color: var(--color-text, #e8e8f0);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.timeline-scrubber__tooltip-time {
|
|
467
|
+
font-size: 10px;
|
|
468
|
+
color: var(--color-text-muted, #888);
|
|
469
|
+
}
|
|
470
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline Scrubber
|
|
3
|
+
*
|
|
4
|
+
* A visual timeline for scrubbing through document history.
|
|
5
|
+
* Shows colored markers for different authors and change types.
|
|
6
|
+
* Supports playback mode with smooth transitions.
|
|
7
|
+
*/
|
|
8
|
+
import { type SnapshotMetadata } from './core/timeline';
|
|
9
|
+
interface TimelineMarker {
|
|
10
|
+
position: number;
|
|
11
|
+
color: string;
|
|
12
|
+
type: SnapshotMetadata['changeType'];
|
|
13
|
+
label: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Timeline markers to display */
|
|
18
|
+
markers: TimelineMarker[];
|
|
19
|
+
/** Current position (0-1, where 1 is live) */
|
|
20
|
+
position?: number;
|
|
21
|
+
/** Whether in playback mode */
|
|
22
|
+
isPlayback?: boolean;
|
|
23
|
+
/** Whether auto-playing */
|
|
24
|
+
isPlaying?: boolean;
|
|
25
|
+
/** Timeline duration in ms */
|
|
26
|
+
duration?: number;
|
|
27
|
+
/** Callback when position changes */
|
|
28
|
+
onPositionChange?: (position: number) => void;
|
|
29
|
+
/** Callback when playback starts */
|
|
30
|
+
onPlay?: () => void;
|
|
31
|
+
/** Callback when playback pauses */
|
|
32
|
+
onPause?: () => void;
|
|
33
|
+
/** Callback to go live */
|
|
34
|
+
onGoLive?: () => void;
|
|
35
|
+
/** Whether the scrubber is enabled */
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
declare const TimelineScrubber: import("svelte").Component<Props, {}, "position">;
|
|
39
|
+
type TimelineScrubber = ReturnType<typeof TimelineScrubber>;
|
|
40
|
+
export default TimelineScrubber;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* TokenRenderer - Safely renders tokenized line content without @html
|
|
4
|
+
*
|
|
5
|
+
* This component prevents XSS by rendering tokens as DOM elements
|
|
6
|
+
* instead of using innerHTML/dangerouslySetInnerHTML.
|
|
7
|
+
* Merges adjacent tokens of the same type to reduce DOM overhead.
|
|
8
|
+
*/
|
|
9
|
+
import { getTokenClass, type TokenizedLine, type Token } from './tokenizer';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
tokens: TokenizedLine | undefined;
|
|
13
|
+
tabSize?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { tokens, tabSize = 2 }: Props = $props();
|
|
17
|
+
|
|
18
|
+
// Process text to handle whitespace for display
|
|
19
|
+
function processText(text: string): string {
|
|
20
|
+
// Replace tabs with spaces for display
|
|
21
|
+
return text.replace(/\t/g, ' '.repeat(tabSize));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Merge adjacent tokens of the same type to reduce DOM elements
|
|
25
|
+
let mergedTokens = $derived.by(() => {
|
|
26
|
+
if (!tokens?.tokens?.length) return [];
|
|
27
|
+
|
|
28
|
+
const result: Token[] = [];
|
|
29
|
+
let current: Token | null = null;
|
|
30
|
+
|
|
31
|
+
for (const token of tokens.tokens) {
|
|
32
|
+
if (current && current.type === token.type) {
|
|
33
|
+
// Merge with current token
|
|
34
|
+
current = {
|
|
35
|
+
type: current.type,
|
|
36
|
+
text: current.text + token.text,
|
|
37
|
+
start: current.start,
|
|
38
|
+
end: token.end
|
|
39
|
+
};
|
|
40
|
+
} else {
|
|
41
|
+
// Push previous and start new
|
|
42
|
+
if (current) result.push(current);
|
|
43
|
+
current = { ...token };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (current) result.push(current);
|
|
47
|
+
|
|
48
|
+
return result;
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
{#if mergedTokens.length > 0}
|
|
53
|
+
{#each mergedTokens as token}
|
|
54
|
+
{#if token.type === 'text'}
|
|
55
|
+
<span class="token-text">{processText(token.text) || '\u00A0'}</span>
|
|
56
|
+
{:else}
|
|
57
|
+
<span class={getTokenClass(token.type)}>{processText(token.text) || '\u00A0'}</span>
|
|
58
|
+
{/if}
|
|
59
|
+
{/each}
|
|
60
|
+
{:else}
|
|
61
|
+
<span> </span>
|
|
62
|
+
{/if}
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
/* Whitespace handling */
|
|
66
|
+
span {
|
|
67
|
+
white-space: pre;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenRenderer - Safely renders tokenized line content without @html
|
|
3
|
+
*
|
|
4
|
+
* This component prevents XSS by rendering tokens as DOM elements
|
|
5
|
+
* instead of using innerHTML/dangerouslySetInnerHTML.
|
|
6
|
+
* Merges adjacent tokens of the same type to reduce DOM overhead.
|
|
7
|
+
*/
|
|
8
|
+
import { type TokenizedLine } from './tokenizer';
|
|
9
|
+
interface Props {
|
|
10
|
+
tokens: TokenizedLine | undefined;
|
|
11
|
+
tabSize?: number;
|
|
12
|
+
}
|
|
13
|
+
declare const TokenRenderer: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type TokenRenderer = ReturnType<typeof TokenRenderer>;
|
|
15
|
+
export default TokenRenderer;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor constants
|
|
3
|
+
*
|
|
4
|
+
* Central location for all magic numbers and configuration values
|
|
5
|
+
* used throughout the custom editor components.
|
|
6
|
+
*/
|
|
7
|
+
/** Cursor blink interval in milliseconds */
|
|
8
|
+
export declare const CURSOR_BLINK_MS = 530;
|
|
9
|
+
/** Debounce delay for onChange callback in milliseconds */
|
|
10
|
+
export declare const ONCHANGE_DEBOUNCE_MS = 50;
|
|
11
|
+
/** Time window for grouping consecutive edits in undo history (ms) */
|
|
12
|
+
export declare const HISTORY_GROUP_TIMEOUT_MS = 300;
|
|
13
|
+
/** Default character width in pixels (monospace) */
|
|
14
|
+
export declare const DEFAULT_CHAR_WIDTH = 8;
|
|
15
|
+
/** Default line height in pixels */
|
|
16
|
+
export declare const DEFAULT_LINE_HEIGHT = 20;
|
|
17
|
+
/** Default gutter (line numbers) width in pixels */
|
|
18
|
+
export declare const DEFAULT_GUTTER_WIDTH = 50;
|
|
19
|
+
/** Padding between gutter and content area in pixels */
|
|
20
|
+
export declare const CONTENT_PADDING = 8;
|
|
21
|
+
/** Right padding inside the gutter for line numbers */
|
|
22
|
+
export declare const GUTTER_PADDING = 24;
|
|
23
|
+
/** Line height multiplier for calculating line spacing */
|
|
24
|
+
export declare const LINE_HEIGHT_MULTIPLIER = 1.4;
|
|
25
|
+
/** Fallback viewport height for page size calculations */
|
|
26
|
+
export declare const FALLBACK_VIEWPORT_HEIGHT = 400;
|
|
27
|
+
/** Default font size in pixels */
|
|
28
|
+
export declare const DEFAULT_FONT_SIZE = 14;
|
|
29
|
+
/** Default word wrap column */
|
|
30
|
+
export declare const DEFAULT_WORD_WRAP_COLUMN = 80;
|
|
31
|
+
/** Maximum number of entries in undo/redo stack */
|
|
32
|
+
export declare const MAX_HISTORY_SIZE = 100;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor constants
|
|
3
|
+
*
|
|
4
|
+
* Central location for all magic numbers and configuration values
|
|
5
|
+
* used throughout the custom editor components.
|
|
6
|
+
*/
|
|
7
|
+
// ==================== Timing ====================
|
|
8
|
+
/** Cursor blink interval in milliseconds */
|
|
9
|
+
export const CURSOR_BLINK_MS = 530;
|
|
10
|
+
/** Debounce delay for onChange callback in milliseconds */
|
|
11
|
+
export const ONCHANGE_DEBOUNCE_MS = 50;
|
|
12
|
+
/** Time window for grouping consecutive edits in undo history (ms) */
|
|
13
|
+
export const HISTORY_GROUP_TIMEOUT_MS = 300;
|
|
14
|
+
// ==================== Layout ====================
|
|
15
|
+
/** Default character width in pixels (monospace) */
|
|
16
|
+
export const DEFAULT_CHAR_WIDTH = 8;
|
|
17
|
+
/** Default line height in pixels */
|
|
18
|
+
export const DEFAULT_LINE_HEIGHT = 20;
|
|
19
|
+
/** Default gutter (line numbers) width in pixels */
|
|
20
|
+
export const DEFAULT_GUTTER_WIDTH = 50;
|
|
21
|
+
/** Padding between gutter and content area in pixels */
|
|
22
|
+
export const CONTENT_PADDING = 8;
|
|
23
|
+
/** Right padding inside the gutter for line numbers */
|
|
24
|
+
export const GUTTER_PADDING = 24;
|
|
25
|
+
/** Line height multiplier for calculating line spacing */
|
|
26
|
+
export const LINE_HEIGHT_MULTIPLIER = 1.4;
|
|
27
|
+
/** Fallback viewport height for page size calculations */
|
|
28
|
+
export const FALLBACK_VIEWPORT_HEIGHT = 400;
|
|
29
|
+
// ==================== Typography ====================
|
|
30
|
+
/** Default font size in pixels */
|
|
31
|
+
export const DEFAULT_FONT_SIZE = 14;
|
|
32
|
+
/** Default word wrap column */
|
|
33
|
+
export const DEFAULT_WORD_WRAP_COLUMN = 80;
|
|
34
|
+
// ==================== History ====================
|
|
35
|
+
/** Maximum number of entries in undo/redo stack */
|
|
36
|
+
export const MAX_HISTORY_SIZE = 100;
|