mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-01 12:32:19 +02:00
Squash Odysseus development history
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
// Shared horizontal scrolling for filter/tag chip rows.
|
||||
|
||||
const CHIP_STRIP_SELECTOR = [
|
||||
'.skills-summary-strip',
|
||||
'#memory-category-filters',
|
||||
'.memory-category-filters:has(> .memory-cat-chip)',
|
||||
'.doclib-lang-chips',
|
||||
'.doclib-chips',
|
||||
'.notes-labels-bar',
|
||||
'.tasks-activity-filters',
|
||||
'.gallery-tag-chips',
|
||||
'.gallery-album-chips',
|
||||
'.gallery-ai-tags',
|
||||
'.cal-filters',
|
||||
].join(',');
|
||||
|
||||
function initChipStrip(strip) {
|
||||
if (!strip || strip.dataset.chipScrollBound === '1') return;
|
||||
const parent = strip.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
strip.dataset.chipScrollBound = '1';
|
||||
strip.classList.add('chip-scroll-strip');
|
||||
|
||||
const frame = document.createElement('div');
|
||||
frame.className = 'doclib-chip-scroll-frame';
|
||||
parent.insertBefore(frame, strip);
|
||||
frame.appendChild(strip);
|
||||
|
||||
const makeArrow = (direction, label) => {
|
||||
const button = document.createElement('button');
|
||||
const glyph = document.createElement('span');
|
||||
button.type = 'button';
|
||||
button.className = `doclib-chip-scroll-arrow ${direction}`;
|
||||
button.setAttribute('aria-label', label);
|
||||
button.title = label;
|
||||
glyph.className = 'doclib-chip-scroll-arrow-glyph';
|
||||
glyph.textContent = direction === 'left' ? '\u2039' : '\u203a';
|
||||
button.appendChild(glyph);
|
||||
button.addEventListener('click', () => {
|
||||
strip.scrollBy({ left: direction === 'left' ? -180 : 180, behavior: 'smooth' });
|
||||
});
|
||||
frame.appendChild(button);
|
||||
return button;
|
||||
};
|
||||
|
||||
const left = makeArrow('left', 'Show previous tags');
|
||||
const right = makeArrow('right', 'Show more tags');
|
||||
let pointerId = null;
|
||||
let startX = 0;
|
||||
let startScroll = 0;
|
||||
let dragging = false;
|
||||
let suppressClick = false;
|
||||
|
||||
const sync = () => {
|
||||
const stripHidden = strip.hidden || getComputedStyle(strip).display === 'none';
|
||||
frame.style.display = stripHidden ? 'none' : '';
|
||||
if (stripHidden) {
|
||||
frame.classList.remove('has-overflow');
|
||||
left.hidden = true;
|
||||
right.hidden = true;
|
||||
return;
|
||||
}
|
||||
const max = Math.max(0, strip.scrollWidth - strip.clientWidth);
|
||||
frame.classList.toggle('has-overflow', max > 1);
|
||||
left.hidden = max <= 1 || strip.scrollLeft <= 1;
|
||||
right.hidden = max <= 1 || strip.scrollLeft >= max - 1;
|
||||
};
|
||||
|
||||
strip.addEventListener('scroll', sync, { passive: true });
|
||||
strip.addEventListener('pointerdown', (event) => {
|
||||
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
||||
if (event.target.closest?.('input, textarea, select, [contenteditable="true"]')) return;
|
||||
pointerId = event.pointerId;
|
||||
startX = event.clientX;
|
||||
startScroll = strip.scrollLeft;
|
||||
dragging = false;
|
||||
suppressClick = false;
|
||||
});
|
||||
strip.addEventListener('pointermove', (event) => {
|
||||
if (pointerId !== event.pointerId) return;
|
||||
const delta = event.clientX - startX;
|
||||
if (!dragging && Math.abs(delta) <= 4) return;
|
||||
if (!dragging) {
|
||||
dragging = true;
|
||||
suppressClick = true;
|
||||
strip.classList.add('is-pointer-dragging');
|
||||
strip.setPointerCapture?.(event.pointerId);
|
||||
}
|
||||
if (event.cancelable) event.preventDefault();
|
||||
strip.scrollLeft = startScroll - delta;
|
||||
});
|
||||
const stopDragging = (event) => {
|
||||
if (pointerId !== event.pointerId) return;
|
||||
if (dragging && strip.hasPointerCapture?.(event.pointerId)) {
|
||||
strip.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
pointerId = null;
|
||||
dragging = false;
|
||||
strip.classList.remove('is-pointer-dragging');
|
||||
sync();
|
||||
};
|
||||
strip.addEventListener('pointerup', stopDragging);
|
||||
strip.addEventListener('pointercancel', stopDragging);
|
||||
strip.addEventListener('click', (event) => {
|
||||
if (!suppressClick) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
suppressClick = false;
|
||||
}, true);
|
||||
|
||||
if (typeof ResizeObserver === 'function') new ResizeObserver(sync).observe(strip);
|
||||
new MutationObserver(sync).observe(strip, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['class', 'hidden', 'style'],
|
||||
});
|
||||
requestAnimationFrame(sync);
|
||||
}
|
||||
|
||||
function scanChipStrips(root = document) {
|
||||
if (root.nodeType === Node.ELEMENT_NODE && root.matches?.(CHIP_STRIP_SELECTOR)) initChipStrip(root);
|
||||
root.querySelectorAll?.(CHIP_STRIP_SELECTOR).forEach(initChipStrip);
|
||||
}
|
||||
|
||||
export function initChipScrollRows() {
|
||||
if (window._chipScrollRowsBound) return;
|
||||
window._chipScrollRowsBound = true;
|
||||
|
||||
['touchstart', 'touchmove'].forEach(type => {
|
||||
document.addEventListener(type, event => {
|
||||
if (event.target.closest?.('.chip-scroll-strip')) event.stopPropagation();
|
||||
}, true);
|
||||
});
|
||||
|
||||
const start = () => {
|
||||
scanChipStrips();
|
||||
new MutationObserver(records => {
|
||||
for (const record of records) {
|
||||
record.addedNodes.forEach(node => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) scanChipStrips(node);
|
||||
});
|
||||
}
|
||||
}).observe(document.body, { childList: true, subtree: true });
|
||||
};
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true });
|
||||
else start();
|
||||
}
|
||||
|
||||
initChipScrollRows();
|
||||
Reference in New Issue
Block a user