Spaces:
Running
Running
| /* | |
| ELYSIA MARKDOWN STUDIO v1.0 - Documents Module | |
| Document management and sidebar | |
| */ | |
| import Utils from "./utils.js"; | |
| import DB from "./db.js"; | |
| const Documents = { | |
| currentFilter: "all", | |
| searchQuery: "", | |
| init() { | |
| this.setupEventListeners(); | |
| this.loadDocuments(); | |
| }, | |
| setupEventListeners() { | |
| // Open/close sidebar | |
| document.getElementById("btn-documents").addEventListener("click", () => { | |
| this.toggleSidebar(); | |
| }); | |
| document.getElementById("btn-close-sidebar").addEventListener("click", () => { | |
| this.toggleSidebar(); | |
| }); | |
| // Filter tabs | |
| document.querySelectorAll(".filter-tab").forEach(tab => { | |
| tab.addEventListener("click", () => { | |
| const filter = tab.getAttribute("data-filter"); | |
| this.setFilter(filter); | |
| }); | |
| }); | |
| // Search | |
| document.getElementById("search-docs").addEventListener( | |
| "input", | |
| Utils.debounce(e => { | |
| this.searchQuery = e.target.value; | |
| this.loadDocuments(); | |
| }, 300) | |
| ); | |
| }, | |
| toggleSidebar() { | |
| const sidebar = document.getElementById("sidebar-docs"); | |
| const main = document.querySelector(".main-container"); | |
| sidebar.classList.toggle("collapsed"); | |
| main.classList.toggle("sidebar-open"); | |
| if (!sidebar.classList.contains("collapsed")) { | |
| this.loadDocuments(); | |
| } | |
| }, | |
| setFilter(filter) { | |
| this.currentFilter = filter; | |
| // Update active tab | |
| document.querySelectorAll(".filter-tab").forEach(tab => { | |
| tab.classList.toggle("active", tab.getAttribute("data-filter") === filter); | |
| }); | |
| this.loadDocuments(); | |
| }, | |
| async loadDocuments() { | |
| const container = document.getElementById("documents-list"); | |
| try { | |
| let docs; | |
| if (this.searchQuery) { | |
| docs = await DB.searchDocuments(this.searchQuery); | |
| } else { | |
| docs = await DB.getAllDocuments(this.currentFilter); | |
| } | |
| if (docs.length === 0) { | |
| container.innerHTML = ` | |
| <div class="empty-state"> | |
| <p>📄 No documents found</p> | |
| <p class="hint">Create your first document!</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| container.innerHTML = docs | |
| .map( | |
| doc => ` | |
| <div class="doc-item ${window.app?.currentDocId === doc.id ? "active" : ""}" data-id="${doc.id}"> | |
| <div class="doc-item-title"> | |
| ${doc.favorite ? "⭐ " : ""}${Utils.truncate(doc.title, 40)} | |
| </div> | |
| <div class="doc-item-meta"> | |
| ${doc.wordCount || 0} words • ${Utils.formatDate(doc.updatedAt)} | |
| </div> | |
| </div> | |
| ` | |
| ) | |
| .join(""); | |
| // Add click handlers | |
| container.querySelectorAll(".doc-item").forEach(item => { | |
| item.addEventListener("click", () => { | |
| const id = item.getAttribute("data-id"); | |
| window.app?.loadDocument(id); | |
| }); | |
| // Right-click context menu | |
| item.addEventListener("contextmenu", e => { | |
| e.preventDefault(); | |
| this.showContextMenu(e, item.getAttribute("data-id")); | |
| }); | |
| }); | |
| } catch (err) { | |
| console.error("Failed to load documents:", err); | |
| Utils.toast.error("Failed to load documents"); | |
| } | |
| }, | |
| showContextMenu(e, docId) { | |
| // Remove any existing context menu | |
| const existingMenu = document.querySelector(".context-menu"); | |
| if (existingMenu) existingMenu.remove(); | |
| // Create context menu | |
| const menu = document.createElement("div"); | |
| menu.className = "context-menu"; | |
| menu.innerHTML = ` | |
| <div class="context-menu-item" data-action="favorite"> | |
| <span class="context-icon">⭐</span> | |
| <span>Toggle Favorite</span> | |
| </div> | |
| <div class="context-menu-item" data-action="rename"> | |
| <span class="context-icon">✏️</span> | |
| <span>Rename</span> | |
| </div> | |
| <div class="context-menu-divider"></div> | |
| <div class="context-menu-item danger" data-action="delete"> | |
| <span class="context-icon">🗑️</span> | |
| <span>Delete</span> | |
| </div> | |
| `; | |
| // Position menu with boundary checks | |
| document.body.appendChild(menu); | |
| // Get menu dimensions | |
| const menuRect = menu.getBoundingClientRect(); | |
| const viewportWidth = window.innerWidth; | |
| const viewportHeight = window.innerHeight; | |
| // Calculate position | |
| let left = e.pageX; | |
| let top = e.pageY; | |
| // Prevent horizontal overflow | |
| if (left + menuRect.width > viewportWidth) { | |
| left = viewportWidth - menuRect.width - 10; | |
| } | |
| // Prevent vertical overflow | |
| if (top + menuRect.height > viewportHeight) { | |
| top = viewportHeight - menuRect.height - 10; | |
| } | |
| menu.style.left = `${left}px`; | |
| menu.style.top = `${top}px`; | |
| // Handle clicks | |
| menu.querySelectorAll(".context-menu-item").forEach(item => { | |
| item.addEventListener("click", async () => { | |
| const action = item.getAttribute("data-action"); | |
| switch (action) { | |
| case "favorite": | |
| await this.toggleFavorite(docId); | |
| break; | |
| case "rename": | |
| await this.renameDocument(docId); | |
| break; | |
| case "delete": | |
| const confirmDelete = confirm("Are you sure you want to delete this document?"); | |
| if (confirmDelete) { | |
| await this.deleteDocument(docId); | |
| } | |
| break; | |
| } | |
| menu.remove(); | |
| }); | |
| }); | |
| // Close menu on outside click | |
| const closeMenu = event => { | |
| if (!menu.contains(event.target)) { | |
| menu.remove(); | |
| document.removeEventListener("click", closeMenu); | |
| } | |
| }; | |
| setTimeout(() => { | |
| document.addEventListener("click", closeMenu); | |
| }, 10); | |
| }, | |
| async renameDocument(docId) { | |
| const doc = await DB.getDocument(docId); | |
| if (!doc) return; | |
| const newTitle = prompt("Enter new title:", doc.title); | |
| if (newTitle && newTitle !== doc.title) { | |
| await DB.updateDocument(docId, { title: newTitle }); | |
| Utils.toast.success("Document renamed!"); | |
| this.loadDocuments(); | |
| // Update editor if this is current doc | |
| if (window.app?.currentDocId === docId) { | |
| document.getElementById("doc-title").value = newTitle; | |
| } | |
| } | |
| }, | |
| async toggleFavorite(docId) { | |
| const isFavorite = await DB.toggleFavorite(docId); | |
| Utils.toast.success(isFavorite ? "Added to favorites" : "Removed from favorites"); | |
| this.loadDocuments(); | |
| }, | |
| async deleteDocument(docId) { | |
| await DB.deleteDocument(docId); | |
| // If deleting current doc, clear editor | |
| if (window.app?.currentDocId === docId) { | |
| window.app.newDocument(); | |
| } | |
| this.loadDocuments(); | |
| }, | |
| // Update current doc item highlight | |
| updateActiveDoc(docId) { | |
| document.querySelectorAll(".doc-item").forEach(item => { | |
| item.classList.toggle("active", item.getAttribute("data-id") === docId); | |
| }); | |
| } | |
| }; | |
| export default Documents; | |