code cleanup (remove export functionality)

Hunter
Wednesday, February 4th, 2026 at 4:52 PM

resources/codemirror-config.js -1
resources/editor.js+1-13
resources/file-operations.js -23
resources/html-utils.js -10
resources/page-flip-animation.js+5-9
resources/preview-manager.js -3
resources/spread-layout.js+1-1
resources/viewer-generator.jsdeleted

resources/codemirror-config.js
 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,
resources/editor.js
 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();
resources/file-operations.js
   -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';
resources/html-utils.js
 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();
resources/page-flip-animation.js
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);
resources/preview-manager.js
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  
resources/spread-layout.js
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
resources/viewer-generator.js
  -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 };
  -}