diff --git a/static/css/app.css b/static/css/app.css index 6278e2a..cd1bc4e 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -1820,6 +1820,7 @@ body.reader-immersive.reader-show-bottom .reader-overlay { bottom: var(--bar-h) } .reader-margin.open .reader-margin-header { display:flex; } .reader-margin-title { font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:var(--muted,#888); } +.reader-margin-header-actions { display:flex; align-items:center; gap:6px; } .reader-margin-markers { position:relative; flex:1; overflow-y:auto; overflow-x:hidden; cursor:crosshair; } .margin-note { position:absolute; left:8px; right:8px; cursor:pointer; } @@ -1845,6 +1846,16 @@ body.reader-immersive.reader-show-bottom .reader-overlay { bottom: var(--bar-h) border-left-color:#e6c229; background:rgba(241,196,15,.13); } +/* Inline note editor — appears right where the note lives in the margin, so + setting/editing a note never means jumping to a separate panel */ +.margin-note-editor { position:absolute; left:8px; right:8px; z-index:1; } +.margin-note-textarea { + width:100%; min-height:52px; font-size:11px; line-height:1.4; padding:5px 7px; + border-radius:3px; border:1px solid var(--accent,#e63946); + background:var(--bg-card,#1a1a1a); color:var(--fg,#eee); + box-shadow:1px 2px 5px rgba(0,0,0,.4); resize:vertical; +} + @media (max-width: 600px) { .reader-margin.open { width:150px; } .reader-margin-title { display:none; } diff --git a/static/js/app.js b/static/js/app.js index 010754f..959acce 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -5356,6 +5356,7 @@ function toggleAnnotationsMargin() { function _repositionMarginMarkers() { const markersEl = $('reader-margin-markers'); if (!markersEl) return; + if (markersEl.querySelector('.margin-note-editor')) return; // don't blow away an active inline edit markersEl.innerHTML = ''; if (!readerAnnotationsMarginOpen || currentPdfDoc) return; @@ -5401,7 +5402,7 @@ function handleMarginClick(e) { const markerBtn = e.target.closest('.margin-note'); if (markerBtn) { const h = currentHighlights.find(x => x.id === markerBtn.dataset.highlightId); - if (h) showHighlightTooltip(markerBtn, h); + if (h) editNoteInlineInMargin(h); return; } @@ -5410,6 +5411,9 @@ function handleMarginClick(e) { if (!readerAnnotationsMarginOpen || currentPdfDoc) return; const markersEl = $('reader-margin-markers'); if (!markersEl || !markersEl.contains(e.target)) return; + // If an editor is already open, this click just blurs/commits it (see its + // own blur handler) — it shouldn't also create a second, unrelated note. + if (markersEl.querySelector('.margin-note-editor')) return; const contentEl = $('reader-content'); if (!contentEl) return; @@ -5478,7 +5482,59 @@ function createFreeformNote(range) { renderHighlight(h); _repositionMarginMarkers(); debounceSaveHighlights(); - openNoteEditor(h); + editNoteInlineInMargin(h); +} + +// Edit a note's text right where it lives in the margin, instead of jumping +// to the separate right-hand sidebar — the whole point of setting a note in +// the margin is that you shouldn't have to look away from it to write it. +function editNoteInlineInMargin(h) { + const markersEl = $('reader-margin-markers'); + if (!markersEl) return; + markersEl.querySelector('.margin-note-editor')?.remove(); + + const existingCard = markersEl.querySelector(`[data-highlight-id="${h.id}"]`); + const top = existingCard ? (parseFloat(existingCard.style.top) || 0) : 0; + if (existingCard) existingCard.style.display = 'none'; + + const editor = document.createElement('div'); + editor.className = 'margin-note-editor'; + editor.style.top = top + 'px'; + editor.innerHTML = ''; + markersEl.appendChild(editor); + const textarea = editor.querySelector('textarea'); + textarea.value = h.note || ''; + + function commit() { + const text = textarea.value.trim(); + if (h.note !== text) { + h.note = text; + highlightsDirty = true; + debounceSaveHighlights(); + } + editor.remove(); + _repositionMarginMarkers(); + } + + textarea.addEventListener('blur', commit); + textarea.addEventListener('keydown', e => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + e.stopPropagation(); + textarea.blur(); // triggers commit() + } else if (e.key === 'Escape') { + // Stop this from bubbling to the document-level Escape handler, which + // would otherwise close the whole reader (it doesn't know we just want + // to cancel this one edit) — see the global keydown listener. + e.stopPropagation(); + textarea.removeEventListener('blur', commit); // discard, don't save + editor.remove(); + _repositionMarginMarkers(); + } + }); + + textarea.focus(); + textarea.select(); } // --------------------------------------------------------------------------- diff --git a/static/js/sw.js b/static/js/sw.js index 1493216..979594c 100644 --- a/static/js/sw.js +++ b/static/js/sw.js @@ -2,7 +2,7 @@ * diora service worker — caches the app shell for offline use. */ -const CACHE = 'diora-v24'; +const CACHE = 'diora-v25'; const PODCAST_CACHE = 'diora-podcast-v1'; const SHELL = [ '/static/css/app.css', diff --git a/templates/radio/player.html b/templates/radio/player.html index f5f94b0..c7a5c93 100644 --- a/templates/radio/player.html +++ b/templates/radio/player.html @@ -343,7 +343,6 @@ - @@ -353,8 +352,11 @@