mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-15 11:22: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>
88 lines
3.0 KiB
JavaScript
88 lines
3.0 KiB
JavaScript
/** Pure Notes completion, tags, reminder and serialization rules. */
|
|
import { _hasTimeComponent } from './recurrence.js';
|
|
export function _isCalendarReminderNote(n) {
|
|
return n && n.source === 'calendar' && n.label === 'calendar';
|
|
}
|
|
|
|
export function _hasActiveReminder(n, clock) {
|
|
if (!n || n.archived || _isNoteFullyDone(n)) return false;
|
|
if (!n.due_date) return false;
|
|
const t = new Date(n.due_date).getTime();
|
|
return !isNaN(t) && t <= clock.now();
|
|
}
|
|
|
|
export function _isNoteFullyDone(note) {
|
|
if (_hasItems(note) && Array.isArray(note.items) && note.items.length > 0) {
|
|
return note.items.every(it => it.done);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function _hasItems(note) {
|
|
return note && (note.note_type === 'todo' || note.note_type === 'goal' || note.note_type === 'checklist');
|
|
}
|
|
|
|
export function _goalProgress(note) {
|
|
if (!Array.isArray(note?.items) || note.items.length === 0) return '';
|
|
const done = note.items.filter(it => it.done).length;
|
|
return ` ${done}/${note.items.length}`;
|
|
}
|
|
|
|
export function _nextGoalStep(note) {
|
|
if (!Array.isArray(note?.items)) return null;
|
|
for (let i = 0; i < note.items.length; i++) {
|
|
if (!note.items[i].done) return { idx: i, item: note.items[i] };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function _noteTags(n) {
|
|
const tags = [];
|
|
if (n?.label) tags.push(...n.label.trim().split(/\s+/).filter(Boolean));
|
|
if (n?.due_date && _hasTimeComponent(n.due_date)) tags.push('reminder');
|
|
return [...new Set(tags.map(t => t.replace(/^#+/, '').trim()).filter(Boolean))];
|
|
}
|
|
|
|
export function _visibleNoteTags(n) {
|
|
return _noteTags(n).filter(t => t !== 'reminder');
|
|
}
|
|
|
|
export function _isPastReminder(n, clock) {
|
|
if (!n?.due_date || !_hasTimeComponent(n.due_date)) return false;
|
|
const due = new Date(n.due_date).getTime();
|
|
return !isNaN(due) && due <= clock.now();
|
|
}
|
|
|
|
export function _serializeNoteForCopy(note) {
|
|
const lines = [];
|
|
if (note.title) lines.push(note.title);
|
|
if (note.content) lines.push(note.content);
|
|
if (Array.isArray(note.items) && note.items.length) {
|
|
if (lines.length) lines.push('');
|
|
for (const it of note.items) {
|
|
if (!it || !(it.text || '').trim()) continue;
|
|
lines.push(`- [${it.done ? 'x' : ' '}] ${(it.text || '').trim()}`);
|
|
}
|
|
}
|
|
return lines.join('\n').trim();
|
|
}
|
|
|
|
export function _noteToAgentPrompt(note) {
|
|
const parts = [];
|
|
if ((note.title || '').trim()) parts.push(note.title.trim());
|
|
if ((note.content || '').trim()) parts.push(note.content.trim());
|
|
if (Array.isArray(note.items)) {
|
|
note.items.filter(it => !it.done && (it.text || '').trim())
|
|
.forEach(it => parts.push('- ' + it.text.trim()));
|
|
}
|
|
const body = parts.join('\n');
|
|
return body ? `Help me get this done:\n\n${body}\n\nThe source note is read-only. Do not edit, replace, or update it.` : '';
|
|
}
|
|
|
|
export function _standardVisualVariant(note) {
|
|
const source = String(note?.id || note?.title || note?.created_at || 'standard-note');
|
|
let hash = 0;
|
|
for (let i = 0; i < source.length; i++) hash = ((hash << 5) - hash + source.charCodeAt(i)) | 0;
|
|
return Math.abs(hash) % 5;
|
|
}
|