Fix note entry dialog

This commit is contained in:
Ian Keane 2026-06-11 16:29:30 -04:00
parent 13042bcfa3
commit 55c8180693
3 changed files with 117 additions and 49 deletions

88
app.js
View file

@ -21,6 +21,7 @@ let editingOriginalRefIds = []; // ref IDs present when edit modal opened
// notes modal state // notes modal state
let notesTuneId = null; let notesTuneId = null;
let notesEntryId = null; let notesEntryId = null;
let noteEditorState = null; // { type: 'tune'|'inst', noteId: null|number }
let unlocked = false; let unlocked = false;
@ -840,35 +841,62 @@ function renderInstNotes(notes) {
}); });
} }
$('btn-add-tune-note').addEventListener('click', async () => { $('btn-add-tune-note').addEventListener('click', () => showNoteEditor('tune', null));
const text = prompt('Note:'); $('btn-add-inst-note').addEventListener('click', () => showNoteEditor('inst', null));
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-inst-note').addEventListener('click', async () => { function editTuneNote(n) { showNoteEditor('tune', n.id, n.note); }
const text = prompt('Note:'); function editInstNote(n) { showNoteEditor('inst', n.id, n.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); }
});
async function editTuneNote(n) { function showNoteEditor(type, noteId, existingText = '') {
const text = prompt('Edit note:', n.note); noteEditorState = { type, noteId };
if (text === null) return; $('note-editor-title').textContent = noteId ? 'Edit note' : 'Add note';
try { $('note-editor-ta').value = existingText;
await apiFetch(`/tunes/${notesTuneId}/notes/${n.id}`, $('note-editor-modal').showModal();
{ method: 'PATCH', body: JSON.stringify({ note: text }) }); $('note-editor-ta').focus();
await reloadNotesModal();
} catch (e) { setStatus(e.message, true); }
} }
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) { async function deleteTuneNote(noteId) {
if (!confirm('Delete this note?')) return; if (!confirm('Delete this note?')) return;
try { try {
@ -877,16 +905,6 @@ async function deleteTuneNote(noteId) {
} catch (e) { setStatus(e.message, true); } } 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) { async function deleteInstNote(noteId) {
if (!confirm('Delete this note?')) return; if (!confirm('Delete this note?')) return;
try { try {

View file

@ -171,7 +171,7 @@
</div> </div>
</dialog> </dialog>
<!-- ── Notes modal ──────────────────────────────────────────── --> <!-- ── Notes modal ───────────────────────────────── -->
<dialog id="notes-modal"> <dialog id="notes-modal">
<h2 id="notes-modal-title">Notes</h2> <h2 id="notes-modal-title">Notes</h2>
<div id="tune-notes-section"> <div id="tune-notes-section">
@ -189,6 +189,16 @@
</div> </div>
</dialog> </dialog>
<!-- ── Note editor modal (fullscreen, separate from notes list) ─ -->
<dialog id="note-editor-modal">
<h2 id="note-editor-title">Add note</h2>
<textarea id="note-editor-ta" placeholder="Write your note… (Ctrl+↵ to save)"></textarea>
<div class="modal-actions">
<button id="btn-note-save">Save note</button>
<button id="btn-note-cancel">Cancel</button>
</div>
</dialog>
<!-- ── Call modal ─────────────────────────────────────────── --> <!-- ── Call modal ─────────────────────────────────────────── -->
<dialog id="call-modal"> <dialog id="call-modal">
<div id="call-header"> <div id="call-header">

View file

@ -255,6 +255,9 @@ th.sort-desc .sort-indicator::after { content: '▼'; }
/* ── Modal ────────────────────────────────────────────────── */ /* ── Modal ────────────────────────────────────────────────── */
dialog { dialog {
position: fixed;
inset: 0;
margin: auto;
background: var(--surface); background: var(--surface);
color: var(--text); color: var(--text);
border: 1px solid var(--border); border: 1px solid var(--border);
@ -356,7 +359,7 @@ fieldset.active .fs-body { display: contents; }
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
} }
.note-row:last-child { border-bottom: none; } .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; } .note-edit-controls { display: flex; gap: .25rem; }
.btn-edit-row { background: transparent; border: none; font-size: 1rem; padding: .2rem .3rem; } .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-save-tune { background: var(--accent); }
#btn-cancel { background: transparent; border: 1px solid var(--border); } #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 modal ─────────────────────────────────────────── */
#detail-content h2 { margin-bottom: .75rem; } #detail-content h2 { margin-bottom: .75rem; }
#detail-content table { width: 100%; border-collapse: collapse; font-size: .85rem; } #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; } .mob-actions { display: flex; gap: .2rem; flex-shrink: 0; }
/* Modals: fullscreen */ /* Modals: fullscreen — !important beats any ID-specific desktop size rules */
dialog { dialog {
position: fixed; position: fixed !important;
inset: 0; inset: 0 !important;
width: 100%; width: 100% !important;
max-width: 100%; max-width: 100% !important;
height: 100%; height: 100% !important;
max-height: 100%; max-height: 100% !important;
border-radius: 0; border-radius: 0 !important;
border: none; border: none !important;
margin: 0; margin: 0 !important;
} }
} }