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 (#6713)
* 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>
This commit is contained in:
co-authored by
Léo
parent
9f8eae72fd
commit
f1a47f3812
@@ -0,0 +1,129 @@
|
||||
/** 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user