From f548701999e55466016faeae3104a9d21a466233 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 07:54:40 +0000 Subject: [PATCH] Add Drag & Drop import, Line Numbers, Zen Mode, and Font Size controls - Implement full-screen Drag & Drop file import on Dashboard and Editor. - Add line-number gutter with scroll sync and toggle setting. - Implement Focus/Zen Mode with smooth transitions and keyboard shortcuts. - Add range sliders for Editor and Preview font sizes in Settings modal. - Ensure all new settings persist to localStorage and respect the design system. Co-authored-by: prathmeshcodes-ai <223116860+prathmeshcodes-ai@users.noreply.github.com> --- ai.js | 45 +++++++++++++- dashboard.js | 86 +++++++++++++++++++++++++++ editor.html | 30 ++++++++++ editor.js | 152 +++++++++++++++++++++++++++++++++++++++++++++-- index.html | 11 ++++ style.css | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 479 insertions(+), 9 deletions(-) diff --git a/ai.js b/ai.js index a985350..30b103b 100644 --- a/ai.js +++ b/ai.js @@ -195,12 +195,39 @@ document.addEventListener('DOMContentLoaded', () => { const settingsModalCloseBtn = document.getElementById('settings-modal-close-btn'); const apiKeyInput = document.getElementById('api-key-input'); const settingsSaveBtn = document.getElementById('settings-save-btn'); + const showLineNumbersCheckbox = document.getElementById('show-line-numbers'); + const editorFontSizeSlider = document.getElementById('editor-font-size-slider'); + const previewFontSizeSlider = document.getElementById('preview-font-size-slider'); + const editorFontSizeVal = document.getElementById('editor-font-size-val'); + const previewFontSizeVal = document.getElementById('preview-font-size-val'); settingsBtn.addEventListener('click', () => { apiKeyInput.value = localStorage.getItem('gemini-api-key') || ''; + showLineNumbersCheckbox.checked = localStorage.getItem('show-line-numbers') === 'true'; + + const editorSize = localStorage.getItem('editor-font-size') || '16'; + const previewSize = localStorage.getItem('preview-font-size') || '16'; + + editorFontSizeSlider.value = editorSize; + editorFontSizeVal.textContent = `${editorSize}px`; + previewFontSizeSlider.value = previewSize; + previewFontSizeVal.textContent = `${previewSize}px`; + settingsModalOverlay.classList.remove('hidden'); }); + editorFontSizeSlider.addEventListener('input', (e) => { + const size = e.target.value; + editorFontSizeVal.textContent = `${size}px`; + document.documentElement.style.setProperty('--editor-font-size', `${size}px`); + }); + + previewFontSizeSlider.addEventListener('input', (e) => { + const size = e.target.value; + previewFontSizeVal.textContent = `${size}px`; + document.documentElement.style.setProperty('--preview-font-size', `${size}px`); + }); + settingsModalCloseBtn.addEventListener('click', () => { settingsModalOverlay.classList.add('hidden'); }); @@ -208,8 +235,24 @@ document.addEventListener('DOMContentLoaded', () => { settingsSaveBtn.addEventListener('click', () => { apiKey = apiKeyInput.value.trim(); localStorage.setItem('gemini-api-key', apiKey); + + const showLineNumbers = showLineNumbersCheckbox.checked; + localStorage.setItem('show-line-numbers', showLineNumbers); + + localStorage.setItem('editor-font-size', editorFontSizeSlider.value); + localStorage.setItem('preview-font-size', previewFontSizeSlider.value); + + // Dispatch event to notify editor.js + window.dispatchEvent(new CustomEvent('settings-updated', { + detail: { + showLineNumbers, + editorFontSize: editorFontSizeSlider.value, + previewFontSize: previewFontSizeSlider.value + } + })); + settingsModalOverlay.classList.add('hidden'); - if (window.toast) window.toast('API key saved', 'success'); + if (window.toast) window.toast('Settings saved', 'success'); }); aiNewChatBtn.addEventListener('click', () => { diff --git a/dashboard.js b/dashboard.js index 1808fa5..fe11ed5 100644 --- a/dashboard.js +++ b/dashboard.js @@ -10,6 +10,7 @@ document.addEventListener('DOMContentLoaded', () => { const modalConfirmBtn = document.getElementById('modal-confirm-btn'); const themeToggle = document.getElementById('theme-toggle'); const docCountBadge = document.getElementById('doc-count-badge'); + const dropOverlay = document.getElementById('drop-overlay'); let files = []; let modalResolve = null; @@ -38,6 +39,34 @@ document.addEventListener('DOMContentLoaded', () => { files = await RazenFS.getAllFiles(); }; + window.toast = (message, type = 'info') => { + const container = document.getElementById('toast-container'); + if (!container) return; + + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + + let icon = 'info-circle'; + if (type === 'success') icon = 'check-circle'; + if (type === 'error') icon = 'exclamation-circle'; + if (type === 'warning') icon = 'exclamation-triangle'; + + toast.innerHTML = ` + + ${message} + `; + + container.appendChild(toast); + + const dismiss = () => { + toast.classList.add('fade-out'); + setTimeout(() => toast.remove(), 300); + }; + + toast.onclick = dismiss; + setTimeout(dismiss, 3000); + }; + const updateDocCount = () => { if (!docCountBadge) return; const n = files.length; @@ -160,6 +189,63 @@ document.addEventListener('DOMContentLoaded', () => { } }; + const handleFileImport = (file) => { + if (!file.name.endsWith('.md') && !file.name.endsWith('.txt')) { + toast('Only .md and .txt files are supported', 'error'); + return; + } + + const reader = new FileReader(); + reader.onload = async (e) => { + const content = e.target.result; + const name = file.name.replace(/\.(md|txt)$/, ''); + const newId = `file_${Date.now()}`; + const newFile = { + id: newId, + name: name, + content: content + }; + await RazenFS.saveFile(newFile); + files.push(newFile); + renderDocuments(); + toast('File imported', 'success'); + }; + reader.readAsText(file); + }; + + // Drag and Drop + const handleDragOver = (e) => { + e.preventDefault(); + e.stopPropagation(); + dropOverlay.classList.remove('hidden'); + }; + + const handleDragLeave = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (e.relatedTarget === null || !dropOverlay.contains(e.relatedTarget)) { + dropOverlay.classList.add('hidden'); + } + }; + + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + dropOverlay.classList.add('hidden'); + + const file = e.dataTransfer.files[0]; + if (file) { + handleFileImport(file); + } + }; + + if (documentsGrid) { + documentsGrid.addEventListener('dragenter', handleDragOver); + dropOverlay.addEventListener('dragover', (e) => e.preventDefault()); + dropOverlay.addEventListener('dragleave', handleDragLeave); + dropOverlay.addEventListener('drop', handleDrop); + } + const handleDownloadFile = (id) => { const file = files.find(f => f.id === id); if (!file) return; diff --git a/editor.html b/editor.html index c04db2c..59478e4 100644 --- a/editor.html +++ b/editor.html @@ -58,6 +58,10 @@

Velox Editor

+ + @@ -122,6 +126,7 @@

Velox Editor

+
@@ -277,6 +282,18 @@

Settings

+
+ + +
+
+ + +
+
+ + +
@@ -298,6 +315,19 @@

Settings

+ + + + + +
diff --git a/editor.js b/editor.js index 7703b51..0ffbf54 100644 --- a/editor.js +++ b/editor.js @@ -7,6 +7,10 @@ document.addEventListener('DOMContentLoaded', () => { const exportBtn = document.getElementById('export-btn'); const exportDropdownToggle = document.getElementById('export-dropdown-toggle'); const exportMenu = document.getElementById('export-menu'); + const importBtn = document.getElementById('import-btn'); + const importInput = document.getElementById('import-input'); + const dropOverlay = document.getElementById('drop-overlay'); + const appMain = document.getElementById('app-main'); const exportPdfBtn = document.getElementById('export-pdf-btn'); const exportHtmlBtn = document.getElementById('export-html-btn'); const sidebarToggle = document.getElementById('sidebar-toggle'); @@ -31,6 +35,8 @@ document.addEventListener('DOMContentLoaded', () => { const replaceAllBtn = document.getElementById('replace-all-btn'); const closeFindBtn = document.getElementById('close-find-btn'); const editorBackdrop = document.getElementById('editor-backdrop'); + const lineNumbers = document.getElementById('line-numbers'); + const zenExitPill = document.getElementById('zen-exit-pill'); // Modal Elements const modalOverlay = document.getElementById('modal-overlay'); @@ -49,6 +55,8 @@ document.addEventListener('DOMContentLoaded', () => { activeTab: 'outline', findMatches: [], currentMatchIndex: -1, + showLineNumbers: localStorage.getItem('show-line-numbers') === 'true', + isZenMode: false, }; // 3. Modern Tech Setup @@ -177,6 +185,57 @@ document.addEventListener('DOMContentLoaded', () => { charCountEl.textContent = text.length; wordCountEl.textContent = wordCount; + updateLineNumbers(); + }; + + const updateLineNumbers = () => { + if (!state.showLineNumbers) return; + const lines = editor.value.split('\n'); + const count = lines.length; + let html = ''; + for (let i = 1; i <= count; i++) { + html += `
${i}
`; + } + lineNumbers.innerHTML = html; + }; + + const toggleLineNumbers = (show) => { + state.showLineNumbers = show; + lineNumbers.classList.toggle('hidden', !show); + document.body.classList.toggle('show-line-numbers', show); + localStorage.setItem('show-line-numbers', show); + if (show) updateLineNumbers(); + }; + + const toggleZenMode = (show) => { + if (show === undefined) show = !state.isZenMode; + state.isZenMode = show; + + document.body.classList.add('fade-exit'); + requestAnimationFrame(() => { + document.body.classList.add('fade-exit-active'); + }); + + setTimeout(() => { + document.body.classList.remove('fade-exit', 'fade-exit-active'); + document.body.classList.toggle('zen-mode', show); + document.body.classList.add('fade-enter'); + requestAnimationFrame(() => { + document.body.classList.add('fade-enter-active'); + }); + + setTimeout(() => { + document.body.classList.remove('fade-enter', 'fade-enter-active'); + }, 300); + + if (show) { + zenExitPill.classList.remove('hidden'); + setTimeout(() => zenExitPill.classList.add('hidden'), 2000); + if (state.isSidebarVisible) toggleSidebar(false); + } else { + zenExitPill.classList.add('hidden'); + } + }, 300); }; const saveState = () => { @@ -339,6 +398,7 @@ document.addEventListener('DOMContentLoaded', () => { preview.scrollTop = scrollPercentage * (preview.scrollHeight - preview.clientHeight); } editorBackdrop.scrollTop = editor.scrollTop; + lineNumbers.scrollTop = editor.scrollTop; }; // --- Find & Replace Logic --- @@ -463,19 +523,19 @@ document.addEventListener('DOMContentLoaded', () => { }; // 5. File Operations - const createNewFile = async () => { + const createNewFile = async (name, content) => { const newId = `file_${Date.now()}`; const fileNumber = state.files.length + 1; const newFile = { id: newId, - name: `Untitled ${fileNumber}`, - content: `# Untitled ${fileNumber}\n\nStart writing your markdown here.` + name: name || `Untitled ${fileNumber}`, + content: content || `# Untitled ${fileNumber}\n\nStart writing your markdown here.` }; state.files.push(newFile); state.activeFileId = newId; await RazenFS.saveFile(newFile); - toast('File created', 'success'); + toast(name ? 'File imported' : 'File created', 'success'); updateEditorAndPreview(); return newId; }; @@ -775,6 +835,65 @@ document.addEventListener('DOMContentLoaded', () => { exportMenu.classList.add('hidden'); }); + importBtn.addEventListener('click', () => { + importInput.click(); + }); + + importInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + handleFileImport(file); + } + importInput.value = ''; + }); + + const handleFileImport = (file) => { + if (!file.name.endsWith('.md') && !file.name.endsWith('.txt')) { + toast('Only .md and .txt files are supported', 'error'); + return; + } + + const reader = new FileReader(); + reader.onload = async (e) => { + const content = e.target.result; + const name = file.name.replace(/\.(md|txt)$/, ''); + const newId = await createNewFile(name, content); + switchActiveFile(newId); + }; + reader.readAsText(file); + }; + + // Drag and Drop + const handleDragOver = (e) => { + e.preventDefault(); + e.stopPropagation(); + dropOverlay.classList.remove('hidden'); + }; + + const handleDragLeave = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (e.relatedTarget === null || !dropOverlay.contains(e.relatedTarget)) { + dropOverlay.classList.add('hidden'); + } + }; + + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + dropOverlay.classList.add('hidden'); + + const file = e.dataTransfer.files[0]; + if (file) { + handleFileImport(file); + } + }; + + appMain.addEventListener('dragenter', handleDragOver); + dropOverlay.addEventListener('dragover', (e) => e.preventDefault()); + dropOverlay.addEventListener('dragleave', handleDragLeave); + dropOverlay.addEventListener('drop', handleDrop); + document.addEventListener('click', (e) => { if (!e.target.closest('.export-dropdown')) { exportMenu.classList.add('hidden'); @@ -905,6 +1024,12 @@ document.addEventListener('DOMContentLoaded', () => { const init = async () => { const savedTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); applyTheme(savedTheme); + toggleLineNumbers(state.showLineNumbers); + + const savedEditorSize = localStorage.getItem('editor-font-size') || '16'; + const savedPreviewSize = localStorage.getItem('preview-font-size') || '16'; + document.documentElement.style.setProperty('--editor-font-size', `${savedEditorSize}px`); + document.documentElement.style.setProperty('--preview-font-size', `${savedPreviewSize}px`); await loadState(); @@ -917,6 +1042,12 @@ document.addEventListener('DOMContentLoaded', () => { }; // Global keyboard shortcuts + window.addEventListener('settings-updated', (e) => { + if (e.detail.showLineNumbers !== undefined) { + toggleLineNumbers(e.detail.showLineNumbers); + } + }); + window.addEventListener('keydown', (e) => { const isMod = e.ctrlKey || e.metaKey; @@ -965,9 +1096,20 @@ document.addEventListener('DOMContentLoaded', () => { const currentTheme = document.documentElement.getAttribute('data-theme'); applyTheme(currentTheme === 'dark' ? 'light' : 'dark'); break; + case 'f': + if (e.shiftKey) { + e.preventDefault(); + toggleZenMode(); + } + break; } + } else if (e.key === 'F11') { + e.preventDefault(); + toggleZenMode(); } else if (e.key === 'Escape') { - if (!findReplacePanel.classList.contains('hidden')) { + if (state.isZenMode) { + toggleZenMode(false); + } else if (!findReplacePanel.classList.contains('hidden')) { toggleFindReplace(false); } } diff --git a/index.html b/index.html index d499ca9..97328a3 100644 --- a/index.html +++ b/index.html @@ -79,6 +79,17 @@

My Documents

+ + + + +
+