Elysia-Suite's picture
Upload 25 files
0b194e5 verified
/*
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 = `
<div class="modal-content">
<div class="modal-header">
<h2>⌨️ Keyboard Shortcuts</h2>
<button class="modal-close" onclick="this.closest('.modal').remove()">Γ—</button>
</div>
<div class="modal-body">
<div class="markdown-preview">${marked.parse(shortcuts)}</div>
</div>
<div class="modal-footer">
<button class="btn-primary" onclick="this.closest('.modal').remove()">Got it!</button>
</div>
</div>
`;
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;