tags and title fix
This commit is contained in:
@@ -910,12 +910,28 @@ body.theme-light .setting-item select option {
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-family: var(--font-display);
|
||||
letter-spacing: -0.2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-title-text {
|
||||
display: inline-block;
|
||||
padding-right: 24px;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.video-card.is-title-active .video-title-text {
|
||||
animation: video-title-marquee 10s linear infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@keyframes video-title-marquee {
|
||||
to {
|
||||
transform: translateX(calc(-1 * var(--marquee-distance, 0px)));
|
||||
}
|
||||
}
|
||||
|
||||
.video-card p {
|
||||
@@ -929,6 +945,36 @@ body.theme-light .setting-item select option {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.video-tags {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 6px;
|
||||
padding: 0 12px 8px 12px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: thin;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.video-tag {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 3px 8px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0.2px;
|
||||
white-space: nowrap;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.video-tag:focus-visible {
|
||||
outline: 2px solid var(--text-primary);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.uploader-link {
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
@@ -10,6 +10,40 @@ App.videos = App.videos || {};
|
||||
threshold: 1.0
|
||||
});
|
||||
|
||||
const titleEnv = {
|
||||
useHoverFocus: window.matchMedia('(hover: hover) and (pointer: fine)').matches
|
||||
};
|
||||
|
||||
const titleVisibility = new Map();
|
||||
let titleObserver = null;
|
||||
if (!titleEnv.useHoverFocus) {
|
||||
titleObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
titleVisibility.set(entry.target, entry.intersectionRatio || 0);
|
||||
} else {
|
||||
titleVisibility.delete(entry.target);
|
||||
entry.target.dataset.titlePrimary = '0';
|
||||
updateTitleActive(entry.target);
|
||||
}
|
||||
});
|
||||
let topCard = null;
|
||||
let topRatio = 0;
|
||||
titleVisibility.forEach((ratio, card) => {
|
||||
if (ratio > topRatio) {
|
||||
topRatio = ratio;
|
||||
topCard = card;
|
||||
}
|
||||
});
|
||||
titleVisibility.forEach((ratio, card) => {
|
||||
card.dataset.titlePrimary = card === topCard && ratio >= 0.55 ? '1' : '0';
|
||||
updateTitleActive(card);
|
||||
});
|
||||
}, {
|
||||
threshold: [0, 0.25, 0.55, 0.8, 1.0]
|
||||
});
|
||||
}
|
||||
|
||||
App.videos.observeSentinel = function() {
|
||||
const sentinel = document.getElementById('sentinel');
|
||||
if (sentinel) {
|
||||
@@ -17,6 +51,47 @@ App.videos = App.videos || {};
|
||||
}
|
||||
};
|
||||
|
||||
const updateTitleActive = function(card) {
|
||||
if (!card || !card.classList.contains('has-marquee')) {
|
||||
if (card) card.classList.remove('is-title-active');
|
||||
return;
|
||||
}
|
||||
const hovered = card.dataset.titleHovered === '1';
|
||||
const focused = card.dataset.titleFocused === '1';
|
||||
const primary = card.dataset.titlePrimary === '1';
|
||||
const active = titleEnv.useHoverFocus ? (hovered || focused) : (focused || primary);
|
||||
card.classList.toggle('is-title-active', active);
|
||||
};
|
||||
|
||||
const measureTitle = function(card) {
|
||||
if (!card) return;
|
||||
const titleWrap = card.querySelector('.video-title');
|
||||
const titleText = card.querySelector('.video-title-text');
|
||||
if (!titleWrap || !titleText) return;
|
||||
const overflow = titleText.scrollWidth - titleWrap.clientWidth;
|
||||
if (overflow > 4) {
|
||||
card.classList.add('has-marquee');
|
||||
titleText.style.setProperty('--marquee-distance', `${overflow + 12}px`);
|
||||
} else {
|
||||
card.classList.remove('has-marquee', 'is-title-active');
|
||||
titleText.style.removeProperty('--marquee-distance');
|
||||
}
|
||||
updateTitleActive(card);
|
||||
};
|
||||
|
||||
let titleMeasureRaf = null;
|
||||
const scheduleTitleMeasure = function() {
|
||||
if (titleMeasureRaf) return;
|
||||
titleMeasureRaf = requestAnimationFrame(() => {
|
||||
titleMeasureRaf = null;
|
||||
document.querySelectorAll('.video-card').forEach((card) => {
|
||||
measureTitle(card);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('resize', scheduleTitleMeasure);
|
||||
|
||||
App.videos.formatDuration = function(seconds) {
|
||||
if (!seconds || seconds <= 0) return '';
|
||||
const totalSeconds = Math.floor(seconds);
|
||||
@@ -126,6 +201,10 @@ App.videos = App.videos || {};
|
||||
const durationText = App.videos.formatDuration(v.duration);
|
||||
const favoriteKey = App.favorites.getKey(v);
|
||||
const uploaderText = v.uploader || v.channel || '';
|
||||
const tags = Array.isArray(v.tags) ? v.tags.filter(tag => tag) : [];
|
||||
const tagsMarkup = tags.length
|
||||
? `<div class="video-tags">${tags.map(tag => `<button class="video-tag" type="button" data-tag="${tag}">${tag}</button>`).join('')}</div>`
|
||||
: '';
|
||||
card.innerHTML = `
|
||||
<button class="favorite-btn" type="button" aria-pressed="false" aria-label="Add to favorites" data-fav-key="${favoriteKey || ''}">♡</button>
|
||||
<button class="video-menu-btn" type="button" aria-haspopup="true" aria-expanded="false" aria-label="More options">⋯</button>
|
||||
@@ -137,7 +216,8 @@ App.videos = App.videos || {};
|
||||
<div class="video-loading" aria-hidden="true">
|
||||
<div class="video-loading-spinner"></div>
|
||||
</div>
|
||||
<h4>${v.title}</h4>
|
||||
<h4 class="video-title"><span class="video-title-text">${v.title}</span></h4>
|
||||
${tagsMarkup}
|
||||
${uploaderText ? `<p class="video-meta"><button class="uploader-link" type="button" data-uploader="${uploaderText}">${uploaderText}</button></p>` : ''}
|
||||
${durationText ? `<p class="video-duration">${durationText}</p>` : ''}
|
||||
`;
|
||||
@@ -154,6 +234,33 @@ App.videos = App.videos || {};
|
||||
App.favorites.toggle(v);
|
||||
};
|
||||
}
|
||||
const titleWrap = card.querySelector('.video-title');
|
||||
const titleText = card.querySelector('.video-title-text');
|
||||
if (titleWrap && titleText) {
|
||||
requestAnimationFrame(() => {
|
||||
measureTitle(card);
|
||||
});
|
||||
card.addEventListener('focusin', () => {
|
||||
card.dataset.titleFocused = '1';
|
||||
updateTitleActive(card);
|
||||
});
|
||||
card.addEventListener('focusout', () => {
|
||||
card.dataset.titleFocused = '0';
|
||||
updateTitleActive(card);
|
||||
});
|
||||
if (titleEnv.useHoverFocus) {
|
||||
card.addEventListener('mouseenter', () => {
|
||||
card.dataset.titleHovered = '1';
|
||||
updateTitleActive(card);
|
||||
});
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.dataset.titleHovered = '0';
|
||||
updateTitleActive(card);
|
||||
});
|
||||
} else if (titleObserver) {
|
||||
titleObserver.observe(card);
|
||||
}
|
||||
}
|
||||
const uploaderBtn = card.querySelector('.uploader-link');
|
||||
if (uploaderBtn) {
|
||||
uploaderBtn.onclick = (event) => {
|
||||
@@ -162,6 +269,16 @@ App.videos = App.videos || {};
|
||||
App.videos.handleSearch(uploader);
|
||||
};
|
||||
}
|
||||
const tagButtons = card.querySelectorAll('.video-tag');
|
||||
if (tagButtons.length) {
|
||||
tagButtons.forEach((tagBtn) => {
|
||||
tagBtn.onclick = (event) => {
|
||||
event.stopPropagation();
|
||||
const tag = tagBtn.dataset.tag || tagBtn.textContent || '';
|
||||
App.videos.handleSearch(tag);
|
||||
};
|
||||
});
|
||||
}
|
||||
const menuBtn = card.querySelector('.video-menu-btn');
|
||||
const menu = card.querySelector('.video-menu');
|
||||
const showInfoBtn = card.querySelector('.video-menu-item[data-action="info"]');
|
||||
|
||||
Reference in New Issue
Block a user