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 @@