tags and title fix
This commit is contained in:
@@ -910,12 +910,28 @@ body.theme-light .setting-item select option {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
letter-spacing: -0.2px;
|
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 {
|
.video-card p {
|
||||||
@@ -929,6 +945,36 @@ body.theme-light .setting-item select option {
|
|||||||
padding-bottom: 4px;
|
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 {
|
.uploader-link {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -10,6 +10,40 @@ App.videos = App.videos || {};
|
|||||||
threshold: 1.0
|
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() {
|
App.videos.observeSentinel = function() {
|
||||||
const sentinel = document.getElementById('sentinel');
|
const sentinel = document.getElementById('sentinel');
|
||||||
if (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) {
|
App.videos.formatDuration = function(seconds) {
|
||||||
if (!seconds || seconds <= 0) return '';
|
if (!seconds || seconds <= 0) return '';
|
||||||
const totalSeconds = Math.floor(seconds);
|
const totalSeconds = Math.floor(seconds);
|
||||||
@@ -126,6 +201,10 @@ App.videos = App.videos || {};
|
|||||||
const durationText = App.videos.formatDuration(v.duration);
|
const durationText = App.videos.formatDuration(v.duration);
|
||||||
const favoriteKey = App.favorites.getKey(v);
|
const favoriteKey = App.favorites.getKey(v);
|
||||||
const uploaderText = v.uploader || v.channel || '';
|
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 = `
|
card.innerHTML = `
|
||||||
<button class="favorite-btn" type="button" aria-pressed="false" aria-label="Add to favorites" data-fav-key="${favoriteKey || ''}">♡</button>
|
<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>
|
<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" aria-hidden="true">
|
||||||
<div class="video-loading-spinner"></div>
|
<div class="video-loading-spinner"></div>
|
||||||
</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>` : ''}
|
${uploaderText ? `<p class="video-meta"><button class="uploader-link" type="button" data-uploader="${uploaderText}">${uploaderText}</button></p>` : ''}
|
||||||
${durationText ? `<p class="video-duration">${durationText}</p>` : ''}
|
${durationText ? `<p class="video-duration">${durationText}</p>` : ''}
|
||||||
`;
|
`;
|
||||||
@@ -154,6 +234,33 @@ App.videos = App.videos || {};
|
|||||||
App.favorites.toggle(v);
|
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');
|
const uploaderBtn = card.querySelector('.uploader-link');
|
||||||
if (uploaderBtn) {
|
if (uploaderBtn) {
|
||||||
uploaderBtn.onclick = (event) => {
|
uploaderBtn.onclick = (event) => {
|
||||||
@@ -162,6 +269,16 @@ App.videos = App.videos || {};
|
|||||||
App.videos.handleSearch(uploader);
|
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 menuBtn = card.querySelector('.video-menu-btn');
|
||||||
const menu = card.querySelector('.video-menu');
|
const menu = card.querySelector('.video-menu');
|
||||||
const showInfoBtn = card.querySelector('.video-menu-item[data-action="info"]');
|
const showInfoBtn = card.querySelector('.video-menu-item[data-action="info"]');
|
||||||
|
|||||||
Reference in New Issue
Block a user