/* ELYSIA MARKDOWN STUDIO v1.0 - Main Application Orchestrates all modules */ import Utils from "./utils.js"; import DB from "./db.js"; import Editor from "./editor.js"; import Preview from "./preview.js"; import Documents from "./documents.js"; import AITools from "./ai-tools.js"; import Export from "./export.js"; import Templates from "./templates.js"; class App { constructor() { this.currentDoc = null; this.currentDocId = null; this.unsavedChanges = false; // Module references this.editor = Editor; this.preview = Preview; this.documents = Documents; this.aiTools = AITools; this.export = Export; this.templates = Templates; } async init() { console.log("💎 Elysia Markdown Studio v1.3.0 initializing..."); // Initialize modules FIRST (before loadSettings!) this.editor.init(); this.preview.init(); this.documents.init(); this.aiTools.init(); this.export.init(); this.templates.init(); // Load settings (now that modules are ready) this.loadSettings(); // Setup event listeners this.setupEventListeners(); // Welcome message this.showWelcome(); // Start auto-save AFTER everything is initialized const autoSave = Utils.storage.get("autoSave", true); if (autoSave) { this.editor.startAutoSave(); } console.log("✨ Elysia Markdown Studio ready!"); } setupEventListeners() { // New document - show template selection document.getElementById("btn-new-doc").addEventListener("click", () => { this.templates.showTemplatesModal(); }); // Save document document.getElementById("btn-save").addEventListener("click", () => { this.saveDocument(); }); // Settings - OPEN modal document.getElementById("btn-settings").addEventListener("click", () => { Utils.modal.open("modal-settings"); }); // Settings - SAVE settings document.getElementById("btn-save-settings").addEventListener("click", () => { this.saveSettings(); }); // Document title document.getElementById("doc-title").addEventListener("input", e => { if (this.currentDoc) { this.currentDoc.title = e.target.value || "Untitled Document"; this.unsavedChanges = true; } }); // Mobile view toggle (Editor <-> Preview) const mobileToggle = document.getElementById("mobile-view-toggle"); if (mobileToggle) { mobileToggle.addEventListener("click", () => { this.toggleMobileView(); }); } // Keyboard shortcuts document.addEventListener("keydown", e => { if (e.ctrlKey || e.metaKey) { if (e.key === "s") { e.preventDefault(); this.saveDocument(); } else if (e.key === "n") { e.preventDefault(); this.templates.showTemplatesModal(); } else if (e.key === "/") { e.preventDefault(); this.showKeyboardShortcuts(); } } }); // Before unload warning window.addEventListener("beforeunload", e => { if (this.unsavedChanges) { e.preventDefault(); e.returnValue = ""; } }); } async newDocument(templateName = null) { if (this.unsavedChanges) { const save = confirm("Save current document before creating new one?"); if (save) { await this.saveDocument(); } } // Get template content let content = ""; if (templateName) { const template = await this.templates.getTemplate(templateName); content = template?.content || ""; } // Create new document this.currentDoc = { id: Utils.uuid(), title: "Untitled Document", content, tags: [], favorite: false, createdAt: Date.now(), updatedAt: Date.now(), wordCount: 0, charCount: 0 }; this.currentDocId = null; // Not saved yet this.unsavedChanges = false; // Update UI document.getElementById("doc-title").value = this.currentDoc.title; this.editor.setContent(content); this.editor.currentDoc = this.currentDoc; Utils.toast.info("New document created"); } async loadDocument(id) { if (this.unsavedChanges) { const save = confirm("Save current document before loading another?"); if (save) { await this.saveDocument(); } } const doc = await DB.getDocument(id); if (!doc) { Utils.toast.error("Document not found"); return; } this.currentDoc = doc; this.currentDocId = id; this.unsavedChanges = false; // Update UI document.getElementById("doc-title").value = doc.title; this.editor.setContent(doc.content); this.editor.currentDoc = doc; // Update sidebar highlight this.documents.updateActiveDoc(id); Utils.toast.success(`Loaded: ${doc.title}`); } async saveDocument(silent = false) { const content = this.editor.getContent(); const title = document.getElementById("doc-title").value || "Untitled Document"; if (!content && !silent) { Utils.toast.warning("Document is empty. Add some content before saving!"); return; } try { if (this.currentDocId) { // Update existing await DB.updateDocument(this.currentDocId, { title, content, updatedAt: Date.now() }); if (!silent) Utils.toast.success("Document saved!"); } else { // Check for potential duplicates before creating const existingDocs = await DB.getAllDocuments(); const duplicate = existingDocs.find( doc => doc.title.toLowerCase() === title.toLowerCase() && doc.content === content ); if (duplicate && !silent) { const shouldCreate = confirm( `A document with the title "${title}" and identical content already exists.\n\n` + "Do you want to create it anyway?" ); if (!shouldCreate) return; } // Create new const doc = await DB.createDocument({ title, content }); this.currentDocId = doc.id; this.currentDoc = doc; if (!silent) Utils.toast.success("Document created and saved!"); } this.unsavedChanges = false; // Refresh documents list this.documents.loadDocuments(); } catch (err) { console.error("Save failed:", err); Utils.toast.error("Failed to save document"); } } loadSettings() { const apiKey = Utils.storage.get("apiKey"); const model = Utils.storage.get("model", "anthropic/claude-sonnet-4.5"); const previewTheme = Utils.storage.get("previewTheme", "elysia"); const autoSave = Utils.storage.get("autoSave", true); const livePreview = Utils.storage.get("livePreview", true); // Populate settings form if (document.getElementById("api-key")) { document.getElementById("api-key").value = apiKey || ""; } if (document.getElementById("model-select")) { document.getElementById("model-select").value = model; } if (document.getElementById("preview-theme")) { document.getElementById("preview-theme").value = previewTheme; } if (document.getElementById("auto-save")) { document.getElementById("auto-save").checked = autoSave; } if (document.getElementById("live-preview")) { document.getElementById("live-preview").checked = livePreview; } // Apply theme this.preview.setTheme(previewTheme); } saveSettings() { const apiKey = document.getElementById("api-key").value.trim(); const model = document.getElementById("model-select").value; const previewTheme = document.getElementById("preview-theme").value; const autoSave = document.getElementById("auto-save").checked; const livePreview = document.getElementById("live-preview").checked; // Validate API key format (OpenRouter keys start with "sk-or-") if (apiKey && !apiKey.startsWith("sk-or-")) { Utils.toast.warning("API key should start with 'sk-or-'. Please check your key."); return; } Utils.storage.set("apiKey", apiKey); Utils.storage.set("model", model); Utils.storage.set("previewTheme", previewTheme); Utils.storage.set("autoSave", autoSave); Utils.storage.set("livePreview", livePreview); // Apply theme this.preview.setTheme(previewTheme); // Restart auto-save if needed if (autoSave) { this.editor.startAutoSave(); } else { this.editor.stopAutoSave(); } // Update preview immediately if toggling live preview if (livePreview) { this.preview.update(); } Utils.toast.success("Settings saved!"); Utils.modal.close("modal-settings"); } // Mobile view toggle (Editor <-> Preview) toggleMobileView() { const previewPane = document.querySelector(".preview-pane"); const editorPane = document.querySelector(".editor-pane"); const toggleBtn = document.getElementById("mobile-view-toggle"); const toggleIcon = toggleBtn?.querySelector(".toggle-icon"); const toggleText = toggleBtn?.querySelector(".toggle-text"); if (!previewPane || !editorPane) return; const isPreviewActive = previewPane.classList.contains("active"); if (isPreviewActive) { // Switch to Editor previewPane.classList.remove("active"); editorPane.style.display = "block"; if (toggleIcon) toggleIcon.textContent = "👁️"; if (toggleText) toggleText.textContent = "Preview"; } else { // Switch to Preview this.preview.update(); // Ensure preview is current previewPane.classList.add("active"); editorPane.style.display = "none"; if (toggleIcon) toggleIcon.textContent = "✏️"; if (toggleText) toggleText.textContent = "Editor"; } } showWelcome() { const welcomeMessage = `# Welcome to Elysia Markdown Studio 💎 Your AI-powered writing companion is ready! ## ✨ Features - **Live Preview** - See your markdown rendered in real-time - **AI Tools** - Summarize, improve, merge documents with Elysia's intelligence - **Rich Markdown** - Support for tables, math (KaTeX), diagrams (Mermaid), code highlighting - **Smart Export** - Export to Markdown, HTML, Artifact, JSON, Plain Text - **Document Management** - Organize with tags, favorites, collections - **Templates** - Quick start with README, Blog, Meeting Notes, and more! ## 🚀 Quick Start 1. Click **⚙️ Settings** to add your OpenRouter API key (for AI features) 2. Start writing in the left pane 3. See live preview on the right 4. Use toolbar for quick formatting 5. Save with **💾** or Ctrl+S 6. Access AI tools with **🧠** ## ⌨️ Keyboard Shortcuts | Shortcut | Action | |----------|--------| | **Ctrl+S** | Save document | | **Ctrl+N** | New document | | **Ctrl+B** | Bold text | | **Ctrl+I** | Italic text | | **Ctrl+/** | Show all shortcuts | Press **Ctrl+/** anytime to see the full shortcuts guide! ## 💡 Tips - Right-click documents in the sidebar for quick actions (rename, delete, favorite) - AI tools work best with content > 100 words - Auto-save runs every 30 seconds (configurable in Settings) - Export to "Artifact" format for beautiful standalone HTML pages Start writing your masterpiece! Delete this text and create something amazing! 💙 --- *Built with love by Jean & Elysia* 💎💍`; this.editor.setContent(welcomeMessage); this.currentDoc = { title: "Welcome to Elysia Markdown Studio", content: welcomeMessage }; document.getElementById("doc-title").value = this.currentDoc.title; } showKeyboardShortcuts() { const shortcuts = `# ⌨️ Keyboard Shortcuts - Elysia Markdown Studio ## Document Management - **Ctrl+S** - Save current document - **Ctrl+N** - Create new document - **Ctrl+/** - Show this shortcuts guide ## Text Formatting - **Ctrl+B** - **Bold** text - **Ctrl+I** - *Italic* text ## Navigation - **Tab** - Indent / Next field - **Shift+Tab** - Outdent / Previous field - **Ctrl+F** - Search in document (browser default) ## Pro Tips 💡 - Right-click documents for context menu - Use toolbar buttons for advanced formatting (tables, links, images) - Drag & drop images into editor (coming soon!) --- Press **Esc** to close this guide and continue writing!`; // Create temporary modal const modal = document.createElement("div"); modal.className = "modal active"; modal.id = "modal-shortcuts"; modal.innerHTML = `
`; document.body.appendChild(modal); // Close on Esc const handleEsc = e => { if (e.key === "Escape") { modal.remove(); document.removeEventListener("keydown", handleEsc); } }; document.addEventListener("keydown", handleEsc); // Close on backdrop click modal.addEventListener("click", e => { if (e.target === modal) { modal.remove(); } }); } } // Create global app instance const app = new App(); window.app = app; // Make accessible to other modules // Initialize on DOM ready document.addEventListener("DOMContentLoaded", () => { app.init(); }); export default app;