diff --git a/CLAUDE.md b/CLAUDE.md index a021497..e763f61 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,3 +38,4 @@ Instructions for Claude Code when working on this project. - On-demand URL preview loading with caching See `docs/dev-plan.md` for complete roadmap and technical details. +- when updating documentation, do not record intermediate changes - write them always as a matter-of-fact information \ No newline at end of file diff --git a/docs/dev-plan.md b/docs/dev-plan.md index 72a9f24..5db989b 100644 --- a/docs/dev-plan.md +++ b/docs/dev-plan.md @@ -303,18 +303,6 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu ### **Phase 12: Advanced Dataset Features** ✅ **COMPLETE** **Goal**: Enhanced dataset workflows -- [x] Detect inline data in Vega-Lite specs -- [x] "Extract to dataset" feature for inline data -- [x] Update snippet UI to show linked datasets -- [x] Dataset usage tracking (which snippets reference which datasets) -- [x] Bidirectional linking between snippets and datasets -- [x] Usage count badges on dataset list items -- [x] "New Snippet" button to create snippet from dataset -- [x] Import datasets from file upload -- [x] Export individual datasets -- [x] Dataset preview with table view -- [x] Column type detection and display - **Deliverables**: - Recursive dataset reference extraction from Vega-Lite specs - Extract to Dataset modal with automatic spec transformation diff --git a/index.html b/index.html index cb28fe3..cd53863 100644 --- a/index.html +++ b/index.html @@ -321,6 +321,42 @@ + + + diff --git a/src/js/app.js b/src/js/app.js index db0b5ba..e68f2f8 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -29,6 +29,9 @@ document.addEventListener('DOMContentLoaded', function () { // Initialize resize functionality initializeResize(); + // Initialize keyboard shortcuts + initializeKeyboardShortcuts(); + // Initialize Monaco Editor require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs' } }); require(['vs/editor/editor.main'], async function () { @@ -70,6 +73,9 @@ document.addEventListener('DOMContentLoaded', function () { formatOnType: true }); + // Register custom keyboard shortcuts in Monaco + registerMonacoKeyboardShortcuts(); + // Add debounced auto-render on editor change editor.onDidChangeModelContent(() => { debouncedRender(); @@ -118,7 +124,7 @@ document.addEventListener('DOMContentLoaded', function () { if (helpLink) { helpLink.addEventListener('click', function () { - alert('Coming soon in a future phase!'); + openHelpModal(); }); } @@ -229,6 +235,23 @@ document.addEventListener('DOMContentLoaded', function () { }); } + // Help Modal + const helpModal = document.getElementById('help-modal'); + const helpModalClose = document.getElementById('help-modal-close'); + + if (helpModalClose) { + helpModalClose.addEventListener('click', closeHelpModal); + } + + // Close on overlay click + if (helpModal) { + helpModal.addEventListener('click', function (e) { + if (e.target === helpModal) { + closeHelpModal(); + } + }); + } + // View mode toggle buttons document.getElementById('view-draft').addEventListener('click', () => { switchViewMode('draft'); @@ -315,3 +338,113 @@ function initializeURLStateManagement() { handleURLStateChange(); } } + +// Keyboard shortcut action handlers (shared between Monaco and document) +const KeyboardActions = { + createNewSnippet: function() { + createNewSnippet(); + }, + + toggleDatasetManager: function() { + const modal = document.getElementById('dataset-modal'); + if (modal && modal.style.display === 'flex') { + closeDatasetManager(); + } else { + openDatasetManager(); + } + }, + + publishDraft: function() { + if (currentViewMode === 'draft' && window.currentSnippetId) { + publishDraft(); + } + }, + + closeAnyModal: function() { + const helpModal = document.getElementById('help-modal'); + const datasetModal = document.getElementById('dataset-modal'); + const extractModal = document.getElementById('extract-modal'); + + if (helpModal && helpModal.style.display === 'flex') { + closeHelpModal(); + return true; + } + if (datasetModal && datasetModal.style.display === 'flex') { + closeDatasetManager(); + return true; + } + if (extractModal && extractModal.style.display === 'flex') { + hideExtractModal(); + return true; + } + return false; + } +}; + +// Keyboard shortcuts handler (document-level) +function initializeKeyboardShortcuts() { + document.addEventListener('keydown', function(e) { + // Escape: Close any open modal + if (e.key === 'Escape') { + if (KeyboardActions.closeAnyModal()) { + return; + } + } + + // Detect modifier key: Cmd on Mac, Ctrl on Windows/Linux + const modifierKey = e.metaKey || e.ctrlKey; + + // Cmd/Ctrl+Shift+N: Create new snippet + if (modifierKey && e.shiftKey && e.key.toLowerCase() === 'n') { + e.preventDefault(); + KeyboardActions.createNewSnippet(); + return; + } + + // Cmd/Ctrl+K: Toggle dataset manager + if (modifierKey && e.key.toLowerCase() === 'k') { + e.preventDefault(); + KeyboardActions.toggleDatasetManager(); + return; + } + + // Cmd/Ctrl+S: Publish draft + if (modifierKey && e.key.toLowerCase() === 's') { + e.preventDefault(); + KeyboardActions.publishDraft(); + return; + } + }); +} + +// Register keyboard shortcuts in Monaco Editor +function registerMonacoKeyboardShortcuts() { + if (!editor) return; + + // Cmd/Ctrl+Shift+N: Create new snippet + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyN, + KeyboardActions.createNewSnippet); + + // Cmd/Ctrl+K: Toggle dataset manager + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK, + KeyboardActions.toggleDatasetManager); + + // Cmd/Ctrl+S: Publish draft + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, + KeyboardActions.publishDraft); +} + +// Help modal functions +function openHelpModal() { + const modal = document.getElementById('help-modal'); + if (modal) { + modal.style.display = 'flex'; + } +} + +function closeHelpModal() { + const modal = document.getElementById('help-modal'); + if (modal) { + modal.style.display = 'none'; + } +} diff --git a/src/styles.css b/src/styles.css index 665777f..a89292f 100644 --- a/src/styles.css +++ b/src/styles.css @@ -298,3 +298,11 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun .detection-preview-label { font-size: 10px; font-weight: bold; margin-bottom: 4px; } .dataset-form-error { color: #f00; font-size: 11px; margin-bottom: 12px; min-height: 16px; } .dataset-form-actions { display: flex; gap: 8px; margin-top: 16px; } + +/* Help Modal */ +.help-shortcuts-table { width: 100%; border-collapse: collapse; } +.help-shortcuts-table tbody tr { border-bottom: 1px solid var(--bg-lighter); } +.help-shortcuts-table tbody tr:last-child { border-bottom: none; } +.help-shortcuts-table td { padding: 12px 8px; font-size: 12px; } +.shortcut-key { font-family: var(--font-mono); font-weight: bold; background: var(--bg-light); border: 1px solid var(--win-gray-dark); padding: 6px 10px; border-radius: 2px; white-space: nowrap; width: 180px; } +.shortcut-desc { color: var(--win-gray-darker); }