Elysia-Suite's picture
Upload 25 files
0b194e5 verified
/*
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;