diff --git a/app.js b/app.js index 8681413..f981420 100644 --- a/app.js +++ b/app.js @@ -19,8 +19,9 @@ let editingEntryId = null; // null = no existing instrument entry let editingOriginalRefIds = []; // ref IDs present when edit modal opened // notes modal state -let notesTuneId = null; -let notesEntryId = null; +let notesTuneId = null; +let notesEntryId = null; +let noteEditorState = null; // { type: 'tune'|'inst', noteId: null|number } let unlocked = false; @@ -840,35 +841,62 @@ function renderInstNotes(notes) { }); } -$('btn-add-tune-note').addEventListener('click', async () => { - const text = prompt('Note:'); - if (!text) return; - try { - await apiFetch(`/tunes/${notesTuneId}/notes`, { method: 'POST', body: JSON.stringify({ note: text }) }); - await reloadNotesModal(); - } catch (e) { setStatus(e.message, true); } -}); +$('btn-add-tune-note').addEventListener('click', () => showNoteEditor('tune', null)); +$('btn-add-inst-note').addEventListener('click', () => showNoteEditor('inst', null)); -$('btn-add-inst-note').addEventListener('click', async () => { - const text = prompt('Note:'); - if (!text) return; - try { - await apiFetch(`/tunes/${notesTuneId}/instruments/${notesEntryId}/notes`, - { method: 'POST', body: JSON.stringify({ note: text }) }); - await reloadNotesModal(); - } catch (e) { setStatus(e.message, true); } -}); +function editTuneNote(n) { showNoteEditor('tune', n.id, n.note); } +function editInstNote(n) { showNoteEditor('inst', n.id, n.note); } -async function editTuneNote(n) { - const text = prompt('Edit note:', n.note); - if (text === null) return; - try { - await apiFetch(`/tunes/${notesTuneId}/notes/${n.id}`, - { method: 'PATCH', body: JSON.stringify({ note: text }) }); - await reloadNotesModal(); - } catch (e) { setStatus(e.message, true); } +function showNoteEditor(type, noteId, existingText = '') { + noteEditorState = { type, noteId }; + $('note-editor-title').textContent = noteId ? 'Edit note' : 'Add note'; + $('note-editor-ta').value = existingText; + $('note-editor-modal').showModal(); + $('note-editor-ta').focus(); } +function hideNoteEditor() { + noteEditorState = null; + $('note-editor-ta').value = ''; + $('note-editor-modal').close(); +} + +$('btn-note-cancel').addEventListener('click', hideNoteEditor); + +$('note-editor-ta').addEventListener('keydown', e => { + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') $('btn-note-save').click(); + if (e.key === 'Escape') hideNoteEditor(); +}); + +$('btn-note-save').addEventListener('click', async () => { + if (!noteEditorState) return; + const text = $('note-editor-ta').value.trim(); + if (!text) return; + const { type, noteId } = noteEditorState; + $('btn-note-save').disabled = true; + try { + if (type === 'tune') { + await apiFetch( + noteId ? `/tunes/${notesTuneId}/notes/${noteId}` : `/tunes/${notesTuneId}/notes`, + { method: noteId ? 'PATCH' : 'POST', body: JSON.stringify({ note: text }) } + ); + } else { + await apiFetch( + noteId + ? `/tunes/${notesTuneId}/instruments/${notesEntryId}/notes/${noteId}` + : `/tunes/${notesTuneId}/instruments/${notesEntryId}/notes`, + { method: noteId ? 'PATCH' : 'POST', body: JSON.stringify({ note: text }) } + ); + } + hideNoteEditor(); + await reloadNotesModal(); + } catch (e) { + setStatus(e.message, true); + } finally { + $('btn-note-save').disabled = false; + } +}); + async function deleteTuneNote(noteId) { if (!confirm('Delete this note?')) return; try { @@ -877,16 +905,6 @@ async function deleteTuneNote(noteId) { } catch (e) { setStatus(e.message, true); } } -async function editInstNote(n) { - const text = prompt('Edit note:', n.note); - if (text === null) return; - try { - await apiFetch(`/tunes/${notesTuneId}/instruments/${notesEntryId}/notes/${n.id}`, - { method: 'PATCH', body: JSON.stringify({ note: text }) }); - await reloadNotesModal(); - } catch (e) { setStatus(e.message, true); } -} - async function deleteInstNote(noteId) { if (!confirm('Delete this note?')) return; try { diff --git a/index.html b/index.html index b0eb916..b2aab72 100644 --- a/index.html +++ b/index.html @@ -171,7 +171,7 @@ - +

Notes

@@ -189,6 +189,16 @@
+ + +

Add note

+ + +
+
diff --git a/style.css b/style.css index e96f8e8..6236ee7 100644 --- a/style.css +++ b/style.css @@ -255,6 +255,9 @@ th.sort-desc .sort-indicator::after { content: '▼'; } /* ── Modal ────────────────────────────────────────────────── */ dialog { + position: fixed; + inset: 0; + margin: auto; background: var(--surface); color: var(--text); border: 1px solid var(--border); @@ -356,7 +359,7 @@ fieldset.active .fs-body { display: contents; } border-bottom: 1px solid var(--border); } .note-row:last-child { border-bottom: none; } -.note-text { flex: 1; font-size: .875rem; } +.note-text { flex: 1; font-size: .875rem; white-space: pre-wrap; word-break: break-word; } .note-edit-controls { display: flex; gap: .25rem; } .btn-edit-row { background: transparent; border: none; font-size: 1rem; padding: .2rem .3rem; } @@ -392,6 +395,43 @@ fieldset.active .fs-body { display: contents; } #btn-save-tune { background: var(--accent); } #btn-cancel { background: transparent; border: 1px solid var(--border); } +#notes-modal { + max-width: 700px; + height: 70vh; + max-height: 85vh; +} + +#note-editor-modal { + max-width: 700px; + height: 90vh; + max-height: 95vh; +} +#note-editor-modal[open] { + display: flex; + flex-direction: column; +} + +#note-editor-ta { + flex: 1; + min-height: 0; + display: block; + width: 100%; + resize: vertical; + background: var(--bg); + border: 1px solid var(--border); + color: var(--text); + border-radius: var(--radius); + padding: .5rem .65rem; + font-size: .875rem; + font-family: var(--font); + line-height: 1.5; + margin-bottom: .5rem; +} +#note-editor-ta:focus { + outline: 2px solid var(--accent2); + outline-offset: -1px; +} + /* ── Detail modal ─────────────────────────────────────────── */ #detail-content h2 { margin-bottom: .75rem; } #detail-content table { width: 100%; border-collapse: collapse; font-size: .85rem; } @@ -480,17 +520,17 @@ fieldset.active .fs-body { display: contents; } } .mob-actions { display: flex; gap: .2rem; flex-shrink: 0; } - /* Modals: fullscreen */ + /* Modals: fullscreen — !important beats any ID-specific desktop size rules */ dialog { - position: fixed; - inset: 0; - width: 100%; - max-width: 100%; - height: 100%; - max-height: 100%; - border-radius: 0; - border: none; - margin: 0; + position: fixed !important; + inset: 0 !important; + width: 100% !important; + max-width: 100% !important; + height: 100% !important; + max-height: 100% !important; + border-radius: 0 !important; + border: none !important; + margin: 0 !important; } }