Spaces:
Running
Running
| /* | |
| ELYSIA MARKDOWN STUDIO v1.0 - Database Layer | |
| IndexedDB for documents storage | |
| */ | |
| import Utils from "./utils.js"; | |
| // Initialize Dexie Database | |
| const db = new Dexie("ElysiaMarkdownStudio"); | |
| // Version 2: Add indexes for search performance | |
| db.version(2) | |
| .stores({ | |
| documents: "++id, title, createdAt, updatedAt, favorite, *tags, collection", | |
| collections: "++id, name, createdAt", | |
| templates: "++id, name, category" | |
| }) | |
| .upgrade(tx => { | |
| // Migration: Ensure all documents have required fields | |
| return tx | |
| .table("documents") | |
| .toCollection() | |
| .modify(doc => { | |
| if (!doc.collection) doc.collection = null; | |
| if (!doc.tags) doc.tags = []; | |
| if (!doc.favorite) doc.favorite = false; | |
| }); | |
| }); | |
| // Backwards compatibility: Version 1 | |
| db.version(1).stores({ | |
| documents: "++id, title, createdAt, updatedAt, favorite, *tags", | |
| collections: "++id, name, createdAt", | |
| templates: "++id, name, category" | |
| }); | |
| const DB = { | |
| // Documents CRUD | |
| async createDocument(data) { | |
| try { | |
| const doc = { | |
| id: Utils.uuid(), | |
| title: data.title || "Untitled Document", | |
| content: data.content || "", | |
| tags: data.tags || [], | |
| favorite: data.favorite || false, | |
| collection: data.collection || null, | |
| createdAt: Date.now(), | |
| updatedAt: Date.now(), | |
| wordCount: Utils.countWords(data.content || ""), | |
| charCount: Utils.countChars(data.content || "") | |
| }; | |
| await db.documents.add(doc); | |
| Utils.toast.success("Document created!"); | |
| return doc; | |
| } catch (err) { | |
| console.error("Failed to create document:", err); | |
| Utils.toast.error("Failed to create document"); | |
| return null; | |
| } | |
| }, | |
| async updateDocument(id, updates) { | |
| try { | |
| const doc = await db.documents.get(id); | |
| if (!doc) throw new Error("Document not found"); | |
| const updated = { | |
| ...doc, | |
| ...updates, | |
| updatedAt: Date.now(), | |
| wordCount: Utils.countWords(updates.content || doc.content), | |
| charCount: Utils.countChars(updates.content || doc.content) | |
| }; | |
| await db.documents.put(updated); | |
| return updated; | |
| } catch (err) { | |
| console.error("Failed to update document:", err); | |
| Utils.toast.error("Failed to save document"); | |
| return null; | |
| } | |
| }, | |
| async getDocument(id) { | |
| try { | |
| return await db.documents.get(id); | |
| } catch (err) { | |
| console.error("Failed to get document:", err); | |
| return null; | |
| } | |
| }, | |
| async getAllDocuments(filter = "all") { | |
| try { | |
| let docs = await db.documents.toArray(); | |
| // Sort by updatedAt (most recent first) | |
| docs.sort((a, b) => b.updatedAt - a.updatedAt); | |
| // Apply filters | |
| if (filter === "recent") { | |
| const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; | |
| docs = docs.filter(d => d.updatedAt > weekAgo); | |
| } else if (filter === "favorites") { | |
| docs = docs.filter(d => d.favorite); | |
| } | |
| return docs; | |
| } catch (err) { | |
| console.error("Failed to get documents:", err); | |
| return []; | |
| } | |
| }, | |
| async deleteDocument(id) { | |
| try { | |
| await db.documents.delete(id); | |
| Utils.toast.success("Document deleted"); | |
| return true; | |
| } catch (err) { | |
| console.error("Failed to delete document:", err); | |
| Utils.toast.error("Failed to delete document"); | |
| return false; | |
| } | |
| }, | |
| async toggleFavorite(id) { | |
| try { | |
| const doc = await db.documents.get(id); | |
| if (!doc) return false; | |
| await db.documents.update(id, { | |
| favorite: !doc.favorite, | |
| updatedAt: Date.now() | |
| }); | |
| return !doc.favorite; | |
| } catch (err) { | |
| console.error("Failed to toggle favorite:", err); | |
| return false; | |
| } | |
| }, | |
| // Search | |
| async searchDocuments(query) { | |
| try { | |
| const docs = await db.documents.toArray(); | |
| query = query.toLowerCase(); | |
| return docs | |
| .filter(doc => { | |
| return ( | |
| doc.title.toLowerCase().includes(query) || | |
| doc.content.toLowerCase().includes(query) || | |
| (doc.tags && doc.tags.some(tag => tag.toLowerCase().includes(query))) | |
| ); | |
| }) | |
| .sort((a, b) => b.updatedAt - a.updatedAt); | |
| } catch (err) { | |
| console.error("Search failed:", err); | |
| return []; | |
| } | |
| }, | |
| // Collections | |
| async createCollection(name) { | |
| try { | |
| const collection = { | |
| name, | |
| createdAt: Date.now() | |
| }; | |
| const id = await db.collections.add(collection); | |
| Utils.toast.success(`Collection "${name}" created`); | |
| return { id, ...collection }; | |
| } catch (err) { | |
| console.error("Failed to create collection:", err); | |
| Utils.toast.error("Failed to create collection"); | |
| return null; | |
| } | |
| }, | |
| async getCollections() { | |
| try { | |
| return await db.collections.toArray(); | |
| } catch (err) { | |
| console.error("Failed to get collections:", err); | |
| return []; | |
| } | |
| }, | |
| // Templates | |
| async saveTemplate(data) { | |
| try { | |
| const template = { | |
| name: data.name, | |
| category: data.category || "Custom", | |
| content: data.content, | |
| description: data.description || "", | |
| createdAt: Date.now() | |
| }; | |
| await db.templates.add(template); | |
| Utils.toast.success(`Template "${data.name}" saved`); | |
| return template; | |
| } catch (err) { | |
| console.error("Failed to save template:", err); | |
| Utils.toast.error("Failed to save template"); | |
| return null; | |
| } | |
| }, | |
| async getTemplates() { | |
| try { | |
| return await db.templates.toArray(); | |
| } catch (err) { | |
| console.error("Failed to get templates:", err); | |
| return []; | |
| } | |
| }, | |
| // Stats | |
| async getStats() { | |
| try { | |
| const docs = await db.documents.toArray(); | |
| const totalWords = docs.reduce((sum, doc) => sum + (doc.wordCount || 0), 0); | |
| const totalChars = docs.reduce((sum, doc) => sum + (doc.charCount || 0), 0); | |
| return { | |
| totalDocuments: docs.length, | |
| totalWords, | |
| totalChars, | |
| favorites: docs.filter(d => d.favorite).length | |
| }; | |
| } catch (err) { | |
| console.error("Failed to get stats:", err); | |
| return { | |
| totalDocuments: 0, | |
| totalWords: 0, | |
| totalChars: 0, | |
| favorites: 0 | |
| }; | |
| } | |
| }, | |
| // Export/Import | |
| async exportAll() { | |
| try { | |
| const docs = await db.documents.toArray(); | |
| const collections = await db.collections.toArray(); | |
| const templates = await db.templates.toArray(); | |
| return { | |
| documents: docs, | |
| collections, | |
| templates, | |
| exportDate: Date.now(), | |
| version: "1.0" | |
| }; | |
| } catch (err) { | |
| console.error("Export failed:", err); | |
| return null; | |
| } | |
| }, | |
| async importAll(data) { | |
| try { | |
| if (data.documents) { | |
| await db.documents.bulkPut(data.documents); | |
| } | |
| if (data.collections) { | |
| await db.collections.bulkPut(data.collections); | |
| } | |
| if (data.templates) { | |
| await db.templates.bulkPut(data.templates); | |
| } | |
| Utils.toast.success("Import successful!"); | |
| return true; | |
| } catch (err) { | |
| console.error("Import failed:", err); | |
| Utils.toast.error("Import failed"); | |
| return false; | |
| } | |
| } | |
| }; | |
| export default DB; | |