Tonne of debugging - getting ready for the production builds

This commit is contained in:
2026-02-16 10:44:18 -07:00
parent a77306fac2
commit 7895ce683e
1367 changed files with 404191 additions and 2005 deletions

View File

@@ -0,0 +1,13 @@
// Auto-generated video player configuration from environment variables
// Generated by mkdocs/docs/hooks/env_config_hook.py
(function() {
window.MEDIA_API_URL = 'http://localhost:4100';
window.PUBLIC_URL = 'http://localhost:3000';
window.VIDEO_PLAYER_DEBUG = false;
console.log('[Video Config] Loaded from environment:', {
mediaApiUrl: window.MEDIA_API_URL,
publicUrl: window.PUBLIC_URL,
debug: window.VIDEO_PLAYER_DEBUG
});
})();

View File

@@ -0,0 +1,18 @@
/**
* Video Player Configuration (LEGACY)
*
* ⚠️ DEPRECATED: This file is no longer used.
*
* Configuration is now auto-generated from environment variables via
* mkdocs/docs/hooks/env_config_hook.py → assets/js/env-config.js
*
* To configure video player URLs, set these environment variables:
* - MEDIA_API_PUBLIC_URL (default: http://localhost:4100)
* - ADMIN_URL (default: http://localhost:3000)
* - VIDEO_PLAYER_DEBUG (default: false)
*
* This file is kept for reference only.
*/
// DEPRECATED - DO NOT EDIT
// See env-config.js (auto-generated)

View File

@@ -0,0 +1,224 @@
/**
* Video Player Hydration for MkDocs
*
* Automatically converts .video-block placeholders into actual video players
* when landing pages with video blocks are exported to MkDocs.
*
* Supports both standard HTML5 video and advanced player with reactions.
*/
(function() {
'use strict';
// Configuration
const MEDIA_API_URL = window.MEDIA_API_URL || 'http://localhost:4100';
const PUBLIC_URL = window.PUBLIC_URL || 'http://localhost:3000';
/**
* Fetch video metadata from Media API
*/
async function fetchVideoMetadata(videoId) {
try {
const response = await fetch(`${MEDIA_API_URL}/api/videos/${videoId}/metadata`);
if (!response.ok) {
throw new Error(`Failed to fetch video ${videoId}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Error fetching video ${videoId} metadata:`, error);
return null;
}
}
/**
* Create standard HTML5 video player
*/
function createStandardPlayer(metadata, options) {
const video = document.createElement('video');
video.src = `${MEDIA_API_URL}/api/videos/${metadata.id}/stream`;
video.poster = `${MEDIA_API_URL}/api/videos/${metadata.id}/thumbnail`;
video.controls = options.controls !== false;
video.autoplay = options.autoplay === true;
video.loop = options.loop === true;
video.muted = options.muted === true;
video.style.width = options.width || '100%';
video.style.height = options.height || 'auto';
video.style.maxWidth = '100%';
video.style.borderRadius = '8px';
video.style.display = 'block';
// Add title attribute for accessibility
video.setAttribute('title', metadata.title);
video.setAttribute('aria-label', metadata.title);
return video;
}
/**
* Create advanced player container with reactions
* Note: Full reaction functionality requires React components,
* so this is a simplified version for MkDocs
*/
function createAdvancedPlayer(metadata, options) {
const container = document.createElement('div');
container.className = 'advanced-video-container';
container.style.maxWidth = options.width || '100%';
container.style.margin = '0 auto';
// Video player
const video = createStandardPlayer(metadata, options);
container.appendChild(video);
// Reaction placeholder (simplified for MkDocs)
if (options.showReactions !== false) {
const reactionBar = document.createElement('div');
reactionBar.className = 'video-reactions';
reactionBar.style.marginTop = '12px';
reactionBar.style.padding = '12px';
reactionBar.style.background = '#f5f5f5';
reactionBar.style.borderRadius = '8px';
reactionBar.style.textAlign = 'center';
reactionBar.style.fontSize = '14px';
reactionBar.style.color = '#666';
reactionBar.innerHTML = `
<p style="margin: 0;">
<a href="${PUBLIC_URL}/media/${metadata.id}" target="_blank" style="color: #1890ff; text-decoration: none;">
View full player with reactions →
</a>
</p>
`;
container.appendChild(reactionBar);
}
// Video metadata
const metaInfo = document.createElement('div');
metaInfo.className = 'video-meta';
metaInfo.style.marginTop = '8px';
metaInfo.style.fontSize = '13px';
metaInfo.style.color = '#999';
metaInfo.innerHTML = `
<strong style="color: #333;">${escapeHtml(metadata.title)}</strong>
${metadata.durationSeconds ? `<span style="margin-left: 8px;">• ${formatDuration(metadata.durationSeconds)}</span>` : ''}
${metadata.width && metadata.height ? `<span style="margin-left: 8px;">• ${metadata.width}×${metadata.height}</span>` : ''}
`;
container.appendChild(metaInfo);
return container;
}
/**
* Render a video block
*/
async function renderVideoBlock(blockElement) {
const videoId = parseInt(blockElement.dataset.videoId, 10);
const playerType = blockElement.dataset.playerType || 'standard';
const width = blockElement.dataset.width || '100%';
const height = blockElement.dataset.height || 'auto';
const autoplay = blockElement.dataset.autoplay === 'true';
const controls = blockElement.dataset.controls !== 'false';
const showReactions = blockElement.dataset.showReactions !== 'false';
if (!videoId || isNaN(videoId)) {
console.warn('Invalid video ID in video block:', blockElement);
showError(blockElement, 'Invalid video ID');
return;
}
// Show loading state
blockElement.innerHTML = '<div class="video-loading" style="text-align: center; padding: 40px; color: #999;">Loading video...</div>';
// Fetch metadata
const metadata = await fetchVideoMetadata(videoId);
if (!metadata) {
showError(blockElement, `Video not found (ID: ${videoId})`);
return;
}
// Create player
const options = { width, height, autoplay, controls, showReactions };
const player = playerType === 'advanced'
? createAdvancedPlayer(metadata, options)
: createStandardPlayer(metadata, options);
// Replace placeholder with player
blockElement.innerHTML = '';
blockElement.appendChild(player);
console.log(`Rendered ${playerType} video player for video ${videoId}`);
}
/**
* Show error state
*/
function showError(blockElement, message) {
blockElement.innerHTML = `
<div class="video-error" style="
padding: 40px;
text-align: center;
background: #fff3f3;
border: 1px solid #ffccc7;
border-radius: 8px;
color: #cf1322;
">
<svg style="width: 48px; height: 48px; margin-bottom: 12px;" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
<p style="margin: 0; font-weight: 600;">${escapeHtml(message)}</p>
</div>
`;
}
/**
* Format duration in seconds to MM:SS
*/
function formatDuration(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
/**
* Escape HTML to prevent XSS
*/
function escapeHtml(unsafe) {
return String(unsafe)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
/**
* Initialize all video blocks on page load
*/
function initVideoBlocks() {
const blocks = document.querySelectorAll('.video-block');
console.log(`Found ${blocks.length} video block(s) to hydrate`);
blocks.forEach(block => {
// Skip if already rendered (has video element)
if (block.querySelector('video')) {
console.log('Video block already rendered, skipping');
return;
}
renderVideoBlock(block);
});
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initVideoBlocks);
} else {
initVideoBlocks();
}
// Re-initialize on MkDocs navigation (for SPA-style navigation)
if (typeof window.document$ !== 'undefined') {
window.document$.subscribe(() => {
console.log('MkDocs navigation detected, re-initializing video blocks');
setTimeout(initVideoBlocks, 100);
});
}
})();