Files
odysseus/static/js/features/notes/application/checklistOperations.js
T
Alexandre TeixeiraandLéo f1a47f3812 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>
2026-10-15 09:29:41 +01:00

69 lines
3.0 KiB
JavaScript

/** Checklist item workflows. Supplied feedback callbacks update the invoking view. */
export function createChecklistOperations({
app,
api,
effects,
uid
}) {
const state = app.state;
function itemNote(id, index) {
const note = state.notes.find(n => n.id === id);
return note && Array.isArray(note.items) && note.items[index] ? note : null;
}
function removeItem(id, index) {
const note = itemNote(id, index);
if (!note) return;
const removed = note.items[index];
app.mergeNote(note, { items: note.items.filter((_, i) => i !== index) });
effects.render();
api.patchNote(id, { items: note.items }).catch(() => {
app.spliceItems(note, index, 0, removed); effects.render(); effects.showError('Failed to remove item');
});
}
function editItem(note, index, text) {
const oldText = (note.items[index].text || '').trim();
if (text === oldText) { effects.render(); return; }
const oldItem = note.items[index];
if (!text) app.spliceItems(note, index, 1);
else app.updateItem(note, index, { text });
api.patchNote(note.id, { items: note.items }).catch(() => {
if (!text) app.spliceItems(note, index, 0, oldItem);
else app.updateItem(note, index, { text: oldText });
effects.render(); effects.showError('Failed to update item');
});
effects.render();
}
function addItem(id, text, added) {
const note = state.notes.find(n => n.id === id);
if (!note) return;
const items = Array.isArray(note.items) ? [...note.items] : [];
items.push({ id: uid(), text, done: false });
app.mergeNote(note, { items });
added(); effects.render();
return () => api.patchNote(id, { items }).catch(() => {
app.mergeNote(note, { items: items.slice(0, -1) }); effects.render(); effects.showError('Failed to add item');
});
}
function toggleItem(id, index, changed, completed) {
const note = itemNote(id, index);
if (!note) return;
const wasDone = note.items.length > 0 && note.items.every(it => it.done);
app.updateItem(note, index, { done: !note.items[index].done });
changed(note.items[index].done);
if (!wasDone && note.items.length > 0 && note.items.every(it => it.done)) completed();
api.patchNote(id, { items: note.items }).catch(() => {
app.updateItem(note, index, { done: !note.items[index].done }); changed(note.items[index].done);
});
}
async function toggleTodayStep(id, index, changed, completed) {
const note = itemNote(id, index);
if (!note) return;
app.updateItem(note, index, { done: !note.items[index].done }); changed();
try {
await api.patchNote(id, { items: note.items }); effects.render();
if (note.items.every(it => it.done)) completed();
} catch { app.updateItem(note, index, { done: !note.items[index].done }); }
}
return { removeItem, editItem, addItem, toggleItem, toggleTodayStep };
}