Mercurial
view mrjunejune/src/notes/editor.js @ 201:6cdee35a7ba9
[MrJuneJune] notes
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 15 Feb 2026 07:07:50 -0800 |
| parents | |
| children | b9b184b3303c |
line wrap: on
line source
console.log("june"); let editor = null; let currentNoteId = 'index'; function getAuthToken() { return localStorage.getItem('notes-auth-token'); } function requireAuth() { if (!getAuthToken()) { const returnUrl = encodeURIComponent(window.location.pathname); window.location.href = '/notes/login?return=' + returnUrl; return false; } return true; } function logout() { localStorage.removeItem('notes-auth-token'); window.location.href = '/notes/login'; } function getNoteIdFromPath() { const path = window.location.pathname; const match = path.match(/^\/notes\/(.+)$/); if (match && match[1] && match[1] !== 'login') { return decodeURIComponent(match[1]); } return 'index'; } function showNewNoteDialog() { document.getElementById('new-note-dialog').classList.add('show'); document.getElementById('new-note-id').focus(); } function hideNewNoteDialog() { document.getElementById('new-note-dialog').classList.remove('show'); document.getElementById('new-note-id').value = ''; } function createNewNote() { let noteId = document.getElementById('new-note-id').value.trim(); if (!noteId) return; // Sanitize note ID noteId = noteId.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-'); hideNewNoteDialog(); window.location.href = '/notes/' + encodeURIComponent(noteId); } // Handle Enter key in new note dialog document.getElementById('new-note-id').addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); createNewNote(); } if (e.key === 'Escape') { hideNewNoteDialog(); } }); // Close dialog on backdrop click document.getElementById('new-note-dialog').addEventListener('click', function(e) { if (e.target === this) { hideNewNoteDialog(); } }); async function uploadFile(file) { const token = getAuthToken(); if (!token) { throw new Error('Not authenticated'); } // 1. Create media record const createResp = await fetch('/api/media/create', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: file.name, content_type: file.type }) }); if (!createResp.ok) { const error = await createResp.json(); throw new Error(error.error || 'Failed to create media record'); } const { media_id, upload_url } = await createResp.json(); // 2. Upload to S3 const uploadResp = await fetch(upload_url, { method: 'PUT', headers: { 'Content-Type': file.type }, body: file }); if (!uploadResp.ok) { throw new Error('S3 upload failed'); } // 3. Mark uploaded await fetch(`/api/media/${media_id}/uploaded`, { method: 'POST', headers: { 'Authorization': 'Bearer ' + token } }); // 4. Poll for images, immediate return for non-images if (file.type.startsWith('image/')) { return await pollForProcessedImage(media_id); } else { // For non-images, return the original S3 URL const s3_url = upload_url.split('?')[0]; return { url: s3_url }; } } async function pollForProcessedImage(mediaId) { const token = getAuthToken(); const maxAttempts = 60; // 2 minutes max for (let i = 0; i < maxAttempts; i++) { await new Promise(r => setTimeout(r, 2000)); // 2 sec interval const resp = await fetch(`/api/media/${mediaId}/status`, { headers: { 'Authorization': 'Bearer ' + token } }); if (!resp.ok) continue; const { status, processed_url, error_message } = await resp.json(); if (status === 'finished') return { url: processed_url }; if (status === 'error') throw new Error(error_message || 'Processing failed'); } throw new Error('Processing timeout'); } async function saveContent(content) { const token = getAuthToken(); if (!token) return; const response = await fetch('/api/editor/save', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ doc_id: currentNoteId, content: content }) }); if (!response.ok) { throw new Error('Failed to save'); } } async function loadNote(noteId) { const token = getAuthToken(); if (!token) return; try { const response = await fetch('/api/editor/load/' + encodeURIComponent(noteId), { headers: { 'Authorization': 'Bearer ' + token } }); if (response.ok) { const data = await response.json(); editor.setContent(data.content || ''); } } catch (error) { console.error('Failed to load note:', error); } } // Initialize document.addEventListener('DOMContentLoaded', function() { if (!requireAuth()) return; currentNoteId = getNoteIdFromPath(); document.getElementById('note-id-display').textContent = currentNoteId; // Update page title document.title = currentNoteId + ' | Notes'; editor = RichEditor.init('editor-container', { uploadCallback: uploadFile, saveCallback: saveContent, debounceMs: 1500, placeholder: 'Start writing... (paste images, drag files, or use /upload)\n\nTip: Click "+ New Note" to create linked notes.' }); loadNote(currentNoteId); });