Tonne of debugging - getting ready for the production builds
This commit is contained in:
13
mkdocs/docs/assets/js/env-config.js
Normal file
13
mkdocs/docs/assets/js/env-config.js
Normal 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
|
||||
});
|
||||
})();
|
||||
18
mkdocs/docs/assets/js/video-config.js
Normal file
18
mkdocs/docs/assets/js/video-config.js
Normal 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)
|
||||
224
mkdocs/docs/assets/js/video-player.js
Normal file
224
mkdocs/docs/assets/js/video-player.js
Normal 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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user