// Document View
import { api } from '../api.js';
export async function renderDocument(app) {
const { id, projectId } = app.state.params;
let doc;
try {
doc = await api.getDocument(id);
} catch (e) {
app.showToast('Failed to load document', 'error');
app.navigate('projects');
return;
}
window.backToProject = () => {
if (projectId) {
app.navigate('project', { id: projectId });
} else {
app.navigate('projects');
}
};
// Mobile menu functions
window.toggleDocumentMenu = function() {
const menu = document.getElementById('document-menu');
if (menu) menu.classList.toggle('open');
};
window.closeDocumentMenu = function() {
const menu = document.getElementById('document-menu');
if (menu) menu.classList.remove('open');
};
const appEl = document.getElementById('app');
function render() {
const priorityEmoji = { high: '🔴', medium: '🟡', low: '🟢' };
const priority = doc.priority || 'medium';
const renderedContent = renderMarkdown(doc.content || '');
appEl.innerHTML = `
`;
window.filterByTag = (tag) => {
// Store the tag to filter by in app state so dashboard can pick it up
app.state.selectedTag = tag;
app.state.selectedLibrary = null;
backToProject();
};
}
render();
async function exportDoc() {
try {
const markdown = await api.exportDocument(id);
const blob = new Blob([markdown], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
// Sanitize filename to prevent path traversal
const safeFilename = (doc.title || 'untitled')
.replace(/[^a-zA-Z0-9_\-\s]/g, '')
.replace(/\s+/g, '-')
.substring(0, 100);
a.download = `${doc.id}-${safeFilename}.md`;
a.click();
URL.revokeObjectURL(url);
app.showToast('Document exported', 'success');
} catch (e) {
app.showToast('Failed to export', 'error');
}
}
async function deleteDoc() {
const confirmed = await app.confirmDelete(`Delete "${doc.title}"? This cannot be undone.`);
if (confirmed) {
try {
await api.deleteDocument(id);
app.showToast('Document deleted', 'success');
backToProject();
} catch (e) {
app.showToast('Failed to delete', 'error');
}
}
}
}
function escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
function renderMarkdown(content) {
// Simple markdown rendering using marked library if available
if (typeof marked !== 'undefined') {
return marked.parse(content);
}
// Fallback simple rendering
return content
.replace(/^### (.+)$/gm, '$1
')
.replace(/^## (.+)$/gm, '$1
')
.replace(/^# (.+)$/gm, '$1
')
.replace(/\*\*(.+?)\*\*/g, '$1')
.replace(/\*(.+?)\*/g, '$1')
.replace(/`(.+?)`/g, '$1')
.replace(/^- (.+)$/gm, '$1')
.replace(/(.*<\/li>)/s, '')
.replace(/\n\n/g, '')
.replace(/^(.+)$/gm, (match) => {
if (match.startsWith('<')) return match;
return `
${match}
`;
});
}