Add sports-match type, device tracking, profile visits, and share refactor
- New SportsMatch model/controller and sports UI components/modal - Move share-modal to a reusable x-share-modal/x-share-button component - Add VideoSharedWithUser notification and share-to-members flow - Device/user-agent tracking on views, downloads, share accesses - ProfileVisit model + migration; subscription source tracking - Email thumbnail support; remove stale TODO files
This commit is contained in:
1 parent
6aae6f86b6
commit
73527f3781
61 files changed
+4014
-636
No files matched your search
@@ -32,7 +32,10 @@
|
||||
.vdb-desc-text a.action-btn { display:inline-flex; margin:4px 6px 4px 0; color:inherit; text-decoration:none; vertical-align:middle; }
|
||||
.vdb-desc-text.vdb-clamp { max-height:130px; overflow:hidden; -webkit-mask-image:linear-gradient(180deg,#000 70%,transparent); mask-image:linear-gradient(180deg,#000 70%,transparent); }
|
||||
.vdb-desc-text.vdb-clamp.vdb-expanded { max-height:none; -webkit-mask-image:none; mask-image:none; }
|
||||
.vdb-show-more { background:none; border:none; color:var(--text-primary); font-weight:700; font-size:13px; cursor:pointer; padding:6px 0 0; }
|
||||
.vdb-show-more { display:flex; align-items:center; justify-content:center; gap:6px; margin:12px auto 0; background:var(--bg-secondary); border:1px solid var(--border-color); color:var(--text-primary); font-weight:700; font-size:13px; cursor:pointer; padding:7px 16px; border-radius:18px; transition:background .15s ease, border-color .15s ease; }
|
||||
.vdb-show-more:hover { background:var(--bg-hover, rgba(127,127,127,.12)); }
|
||||
.vdb-show-more i { font-size:14px; transition:transform .2s ease; }
|
||||
.vdb-show-more.expanded i { transform:rotate(180deg); }
|
||||
</style>
|
||||
|
||||
<div class="vdb-wrap" id="vdbWrap">
|
||||
@@ -58,7 +61,7 @@
|
||||
{!! $descriptionSlot !!}
|
||||
@elseif($renderedDescription !== '')
|
||||
<div id="vdbDescShort" class="vdb-desc-text vdb-clamp">{!! $renderedDescription !!}</div>
|
||||
<button id="vdbShowMore" class="vdb-show-more" style="display:none;" onclick="toggleVdbDesc(this)">Show more</button>
|
||||
<button id="vdbShowMore" class="vdb-show-more" style="display:none;" onclick="toggleVdbDesc(this)"><span>Show more</span> <i class="bi bi-chevron-down"></i></button>
|
||||
@else
|
||||
<p style="font-size:13px;color:var(--text-secondary);margin:0;">No description added.</p>
|
||||
@endif
|
||||
@@ -70,8 +73,24 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Remember which tab the user opened so SPA navigation between videos keeps it active.
|
||||
window._vdbActiveTab = window._vdbActiveTab || 'vdb-about';
|
||||
|
||||
// Scroll back up to the video player on every SPA video-to-video swap.
|
||||
// On mobile the window is locked (see CLAUDE.md mobile scroll model) and `.yt-main`
|
||||
// (id="main") is the real scroll container, so we scroll that instead.
|
||||
window._spaScrollToVideo = window._spaScrollToVideo || function () {
|
||||
const main = document.getElementById('main');
|
||||
if (window.innerWidth <= 768 && main) {
|
||||
main.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Tab switching ──────────────────────────────────────
|
||||
function switchVdbTab(panelId, btn) {
|
||||
window._vdbActiveTab = panelId;
|
||||
document.querySelectorAll('.vdb-tab').forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.vdb-panel').forEach(p => p.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
@@ -82,11 +101,44 @@ function switchVdbTab(panelId, btn) {
|
||||
if (currentUrl && currentUrl !== window._insLoadedUrl) loadInsights();
|
||||
}
|
||||
}
|
||||
|
||||
// Re-apply the remembered tab after an SPA swap replaces #vdbWrap's contents.
|
||||
// If the remembered tab doesn't exist on the new video (e.g. Insights when the
|
||||
// viewer isn't the owner), fall back to About so nothing is left blank.
|
||||
function _vdbApplyActiveTab() {
|
||||
const wrap = document.getElementById('vdbWrap');
|
||||
if (!wrap) return;
|
||||
let target = window._vdbActiveTab || 'vdb-about';
|
||||
let btn = wrap.querySelector('.vdb-tab[data-panel="' + target + '"]');
|
||||
if (!btn) { target = 'vdb-about'; btn = wrap.querySelector('.vdb-tab[data-panel="vdb-about"]'); }
|
||||
if (!btn) return;
|
||||
wrap.querySelectorAll('.vdb-tab').forEach(b => b.classList.remove('active'));
|
||||
wrap.querySelectorAll('.vdb-panel').forEach(p => p.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
const panel = document.getElementById(target);
|
||||
if (panel) panel.classList.add('active');
|
||||
if (target === 'vdb-insights') {
|
||||
const ip = document.getElementById('vdb-insights');
|
||||
const currentUrl = ip && ip.dataset.insightsBase;
|
||||
if (currentUrl && currentUrl !== window._insLoadedUrl && typeof loadInsights === 'function') loadInsights();
|
||||
}
|
||||
}
|
||||
// Observe #vdbWrap so the active tab is re-applied whenever the SPA layer
|
||||
// rewrites its innerHTML during a video-to-video transition.
|
||||
(function _vdbWatchSwaps() {
|
||||
const wrap = document.getElementById('vdbWrap');
|
||||
if (!wrap || wrap._vdbTabObserver) return;
|
||||
const obs = new MutationObserver(() => _vdbApplyActiveTab());
|
||||
obs.observe(wrap, { childList: true, subtree: false });
|
||||
wrap._vdbTabObserver = obs;
|
||||
})();
|
||||
function toggleVdbDesc(btn) {
|
||||
const d = document.getElementById('vdbDescShort');
|
||||
if (!d) return;
|
||||
const expanded = d.classList.toggle('vdb-expanded');
|
||||
btn.textContent = expanded ? 'Show less' : 'Show more';
|
||||
btn.classList.toggle('expanded', expanded);
|
||||
const label = btn.querySelector('span');
|
||||
if (label) label.textContent = expanded ? 'Show less' : 'Show more';
|
||||
}
|
||||
// Reveal "Show more" only when the description overflows the clamp. Compare the
|
||||
// natural content height to the clamp limit (130px) rather than clientHeight,
|
||||
@@ -94,8 +146,8 @@ function toggleVdbDesc(btn) {
|
||||
function _vdbCheckOverflow() {
|
||||
const d = document.getElementById('vdbDescShort'), b = document.getElementById('vdbShowMore');
|
||||
if (!d || !b) return;
|
||||
if (d.classList.contains('vdb-expanded')) { b.style.display = 'block'; return; }
|
||||
b.style.display = (d.scrollHeight > 138) ? 'block' : 'none';
|
||||
if (d.classList.contains('vdb-expanded')) { b.style.display = 'flex'; return; }
|
||||
b.style.display = (d.scrollHeight > 138) ? 'flex' : 'none';
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', _vdbCheckOverflow);
|
||||
window.addEventListener('load', _vdbCheckOverflow);
|
||||
|
||||
Reference in new issue
Block a user