[scrobbles] Clean up notes on webpages
All checks were successful
build & deploy / test (push) Successful in 1m44s
build & deploy / deploy (push) Successful in 21s

This commit is contained in:
2026-03-24 15:45:10 -04:00
parent 06acc9ec2b
commit a79cc4c217
4 changed files with 126 additions and 26 deletions

View File

@ -50,20 +50,25 @@ class BaseLogData(JSONDataclass):
def override_fields(cls) -> dict:
return {}
def notes_as_str(self) -> str:
note_str = ""
def notes_as_str(self, separator: str = " | ") -> str:
import re
if not self.notes:
return note_str
return ""
if self.notes:
if isinstance(self.notes, str):
note_str = self.notes.encode("utf-8").decode("unicode_escape")
if isinstance(self.notes, list):
note_str = (
"\n".join(self.notes).encode("utf-8").decode("unicode_escape")
)
return note_str
if isinstance(self.notes, str):
note_str = self.notes.encode("utf-8").decode("unicode_escape")
return re.sub(r"\s*\[start:\d+\|end:\d+\]$", "", note_str)
if isinstance(self.notes, list):
cleaned_notes = []
for note in self.notes:
cleaned = note.encode("utf-8").decode("unicode_escape")
cleaned = re.sub(r"\s*\[start:\d+\|end:\d+\]$", "", cleaned)
cleaned_notes.append(cleaned)
return separator.join(cleaned_notes)
return ""
def notes_as_html(self) -> str:
if not self.notes:

View File

@ -1,3 +1,4 @@
import re
from datetime import datetime
from django import template
@ -15,3 +16,20 @@ def add_class(field, css_class):
@register.simple_tag
def current_date(format):
return datetime.now().strftime(format)
@register.filter
def note_position(note, article_length):
if not article_length or article_length == 0:
return ""
match = re.search(r"start:(\d+)", note)
if match:
start = int(match.group(1))
percentage = (start / article_length) * 100
return f"style='top: {percentage}%'"
return ""
@register.filter
def note_text(note):
return re.sub(r"\s*\[start:\d+\|end:\d+\]$", "", note)

View File

@ -73,6 +73,10 @@ class WebPageDetailView(ScrobbleableDetailView):
context["latest_scrobble_uuid"] = str(latest.uuid)
notes = latest.log.get("notes", []) if latest.log else []
context["latest_scrobble_notes"] = notes
context["latest_scrobble_notes_html"] = latest.logdata.notes_as_html()
context["article_length"] = (
len(self.object.extract) if self.object.extract else 0
)
return context

View File

@ -1,18 +1,77 @@
{% extends "base_list.html" %}
{% load static %}
{% load form_tags %}
{% block title %}{{object.title}}{% endblock %}
{% block head_extra %}
<style>
.webpage-metadata {border: 1px solid #aaa; border-top:none; border-right:none; line-height:0.65em;padding-top:10px;}
.webpage-body {width:40em; text-align:justify; padding-top:20px; font-family:serif; font-size:1.25em; line-height:1.6em; border-right: 1px #ccc solid; padding-right: 2em;}
.webpage-body {text-align:justify; padding-top:20px; font-family:serif; font-size:1.25em; line-height:1.6em; border-right: 1px #ccc solid; padding-right: 2em;}
.webpage-body br { margin-bottom: 1em; }
.webpage-notes {padding-top:20px; min-width: 220px;}
.webpage-notes .sticky-notes-container {
flex-direction: column;
position: relative;
height: 100%;
min-height: 500px;
}
.webpage-notes .sticky-note {
max-width: none;
position: absolute;
}
</style>
<link href="{% static 'css/recogito.min.css' %}" rel="stylesheet">
{% endblock %}
{% block extra_js %}
<script>
(function() {
var article = document.getElementById('article');
var notesContainer = document.querySelector('.webpage-notes .sticky-notes-container');
if (!article || !notesContainer) return;
var notes = [
{% for note in latest_scrobble_notes %}
{ text: "{{ note|escapejs }}", start: null, end: null },
{% endfor %}
];
var positionRe = /\[start:(\d+)\|end:(\d+)\]$/;
notes.forEach(function(noteObj) {
var match = noteObj.text.match(positionRe);
if (match) {
noteObj.start = parseInt(match[1]);
noteObj.end = parseInt(match[2]);
}
});
function positionNotes() {
var articleText = article.innerText || article.textContent;
var articleHeight = article.offsetHeight;
notesContainer.style.height = articleHeight + 'px';
notes.forEach(function(note, index) {
if (note.start === null) return;
var noteEl = notesContainer.children[index];
if (!noteEl) return;
var percentage = (note.start / articleText.length) * 100;
var topPos = (percentage / 100) * articleHeight;
noteEl.style.top = topPos + 'px';
});
}
if (notes.length > 0) {
positionNotes();
window.addEventListener('resize', positionNotes);
}
})();
</script>
<script src="{% static 'js/recogito.min.js' %}"></script>
<script type="text/javascript">
(function() {
@ -116,19 +175,6 @@
{% endblock %}
{% block lists %}
<div class="row webpage">
<div class="webpage-metadata">
<p>Source: <a href="{{object.url}}">{{object.domain}}</a></p>
{% if object.date %}<p>Published: <em>{{object.date}}</em></p>{% endif %}
<p>Time to read: {{object.estimated_time_to_read_in_minutes}} minutes</p>
</div>
{% if object.extract %}
<div class="webpage-body" id="article">
{{object.extract|linebreaks}}
</div>
{% endif %}
<hr/>
</div>
<div class="row">
<div class="col-md">
<h3>Last scrobbles</h3>
@ -144,7 +190,7 @@
{% for scrobble in scrobbles.all %}
<tr>
<td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
<td>{% if scrobble.logdata.notes %}{% for note in scrobble.logdata.notes %}{{note}}{% if not forloop.last %}; {% endif%}{% endfor %}{% endif %}
<td>{% if scrobble.logdata.notes %}{{ scrobble.logdata.notes_as_str|safe }}{% endif %}
</tr>
{% endfor %}
</tbody>
@ -152,4 +198,31 @@
</div>
</div>
</div>
<hr/>
<div class="row webpage">
<div class="webpage-metadata">
<p>Source: <a href="{{object.url}}">{{object.domain}}</a></p>
{% if object.date %}<p>Published: <em>{{object.date}}</em></p>{% endif %}
<p>Time to read: {{object.estimated_time_to_read_in_minutes}} minutes</p>
</div>
{% if object.extract %}
<div class="col">
<div class="webpage-body" id="article">
{{object.extract|linebreaks}}
</div>
</div>
{% endif %}
<div class="col-auto">
<div class="webpage-notes">
{% if latest_scrobble_notes %}
<h5>Notes</h5>
<div class="sticky-notes-container" style="position: relative; height: 100%;">
{% for note in latest_scrobble_notes %}
<div class="sticky-note" {{ note|note_position:article_length }}>{{ note|note_text }}</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}