diff --git a/app/templates/reader.html b/app/templates/reader.html index ef16a08..aec120c 100644 --- a/app/templates/reader.html +++ b/app/templates/reader.html @@ -3,31 +3,37 @@ {% block title %}{{ title }} | BookWorm Reader{% endblock %} {% block content %} -
+
-
+
{{ content|safe }}
- - +
+ + +
{% endblock %} {% block footer %} @@ -132,6 +138,32 @@

{ - annotations.push({ - text: annotation.dataset.text, - note: annotation.querySelector('.annotation-note').value - }); - }); - return annotations; - } let currentFontSize = parseInt(window.getComputedStyle(readerContent).fontSize); let currentLineHeight = 1.6; @@ -668,7 +690,7 @@

${match}`); - readerContent.innerHTML = annotatedContent; - const annotationElement = document.createElement('div'); - annotationElement.className = 'annotation-box'; - annotationElement.innerHTML = ` - - - - - `; - document.body.appendChild(annotationElement); - annotationElement.querySelector('.delete-annotation').addEventListener('click', function () { - annotationElement.remove(); - removeAnnotation(text); - savePreferences(); - }); - annotationElement.querySelector('.edit-annotation').addEventListener('click', function () { - annotationElement.querySelector('.annotation-note').disabled = false; - }); - annotationElement.querySelector('.save-annotation').addEventListener('click', function () { - annotationElement.querySelector('.annotation-note').disabled = true; - savePreferences(); - }); - savePreferences(); +// CREATE ANNOTATION +// Optional third parameter "existingId": if provided, we use it rather than generating a new one. +function addAnnotation(text, note = '', existingId) { + console.log('Adding annotation for text:', text); + + // 1) Generate or re-use a unique ID for this annotation + const annotationId = existingId || ('annotation-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5)); + + // 2) Highlight the text in the article. + // We only replace the first occurrence not already wrapped. + const originalHTML = readerContent.innerHTML; + const safeText = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // escape regex chars + let replaced = false; + const annotatedHTML = originalHTML.replace(new RegExp(safeText, 'gi'), function(match) { + if (!replaced) { + replaced = true; + return `${match}`; } - - function removeAnnotation(text) { - const content = readerContent.innerHTML; - const unannotatedContent = content.replace( - /(.*?)<\/span>/gi, (match, p1, - p2) => { - return p1.includes(text) ? p2 : match; - }); - readerContent.innerHTML = unannotatedContent; + return match; + }); + readerContent.innerHTML = annotatedHTML; + + // 3) Ensure the sidebar is visible and relatively positioned. + const annotationPanel = document.getElementById('annotation-panel'); + annotationPanel.classList.remove('hidden'); + annotationPanel.style.position = 'relative'; + + // 4) Create the annotation box in the sidebar. + const annotationElement = document.createElement('div'); + annotationElement.className = 'annotation-box p-2 bg-white dark:bg-[#333] shadow rounded'; + annotationElement.style.position = 'absolute'; + // Store unique ID and text for later reference. + annotationElement.dataset.id = annotationId; + annotationElement.dataset.text = text; + + annotationElement.innerHTML = ` +
+ Annotated text: +
+
${text}
+ +
+ + + +
+ `; + annotationPanel.appendChild(annotationElement); + + // 5) Position the annotation box in the sidebar + // Use the highlight span (found by its unique ID) to get the vertical offset. + const highlightSpan = document.querySelector(`span.annotation[data-id="${annotationId}"]`); + let offsetTop = 0; + if (highlightSpan) { + const highlightRect = highlightSpan.getBoundingClientRect(); + const panelRect = annotationPanel.getBoundingClientRect(); + offsetTop = (highlightRect.top + window.scrollY) - (panelRect.top + window.scrollY); + if (offsetTop < 0) offsetTop = 0; + } + annotationElement.style.top = offsetTop + 'px'; + annotationElement.style.right = '0'; + + // 6) Initialize the note textarea as read-only by default. + const noteTextarea = annotationElement.querySelector('.annotation-note'); + noteTextarea.disabled = true; + + // 7) Wire up annotation actions. + annotationElement.querySelector('.delete-annotation').addEventListener('click', function () { + annotationElement.remove(); + removeAnnotation(annotationId); + savePreferences(); + // If no annotations remain, hide the sidebar. + if (annotationPanel.childElementCount === 0) { + annotationPanel.classList.add('hidden'); } + }); + annotationElement.querySelector('.edit-annotation').addEventListener('click', function () { + noteTextarea.disabled = false; + noteTextarea.focus(); + }); + annotationElement.querySelector('.save-annotation').addEventListener('click', function () { + noteTextarea.disabled = true; + savePreferences(); + }); + + // 8) Scroll the annotation panel so the new annotation is in view. + annotationElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + + // 9) Persist changes (e.g., to localStorage). + savePreferences(); +} + +// REMOVE ANNOTATION (by unique ID) +function removeAnnotation(annotationId) { + const content = readerContent.innerHTML; + const unannotatedContent = content.replace( + /(.*?)<\/span>/gi, + (match, p1, p2, p3) => p1 === annotationId ? p3 : match + ); + readerContent.innerHTML = unannotatedContent; +} + +// GET ALL ANNOTATIONS from the sidebar. +function getAnnotations() { + const annotationBoxes = document.querySelectorAll('#annotation-panel .annotation-box'); + const annotations = []; + annotationBoxes.forEach(box => { + const text = box.dataset.text; + const note = box.querySelector('.annotation-note').value; + const id = box.dataset.id; + annotations.push({ id, text, note }); + }); + return annotations; +} + + }); const colorBlindDropdown = document.getElementById('color-blind');