code cleanup (remove export functionality) Hunter Wednesday, February 4th, 2026 at 4:52 PM
97 {key: "Mod-Shift-z", run: redo}, 98 {key: "Mod-o", run: () => { window.loadFile(); return true; }}, 99 {key: "Mod-s", run: () => { window.saveFile(); return true; }}, - {key: "Mod-e", run: () => { window.saveFileWithViewer(); return true; }}, 100 {key: "F1", run: (view) => { toggleLineNumbers(view); return true; }}, 101 {key: "F2", run: (view) => { toggleLineWrapping(view); return true; }}, 102 indentWithTab,
1 import { saveToStorage } from './storage.js'; -import { saveFile, saveFileWithViewer, loadFile } from './file-operations.js'; 2 +import { saveFile, loadFile } from './file-operations.js'; 3 import { updatePreview, setupPreviewPane } from './preview-manager.js'; 4 import { navigateSpread, toggleFullscreen, initializeSpreadNav, getIsFullscreen } from './spread-navigation.js'; 5 import { initializeCodeMirror } from './codemirror-config.js'; 29 navigateSpread(1, updatePreviewWrapper); 30 } else if (event.data === 'saveFile') { 31 window.saveFile(); - } else if (event.data === 'saveFileWithViewer') { - window.saveFileWithViewer(); 32 } else if (event.data?.type === 'scaleChange') { 33 // Update background grid size and position to match zine preview 34 const previewPane = document.querySelector('.preview-pane'); 58 saveFile(() => editorView.state.doc.toString()); 59 }; 60 -window.saveFileWithViewer = function() { - saveFileWithViewer(() => editorView.state.doc.toString()); -}; - 61 window.loadFile = function() { 62 loadFile(editorView, updatePreviewWrapper); 63 }; 89 // Keyboard shortcuts (only when editor not focused, since CodeMirror handles its own) 90 document.addEventListener('keydown', function(e) { 91 const {closeSearchPanel, openSearchPanel} = window.CodeMirror; - const editorHasFocus = editorView.hasFocus; 92 93 if ((e.metaKey || e.ctrlKey) && e.key === 'f') { 94 e.preventDefault(); 95 closeSearchPanel(editorView) || openSearchPanel(editorView); 96 } - // Export with viewer (Cmd+E) - only handle when editor not focused - if (!editorHasFocus && (e.metaKey || e.ctrlKey) && e.key === 'e') { - e.preventDefault(); - window.saveFileWithViewer(); - } 97 // Print the iframe content instead of the parent page 98 if ((e.metaKey || e.ctrlKey) && e.key === 'p') { 99 e.preventDefault();
-import { insertAfterHead, insertBeforeHeadClose } from './html-utils.js'; -import { generateViewerCode } from './viewer-generator.js'; 1 import { saveToStorage } from './storage.js'; 2 import { setCurrentSpread, updateSpreadIndicator } from './spread-navigation.js'; 3 16 downloadHtmlFile(getEditorContent()); 17 } 18 -export function saveFileWithViewer(getEditorContent) { - const code = getEditorContent(); - const { css, js } = generateViewerCode(); - - // Check if code has proper head tags - const hasHead = /<head[^>]*>/i.test(code) && /<\/head>/i.test(code); - - let processedCode; - if (hasHead) { - // Inject viewer CSS at START of head (so user styles take precedence for page content) - // Inject viewer JS at END of head (needs to run after DOM is ready) - processedCode = insertAfterHead(code, `\n<style id="zine-viewer-css">${css}</style>`); - processedCode = insertBeforeHeadClose(processedCode, `<script id="zine-viewer-js">${js}<\/script>\n`); - } else { - // No proper head found, wrap content - processedCode = `<!DOCTYPE html>\n<html>\n<head>\n<style id="zine-viewer-css">${css}</style>\n<script id="zine-viewer-js">${js}<\/script>\n</head>\n<body>\n${code}\n</body>\n</html>`; - } - - downloadHtmlFile(processedCode); -} - 19 export function loadFile(editorView, updatePreviewCallback) { 20 const input = document.createElement('input'); 21 input.type = 'file';
8 return html; 9 } 10 -// Insert content right before </head> tag in HTML string -export function insertBeforeHeadClose(html, content) { - const headEndMatch = html.match(/<\/head>/i); - if (headEndMatch) { - const insertPos = html.indexOf(headEndMatch[0]); - return html.slice(0, insertPos) + content + html.slice(insertPos); - } - return html; -} - 11 // Extract title and favicon from user's HTML 12 export function extractTitleAndFavicon(htmlCode) { 13 const parser = new DOMParser();
145 146 const nextFlip = pendingFlipQueue.shift(); 147 // Use current spread index as fromSpread since we're now at a different position - executePageFlip(currentSpreadIndex, nextFlip.toSpread, nextFlip.container, nextFlip.doc, nextFlip.onComplete); 148 + executePageFlip(currentSpreadIndex, nextFlip.toSpread, nextFlip.doc, nextFlip.onComplete); 149 } 150 151 // Execute a single page flip (internal function) -function executePageFlip(fromSpread, toSpread, container, doc, onComplete) { 152 +function executePageFlip(fromSpread, toSpread, doc, onComplete) { 153 isAnimating = true; 154 currentSpreadIndex = toSpread; 155 - console.log(`\n=== executePageFlip: ${fromSpread} → ${toSpread} ===`); - 156 // Update book position synchronously with the flip animation 157 updateBookPosition(toSpread, true); 158 159 const direction = toSpread > fromSpread ? 'forward' : 'backward'; - console.log(`Direction: ${direction}`); 160 const leaves = doc.querySelectorAll('.zine-leaf'); 161 162 if (direction === 'forward') { 235 } 236 237 // Animate page flip with input buffering -export function animatePageFlip(fromSpread, toSpread, container, doc, onComplete) { 238 +export function animatePageFlip(fromSpread, toSpread, doc, onComplete) { 239 // If already animating, queue this flip request 240 if (isAnimating) { 241 // Check if there's already a queued flip to the same target - if so, skip this one 242 // This prevents the queue from growing unnecessarily when users spam the same direction 243 const lastQueued = pendingFlipQueue[pendingFlipQueue.length - 1]; 244 if (!lastQueued || lastQueued.toSpread !== toSpread) { - pendingFlipQueue.push({ fromSpread, toSpread, container, doc, onComplete }); 245 + pendingFlipQueue.push({ fromSpread, toSpread, doc, onComplete }); 246 } 247 return; 248 } 249 250 // Start the flip immediately - executePageFlip(fromSpread, toSpread, container, doc, onComplete); 251 + executePageFlip(fromSpread, toSpread, doc, onComplete); 252 } 253 254 // Set current spread without animation (for initial load) 255 export function setSpreadImmediate(spreadIndex, doc) { - console.log(`\n=== setSpreadImmediate(${spreadIndex}) ===`); 256 currentSpreadIndex = spreadIndex; 257 updateLeafStates(spreadIndex, doc); 258 updateBookPosition(spreadIndex, false);
173 } else if ((e.metaKey || e.ctrlKey) && e.key === 's') { 174 e.preventDefault(); 175 parent.postMessage('saveFile', '*'); - } else if ((e.metaKey || e.ctrlKey) && e.key === 'e') { - e.preventDefault(); - parent.postMessage('saveFileWithViewer', '*'); 176 } 177 }); 178
48 export function navigateToSpread(container, spreadIndex, doc, useFlipAnimation = false, onComplete) { 49 if (useFlipAnimation && container.querySelector('.zine-book')) { 50 // Always call animatePageFlip - it now handles queueing internally - animatePageFlip(currentFlipSpread, spreadIndex, container, doc, onComplete); 51 + animatePageFlip(currentFlipSpread, spreadIndex, doc, onComplete); 52 currentFlipSpread = spreadIndex; 53 } else { 54 // Original non-animated behavior
-import { PAGE_IDS, SPREADS, NAV_HEIGHT, NAV_BUTTON_CSS, NAV_BUTTON_STYLES } from './constants.js'; -import { ZINE_PAGE_CSS, ZINE_PRINT_CSS, ZINE_FLIP_CSS } from './zine-styles.js'; - -// Generate standalone viewer CSS and JS (matches preview pane rendering) -export function generateViewerCode() { - const css = ` - * { margin: 0; padding: 0; box-sizing: border-box; } - - @media screen { - html, body { - height: 100% !important; - overflow: hidden !important; - } - body { - background: linear-gradient(to top, #2a2a2a, #3a3a3a) !important; - display: flex !important; - justify-content: center !important; - align-items: center !important; - padding-bottom: ${NAV_HEIGHT}px !important; - } - body > *:not(.zine-spread-container):not(.zine-nav) { - display: none !important; - } - ${ZINE_PAGE_CSS} - ${ZINE_FLIP_CSS} - - /* Dynamic z-index based on leaf state */ - .zine-leaf[data-state="closed"][data-leaf-index="0"] { z-index: 14 !important; } - .zine-leaf[data-state="closed"][data-leaf-index="1"] { z-index: 13 !important; } - .zine-leaf[data-state="closed"][data-leaf-index="2"] { z-index: 12 !important; } - .zine-leaf[data-state="closed"][data-leaf-index="3"] { z-index: 11 !important; } - .zine-leaf[data-state="open"][data-leaf-index="0"] { z-index: 4 !important; } - .zine-leaf[data-state="open"][data-leaf-index="1"] { z-index: 3 !important; } - .zine-leaf[data-state="open"][data-leaf-index="2"] { z-index: 2 !important; } - .zine-leaf[data-state="open"][data-leaf-index="3"] { z-index: 1 !important; } - .zine-leaf[data-state="flipping"] { z-index: 20 !important; } - - .zine-spread-container { - display: flex !important; - position: relative; - } - .zine-nav { - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: ${NAV_HEIGHT}px; - ${NAV_BUTTON_CSS} - background: transparent; - } - .zine-nav button { - ${NAV_BUTTON_STYLES} - } - .zine-nav button:hover:not(:disabled) { background: rgba(255,255,255,0.25); } - .zine-nav button:disabled { opacity: 0.3; cursor: default; } - .zine-nav span { color: white; font-size: 14px; min-width: 100px; text-align: center; font-family: monospace; } - } - - @media print { - ${ZINE_PRINT_CSS} - } - - /* Hide content when viewport is very short */ - @media screen and (max-height: 200px) { - .zine-spread-container, .zine-nav { - display: none !important; - } - } - `; - - // Serialize SPREADS array for the viewer - const spreadsJson = JSON.stringify(SPREADS); - const pageIdsJson = JSON.stringify(PAGE_IDS); - - const js = ` - const SPREADS = ${spreadsJson}; - const PAGE_IDS = ${pageIdsJson}; - const NAV_HEIGHT = ${NAV_HEIGHT}; - let currentSpread = 0; - let container; - let isAnimating = false; - const USE_FLIP_ANIMATION = true; - - // Page leaf structure (front and back of each physical page) - const PAGE_LEAVES = [ - { front: 'front-cover', back: 'page1' }, - { front: 'page2', back: 'page3' }, - { front: 'page4', back: 'page5' }, - { front: 'page6', back: 'back-cover' } - ]; - - function easeInOutCubic(t) { - return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; - } - - function scaleToFit() { - if (!container) return; - const bookContainer = container.querySelector('.zine-book') || container; - const vw = document.documentElement.clientWidth; - const vh = document.documentElement.clientHeight - NAV_HEIGHT; - const spreadWidthPx = bookContainer.offsetWidth; - const spreadHeightPx = bookContainer.offsetHeight; - if (spreadWidthPx === 0 || spreadHeightPx === 0) return; - const scaleX = (vw - 40) / spreadWidthPx; - const scaleY = (vh - 40) / spreadHeightPx; - const scale = Math.min(scaleX, scaleY); - bookContainer.style.zoom = scale; - bookContainer.dataset.currentZoom = scale; - } - - function updateBookPosition(spreadIndex, animated) { - const bookContainer = container.querySelector('.zine-book'); - if (!bookContainer) return; - - // Spread 0 (front cover): single right page, shift left to center - // Spread 4 (back cover): single left page, shift right to center - // Doubled to compensate for flex re-centering (net visual shift = half) - let shiftAmount = '0in'; - if (spreadIndex === 0) { - shiftAmount = '-2.75in'; - } else if (spreadIndex === 4) { - shiftAmount = '2.75in'; - } - - if (animated) { - bookContainer.style.transition = 'margin-left 0.6s cubic-bezier(0.645, 0.045, 0.355, 1.000)'; - bookContainer.style.marginLeft = shiftAmount; - - // Remove transition after animation completes - setTimeout(function() { - bookContainer.style.transition = ''; - }, 650); - } else { - bookContainer.style.transition = 'none'; - bookContainer.style.marginLeft = shiftAmount; - bookContainer.offsetHeight; - bookContainer.style.transition = ''; - } - } - - function initFlipMode() { - const book = document.createElement('div'); - book.className = 'zine-book'; - - // Create all leaves - PAGE_LEAVES.forEach((leaf, index) => { - const leafEl = document.createElement('div'); - leafEl.className = 'zine-leaf'; - leafEl.dataset.leafIndex = index; - leafEl.dataset.state = 'closed'; - leafEl.style.zIndex = String(20 - index); // Initial z-index for closed state - - const frontSide = document.createElement('div'); - frontSide.className = 'zine-leaf-front'; - const frontPage = document.getElementById(leaf.front); - if (frontPage) frontSide.appendChild(frontPage.cloneNode(true)); - - const backSide = document.createElement('div'); - backSide.className = 'zine-leaf-back'; - const backPage = document.getElementById(leaf.back); - if (backPage) backSide.appendChild(backPage.cloneNode(true)); - - leafEl.appendChild(frontSide); - leafEl.appendChild(backSide); - book.appendChild(leafEl); - }); - - container.appendChild(book); - } - - function animateFlip(fromSpread, toSpread, onComplete) { - if (isAnimating) return; - isAnimating = true; - - // Update book position synchronously with the flip animation - updateBookPosition(toSpread, true); - - const direction = toSpread > fromSpread ? 'forward' : 'backward'; - const leaves = document.querySelectorAll('.zine-leaf'); - - if (direction === 'forward') { - const leafToFlip = leaves[fromSpread]; - if (leafToFlip) { - leafToFlip.dataset.state = 'flipping'; - leafToFlip.style.zIndex = '100'; - const startTime = performance.now(); - const duration = 600; - - function animate(currentTime) { - const elapsed = currentTime - startTime; - const progress = Math.min(elapsed / duration, 1); - const eased = easeInOutCubic(progress); - const rotation = eased * -180; - leafToFlip.style.transform = 'rotateY(' + rotation + 'deg)'; - - if (progress < 1) { - requestAnimationFrame(animate); - } else { - leafToFlip.dataset.state = 'open'; - leafToFlip.style.zIndex = String(fromSpread + 1); - isAnimating = false; - if (onComplete) onComplete(); - } - } - requestAnimationFrame(animate); - } - } else { - const leafToFlip = leaves[toSpread]; - if (leafToFlip) { - leafToFlip.dataset.state = 'flipping'; - leafToFlip.style.zIndex = '100'; - const startTime = performance.now(); - const duration = 600; - - function animate(currentTime) { - const elapsed = currentTime - startTime; - const progress = Math.min(elapsed / duration, 1); - const eased = easeInOutCubic(progress); - const rotation = -180 + (eased * 180); - leafToFlip.style.transform = 'rotateY(' + rotation + 'deg)'; - - if (progress < 1) { - requestAnimationFrame(animate); - } else { - leafToFlip.dataset.state = 'closed'; - leafToFlip.style.zIndex = String(20 - toSpread); - isAnimating = false; - if (onComplete) onComplete(); - } - } - requestAnimationFrame(animate); - } - } - } - - function showSpread(index, animated) { - const spread = SPREADS[index]; - - if (USE_FLIP_ANIMATION && animated && container.querySelector('.zine-book')) { - animateFlip(currentSpread, index, () => { - document.getElementById('zine-indicator').textContent = spread.label; - document.getElementById('zine-prev').disabled = index === 0; - document.getElementById('zine-next').disabled = index === SPREADS.length - 1; - }); - return; - } - - // Non-animated or initial setup - const visible = [spread.left, spread.right].filter(Boolean); - PAGE_IDS.forEach(id => { - const page = document.getElementById(id); - if (page) { - page.style.display = 'none'; - document.body.appendChild(page); - } - }); - - container.innerHTML = ''; - - if (!spread.left && spread.right) { - const empty = document.createElement('div'); - empty.className = 'zine-empty'; - container.appendChild(empty); - } - - visible.forEach(id => { - const el = document.getElementById(id); - if (el) { - el.style.display = 'block'; - container.appendChild(el); - } - }); - - if (spread.left && !spread.right) { - const empty = document.createElement('div'); - empty.className = 'zine-empty'; - container.appendChild(empty); - } - - document.getElementById('zine-indicator').textContent = spread.label; - document.getElementById('zine-prev').disabled = index === 0; - document.getElementById('zine-next').disabled = index === SPREADS.length - 1; - scaleToFit(); - } - - function navigate(delta) { - const newIndex = currentSpread + delta; - if (newIndex >= 0 && newIndex < SPREADS.length) { - const oldSpread = currentSpread; - currentSpread = newIndex; - showSpread(currentSpread, true); - } - } - - document.addEventListener('DOMContentLoaded', () => { - container = document.createElement('div'); - container.className = 'zine-spread-container'; - document.body.appendChild(container); - - const nav = document.createElement('div'); - nav.className = 'zine-nav'; - nav.innerHTML = '<button id="zine-prev" title="Previous spread">\\u2190</button><span id="zine-indicator"></span><button id="zine-next" title="Next spread">\\u2192</button>'; - document.body.appendChild(nav); - - document.getElementById('zine-prev').addEventListener('click', () => navigate(-1)); - document.getElementById('zine-next').addEventListener('click', () => navigate(1)); - - document.addEventListener('keydown', e => { - if (e.key === 'ArrowLeft') navigate(-1); - else if (e.key === 'ArrowRight') navigate(1); - else if ((e.metaKey || e.ctrlKey) && e.key === 'p') { - e.preventDefault(); - window.print(); - } - else if ((e.metaKey || e.ctrlKey) && (e.key === 's' || e.key === 'e')) { - e.preventDefault(); - // Re-download this viewer file (clean version without dynamic elements) - // Move pages back to body and remove dynamic elements temporarily - PAGE_IDS.forEach(id => { - const page = document.getElementById(id); - if (page) { - page.style.display = ''; - document.body.appendChild(page); - } - }); - const tempContainer = document.querySelector('.zine-spread-container'); - const tempNav = document.querySelector('.zine-nav'); - if (tempContainer) tempContainer.remove(); - if (tempNav) tempNav.remove(); - - const html = '<!DOCTYPE html>' + document.documentElement.outerHTML; - - // Restore dynamic elements - const newContainer = document.createElement('div'); - newContainer.className = 'zine-spread-container'; - document.body.appendChild(newContainer); - container = newContainer; - - const newNav = document.createElement('div'); - newNav.className = 'zine-nav'; - newNav.innerHTML = '<button id="zine-prev" title="Previous spread">\\u2190</button><span id="zine-indicator"></span><button id="zine-next" title="Next spread">\\u2192</button>'; - document.body.appendChild(newNav); - document.getElementById('zine-prev').addEventListener('click', () => navigate(-1)); - document.getElementById('zine-next').addEventListener('click', () => navigate(1)); - - if (USE_FLIP_ANIMATION) { - initFlipMode(); - const leaves = document.querySelectorAll('.zine-leaf'); - leaves.forEach((leaf, index) => { - if (index < currentSpread) { - leaf.dataset.state = 'open'; - leaf.style.transform = 'rotateY(-180deg)'; - leaf.style.zIndex = String(index + 1); - } - }); - scaleToFit(); - updateBookPosition(currentSpread, false); - } else { - showSpread(currentSpread, false); - } - - const blob = new Blob([html], { type: 'text/html' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'my-zine.html'; - a.click(); - URL.revokeObjectURL(url); - } - }); - - window.addEventListener('resize', function() { - scaleToFit(); - if (USE_FLIP_ANIMATION) { - updateBookPosition(currentSpread, false); - } - }); - - // Initialize flip mode - if (USE_FLIP_ANIMATION) { - initFlipMode(); - document.getElementById('zine-indicator').textContent = SPREADS[0].label; - document.getElementById('zine-prev').disabled = true; - document.getElementById('zine-next').disabled = false; - scaleToFit(); - updateBookPosition(0, false); - } else { - showSpread(0, false); - } - }); - `; - - return { css, js }; -}