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,78 @@
|
||||
/** Panel search, archive and list/grid controls. */
|
||||
export function createToolbar({
|
||||
app,
|
||||
state,
|
||||
persistence,
|
||||
_renderNotes,
|
||||
_fetchNotes,
|
||||
_applyMasonry
|
||||
}) {
|
||||
function wireToolbar(pane) {
|
||||
// Search
|
||||
const searchEl = document.getElementById('notes-search');
|
||||
if (searchEl) {
|
||||
searchEl.addEventListener('input', () => {
|
||||
app.setSearchQuery(searchEl.value.trim().toLowerCase());
|
||||
_renderNotes();
|
||||
});
|
||||
}
|
||||
|
||||
// View toggle
|
||||
const archiveBtn = document.getElementById('notes-archive-toggle');
|
||||
if (archiveBtn) {
|
||||
const ARCHIVE_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="5" rx="1"/><path d="M4 8v11a2 2 0 002 2h12a2 2 0 002-2V8"/><path d="M10 12h4"/></svg><span class="notes-header-btn-label">Archive</span>';
|
||||
const CLOSE_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg><span class="notes-header-btn-label">Archive</span>';
|
||||
const syncArchiveBtn = () => {
|
||||
archiveBtn.classList.toggle('active', state.showingArchived);
|
||||
archiveBtn.title = state.showingArchived ? 'Exit archive' : 'View archive';
|
||||
archiveBtn.style.opacity = state.showingArchived ? '1' : '0.8';
|
||||
// Swap to an X while in archive view so it doubles as a close-back-
|
||||
// to-active-notes toggle.
|
||||
archiveBtn.innerHTML = state.showingArchived ? CLOSE_ICON : ARCHIVE_ICON;
|
||||
// Tint the whole pane so it's obvious you're not in the active list.
|
||||
pane.classList.toggle('notes-pane-archive', state.showingArchived);
|
||||
};
|
||||
syncArchiveBtn();
|
||||
archiveBtn.addEventListener('click', async () => {
|
||||
app.setShowingArchived(!state.showingArchived);
|
||||
app.clearSelection();
|
||||
syncArchiveBtn();
|
||||
// Brief fade so the body content swap doesn't snap : the bg-tint
|
||||
// change is already eased by CSS transitions on .notes-pane*.
|
||||
const _bodyEl = document.querySelector('#notes-pane .notes-pane-body');
|
||||
if (_bodyEl) {
|
||||
_bodyEl.style.transition = 'opacity 0.18s ease';
|
||||
_bodyEl.style.opacity = '0.25';
|
||||
}
|
||||
await _fetchNotes();
|
||||
_renderNotes();
|
||||
if (_bodyEl) {
|
||||
requestAnimationFrame(() => {
|
||||
_bodyEl.style.opacity = '';
|
||||
_bodyEl.addEventListener('transitionend', () => { _bodyEl.style.transition = ''; }, { once: true });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
const viewBtn = document.getElementById('notes-view-toggle');
|
||||
if (viewBtn) {
|
||||
pane.classList.toggle('notes-view-grid', state.viewMode === 'grid');
|
||||
// Label shows what you'll switch TO : "Grid" while in list, "List" while in grid.
|
||||
const _setViewLabel = () => {
|
||||
const lbl = viewBtn.querySelector('.notes-header-btn-label');
|
||||
if (lbl) lbl.textContent = state.viewMode === 'grid' ? 'List' : 'Grid';
|
||||
};
|
||||
_setViewLabel();
|
||||
requestAnimationFrame(() => _applyMasonry(document.querySelector('#notes-pane .notes-pane-body')));
|
||||
viewBtn.addEventListener('click', () => {
|
||||
app.setViewMode(state.viewMode === 'grid' ? 'list' : 'grid');
|
||||
try { persistence.setItem('odysseus-notes-view', state.viewMode); } catch {}
|
||||
pane.classList.toggle('notes-view-grid', state.viewMode === 'grid');
|
||||
_setViewLabel();
|
||||
requestAnimationFrame(() => _applyMasonry(document.querySelector('#notes-pane .notes-pane-body')));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
return { wireToolbar };
|
||||
}
|
||||
Reference in New Issue
Block a user