mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-15 19:32:21 +02:00
* refactor(notes): decompose Notes into feature-owned modules * fix(notes): avoid HTML interpolation in reminder picker * docs(notes): drop checkpoint archives, session logs and publication drafts The N0-N6 tarballs and raw pytest/node logs are not read by any script or test, and the logs carry absolute home-directory paths. Once squashed into dev they would stay in history for good. notes-pr.md and notes-issue.md were the drafts used to open #6712 and #6713, not documentation. notes-validation.md keeps the counts, the checkpoint hashes and implementation.sha256 as the record; local interpreter paths and the local Docker image name are replaced with portable placeholders. * fix(notes): keep the state store raw and stop masonry re-observing after close - state: writes built from views (filter, spread, splice) stored nested read-only views in the store, so structuredClone on a stored note threw DataCloneError. Store writes now unwrap nested views. - layout: a save that resolves after closePanel re-renders the leaving pane and re-created the masonry ResizeObserver that teardown had disposed. Skip creating it once Notes is closed. - index.js: drop 30 imported or declared names that were never used and group the imports at the top. Import order is unchanged. - Revert unrelated punctuation edits in .gitattributes and keep the header comment and docstring first in compare/vote.js and test_notes_dom_xss_helpers.py. Both fixes have a node test that fails on the previous head. * test(notes): make script rejection assertion case-insensitive Address CodeQL js/bad-tag-filter alert #1150 in test-only assertion and refresh Notes evidence manifest checksum. --------- Co-authored-by: Léo <leograndcontact@gmail.com>
130 lines
4.7 KiB
JavaScript
130 lines
4.7 KiB
JavaScript
/** Reminder badges, sticky card highlights and entry scrolling. */
|
|
import { _isNoteFullyDone } from '../domain/noteRules.js';
|
|
import { _hasTimeComponent } from '../domain/recurrence.js';
|
|
export function createReminderDisplay({
|
|
_countFiredReminders,
|
|
_hasActiveReminder,
|
|
_loadActiveHighlights,
|
|
_loadGlowedReminders,
|
|
_loadPendingHighlights,
|
|
_saveActiveHighlights,
|
|
_saveGlowedReminders,
|
|
_savePendingHighlights,
|
|
_smoothScrollToNoteCard,
|
|
state
|
|
}) {
|
|
function _clearViewedReminderGlows() {
|
|
const active = _loadActiveHighlights();
|
|
if (!active.size) return;
|
|
_saveActiveHighlights(new Set());
|
|
document.querySelectorAll('.note-card-reminder-fired-sticky').forEach(card => {
|
|
card.classList.remove('note-card-reminder-fired-sticky');
|
|
});
|
|
}
|
|
|
|
function _setReminderCardGlow(noteId, on = true) {
|
|
if (!noteId) return;
|
|
const active = _loadActiveHighlights();
|
|
if (on) active.add(noteId);
|
|
else active.delete(noteId);
|
|
_saveActiveHighlights(active);
|
|
document.querySelectorAll(`.note-card[data-note-id="${noteId}"]`).forEach(card => {
|
|
card.classList.toggle('note-card-reminder-fired-sticky', on);
|
|
});
|
|
}
|
|
|
|
function _queuePendingHighlight(noteId) {
|
|
const set = _loadPendingHighlights();
|
|
set.add(noteId);
|
|
_savePendingHighlights(set);
|
|
}
|
|
|
|
function _flushPendingHighlights() {
|
|
// Fresh firings (queued by the background loop while the panel was closed)
|
|
// glow unconditionally : a notification just told the user something
|
|
// happened, so we always point at the note even if it was glowed before.
|
|
const queued = _loadPendingHighlights();
|
|
const glowed = _loadGlowedReminders();
|
|
const toGlow = new Set(queued);
|
|
// For notes that are merely overdue at open time (no fresh firing event),
|
|
// only glow the ones we haven't already shown : otherwise reopening the
|
|
// panel keeps lighting up old reminders forever.
|
|
for (const n of state.notes) {
|
|
if (!_hasActiveReminder(n) || !_hasTimeComponent(n.due_date)) continue;
|
|
if (queued.has(n.id) || !glowed.has(n.id)) toGlow.add(n.id);
|
|
}
|
|
// Always consume the queue.
|
|
_savePendingHighlights(new Set());
|
|
if (!toGlow.size) return;
|
|
let firstCard = null;
|
|
for (const id of toGlow) {
|
|
const card = document.querySelector(`.note-card[data-note-id="${id}"]`);
|
|
if (!card) continue;
|
|
_setReminderCardGlow(id, true);
|
|
if (!firstCard) firstCard = card;
|
|
glowed.add(id);
|
|
}
|
|
_saveGlowedReminders(glowed);
|
|
// Bring the first one into view so it can't get buried below the fold.
|
|
if (firstCard) {
|
|
requestAnimationFrame(() => {
|
|
_smoothScrollToNoteCard(firstCard);
|
|
});
|
|
}
|
|
}
|
|
|
|
function _updateRailBadge() {
|
|
const fired = _countFiredReminders();
|
|
// Rail (mini sidebar) : only show the count when reminders have ACTUALLY
|
|
// fired since the last dismissal (i.e. you haven't opened notes yet).
|
|
// Showing every overdue note forever made the badge feel permanent.
|
|
const railBtn = document.getElementById('rail-notes');
|
|
if (railBtn) {
|
|
let badge = railBtn.querySelector('.rail-notes-badge');
|
|
if (fired > 0) {
|
|
if (!badge) {
|
|
badge = document.createElement('span');
|
|
badge.className = 'rail-notes-badge';
|
|
railBtn.appendChild(badge);
|
|
}
|
|
badge.textContent = fired > 99 ? '99+' : String(fired);
|
|
badge.classList.add('fired');
|
|
} else if (badge) {
|
|
badge.remove();
|
|
}
|
|
}
|
|
// Main sidebar button
|
|
const sidebarBtn = document.getElementById('tool-notes-btn');
|
|
if (sidebarBtn) {
|
|
let dot = sidebarBtn.querySelector('.tool-notes-dot');
|
|
if (fired > 0) {
|
|
if (!dot) {
|
|
dot = document.createElement('span');
|
|
dot.className = 'tool-notes-dot';
|
|
const newNoteBtn = sidebarBtn.querySelector('#notes-new-note-btn');
|
|
sidebarBtn.insertBefore(dot, newNoteBtn || null);
|
|
}
|
|
} else if (dot) {
|
|
dot.remove();
|
|
}
|
|
}
|
|
// Individual note cards : pulse ones with fired reminders
|
|
document.querySelectorAll('.note-card').forEach(card => {
|
|
const id = card.dataset.noteId;
|
|
const note = state.notes.find(n => n.id === id);
|
|
if (!note || note.archived || _isNoteFullyDone(note)) {
|
|
card.classList.remove('note-card-reminder-due');
|
|
return;
|
|
}
|
|
if (note.due_date && _hasTimeComponent(note.due_date)) {
|
|
const t = new Date(note.due_date).getTime();
|
|
card.classList.toggle('note-card-reminder-due', !isNaN(t) && t <= Date.now());
|
|
} else {
|
|
card.classList.remove('note-card-reminder-due');
|
|
}
|
|
});
|
|
}
|
|
|
|
return { _clearViewedReminderGlows, _setReminderCardGlow, _queuePendingHighlight, _flushPendingHighlights, _updateRailBadge };
|
|
}
|