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>
112 lines
3.8 KiB
JavaScript
112 lines
3.8 KiB
JavaScript
/** Mobile checklist grip dragging and document listener ownership. */
|
|
export function createChecklistReorder({
|
|
lifecycle
|
|
}) {
|
|
let _clDrag = null;
|
|
|
|
let _clDocBound = false;
|
|
let _clOwner = null;
|
|
|
|
function _wireChecklistTouchReorder(form) {
|
|
const container = form.querySelector('.note-checklist-inputs');
|
|
if (!container) return;
|
|
container.querySelectorAll('.note-cl-grip').forEach(grip => {
|
|
if (grip.dataset.touchBound === '1') return;
|
|
grip.dataset.touchBound = '1';
|
|
grip.addEventListener('touchstart', (e) => {
|
|
if (e.touches.length !== 1) return;
|
|
e.preventDefault();
|
|
const row = grip.closest('.note-cl-row');
|
|
if (!row) return;
|
|
_beginChecklistGrab(row, container, e.touches[0]);
|
|
}, { passive: false });
|
|
});
|
|
if (!_clDocBound || _clOwner !== form) {
|
|
if (_clOwner) lifecycle.release(_clOwner);
|
|
_clOwner = form;
|
|
lifecycle.listen(form, document, 'touchmove', _onClTouchMove, { passive: false });
|
|
lifecycle.listen(form, document, 'touchend', _onClTouchEnd, { passive: true });
|
|
lifecycle.listen(form, document, 'touchcancel', _onClTouchEnd, { passive: true });
|
|
_clDocBound = true;
|
|
}
|
|
}
|
|
|
|
function _beginChecklistGrab(row, container, touch) {
|
|
if (_clDrag) return;
|
|
const rect = row.getBoundingClientRect();
|
|
const placeholder = document.createElement('div');
|
|
placeholder.className = 'note-cl-row-placeholder';
|
|
placeholder.style.height = rect.height + 'px';
|
|
container.insertBefore(placeholder, row);
|
|
|
|
row.classList.add('note-cl-row-dragging');
|
|
row.style.position = 'fixed';
|
|
row.style.left = rect.left + 'px';
|
|
row.style.top = rect.top + 'px';
|
|
row.style.width = rect.width + 'px';
|
|
row.style.zIndex = '10002';
|
|
row.style.pointerEvents = 'none';
|
|
|
|
_clDrag = {
|
|
row, placeholder, container,
|
|
grabOffsetX: touch.clientX - rect.left,
|
|
grabOffsetY: touch.clientY - rect.top,
|
|
};
|
|
try { navigator.vibrate?.(8); } catch {}
|
|
}
|
|
|
|
function _onClTouchMove(e) {
|
|
if (!_clDrag) return;
|
|
if (e.touches.length !== 1) return;
|
|
e.preventDefault();
|
|
const t = e.touches[0];
|
|
const { row, placeholder, container } = _clDrag;
|
|
row.style.left = (t.clientX - _clDrag.grabOffsetX) + 'px';
|
|
row.style.top = (t.clientY - _clDrag.grabOffsetY) + 'px';
|
|
|
|
const under = document.elementFromPoint(t.clientX, t.clientY);
|
|
const target = under && under.closest
|
|
? under.closest('.note-cl-row:not(.note-cl-row-dragging)')
|
|
: null;
|
|
if (!target || target === row) return;
|
|
if (target.parentNode !== container) return;
|
|
|
|
const tRect = target.getBoundingClientRect();
|
|
const targetMidY = tRect.top + tRect.height / 2;
|
|
if (t.clientY < targetMidY) {
|
|
if (placeholder.nextElementSibling !== target) {
|
|
container.insertBefore(placeholder, target);
|
|
}
|
|
} else {
|
|
if (target.nextElementSibling !== placeholder) {
|
|
container.insertBefore(placeholder, target.nextElementSibling);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _onClTouchEnd() {
|
|
if (!_clDrag) return;
|
|
const { row, placeholder } = _clDrag;
|
|
_clDrag = null;
|
|
const phRect = placeholder.getBoundingClientRect();
|
|
row.style.transition = 'left 0.18s ease, top 0.18s ease';
|
|
row.style.left = phRect.left + 'px';
|
|
row.style.top = phRect.top + 'px';
|
|
setTimeout(() => {
|
|
placeholder.parentNode.insertBefore(row, placeholder);
|
|
placeholder.remove();
|
|
row.classList.remove('note-cl-row-dragging');
|
|
row.style.cssText = '';
|
|
// Order is persisted as part of the form's normal save (rows are
|
|
// re-serialized in DOM order via _collectItems).
|
|
}, 200);
|
|
}
|
|
|
|
function dispose() {
|
|
if (_clDrag) _onClTouchEnd();
|
|
_clDocBound = false;
|
|
_clOwner = null;
|
|
}
|
|
return { dispose, _wireChecklistTouchReorder, _beginChecklistGrab, _onClTouchMove, _onClTouchEnd };
|
|
}
|