.meganote { font-style: italic; } /* example utility class */
#wiki-multipane {
display: grid;
grid-template-columns: minmax(25rem, 1fr) minmax(20rem, 1fr); /* main (min 600px) | source (400px) */
grid-template-rows: 1fr;
gap: 1rem;
}
.megapage-grid {
/* Remove grid properties - let Material handle its normal layout */
display: block;
}
/* Source pane positioning - fixed relative to viewport but below header */
#wiki-source-pane {
border-left: 1px solid var(--md-default-fg-color--lightest);
border-radius: 0.5rem 0 0 0.5rem;
position: fixed;
top: 2.5rem; /* Below Material's header */
right: 0rem;
width: calc(40% - 1.5rem); /* Responsive width minus gap */
min-width: 20rem;
height: calc(100vh - 2.5rem); /* Full height minus header and small margin */
padding: 0;
background: var(--md-default-bg-color);
z-index: 100;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Source pane header with controls */
.source-pane-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0rem;
border-bottom: 1px solid var(--md-default-fg-color--lightest);
background: var(--md-default-bg-color);
flex-shrink: 0;
position: absolute;
width: 100%;
min-width: 20rem;
z-index: 1000;
opacity: 1.0;
}
.source-pane-links {
display: flex;
gap: 0;
align-items: center;
}
.source-pane-controls {
display: flex;
gap: 0.25rem;
align-items: center;
}
.source-control-btn {
background: var(--md-default-accent-fg-color);
color: var(--md-default-fg-color);
/* border: 1px solid var(--md-default-fg-color--light); */
font-size: 12px;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.25rem;
transition: background-color 0.2s;
}
.source-control-btn:hover {
background: var(--md-default-fg-color--lightest);
text-decoration: none;
}
.source-control-btn .icon {
font-size: 10px;
}
#source-frame {
width: 100%;
height: 100%;
flex: 1;
border: none;
border-radius: 0;
}
/* Adjust main content margin when source pane is visible */
#wiki-multipane:has(#wiki-source-pane[style*="block"]) #wiki-main-pane {
margin-right: 1rem;
}
/* Close button styling */
.close-btn {
background: var(--md-accent-fg-color);
color: white;
border: none;
font-size: 16px;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
line-height: 1;
font-weight: bold;
}
.close-btn:hover {
opacity: 0.8;
}
/* Hide the source pane when there's not enough space for both main (600px) + source (400px) + gap */
@media (max-width: 64rem) { /* 600px + 400px + gap ≈ 1024px */
#wiki-multipane { grid-template-columns: 1fr; }
#wiki-source-pane { display: none !important; }
#wiki-main-pane { margin-right: 0 !important; }
}
/* Dark mode adjustments */
@media (prefers-color-scheme: dark) {
#wiki-source-pane {
border-left-color: var(--md-default-fg-color--lightest);
}
}
.md-footer {
display: none;
}ove source parameter from hash
const currentHash = window.location.hash;
const newHash = currentHash
.replace(/[#&]source=[^&]*/, '')
.replace(/^&/, '#')
.replace(/&$/, '');
if (newHash !== currentHash) {
history.pushState(null, '', newHash || '#');
}
}
async function discoverAndPreloadSources() {
console.log('🔍 Discovering available source pages...');
// Find all source-link elements on the page
const sourceLinks = document.querySelectorAll('a.source-link');
const discoveryPromises = [];
for (const link of sourceLinks) {
const originalUrl = link.href;
const localPath = `/sources/${await slug(originalUrl)}`;
// Check if local version exists (do this once during preload)
discoveryPromises.push(
fetch(localPath, { method: 'HEAD' })
.then(response => {
if (response.ok) {
sourceCache.registerSource(originalUrl, localPath);
console.log(`📍 Registered: ${originalUrl} -> ${localPath}`);
}
})
.catch(() => {
// Local version doesn't exist, that's fine
})
);
}
await Promise.all(discoveryPromises);
console.log(`✅ Discovery complete. Found ${sourceCache.sourceRegistry.size} local sources.`);
}
async function preloadSource(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const content = await response.text();
sourceCache.set(url, content);
return content;
} catch (error) {
console.warn(`Failed to preload source: ${url}`, error);
return null;
}
}
async function preloadAllSources() {
if (sourceCache.preloadComplete) return;
await discoverAndPreloadSources();
// Preload a few of the most recently discovered sources
const localSources = Array.from(sourceCache.sourceRegistry.values()).slice(0, 10); // Limit to first 10
if (localSources.length > 0) {
console.log(`📦 Preloading ${localSources.length} source pages...`);
const preloadPromises = localSources.map(async (localPath) => {
if (!sourceCache.has(localPath)) {
await preloadSource(localPath);
}
});
await Promise.all(preloadPromises);
console.log(`🚀 Preloaded ${sourceCache.size()} source pages`);
}
sourceCache.preloadComplete = true;
}
function createDataUrl(htmlContent) {
// Create a data URL for the HTML content
const blob = new Blob([htmlContent], { type: 'text/html' });
return URL.createObjectURL(blob);
}
function updateSourceControls() {
const originalBtn = document.querySelector('.source-control-original');
const archiveBtn = document.querySelector('.source-control-archive');
if (!currentSourceUrl || !originalBtn || !archiveBtn) return;
// Update original button
originalBtn.href = currentSourceUrl;
// Update archive button
if (sourceCache.isAvailableLocally(currentSourceUrl)) {
const localPath = sourceCache.getLocalPath(currentSourceUrl);
archiveBtn.href = localPath;
archiveBtn.style.display = 'inline-flex';
} else {
archiveBtn.style.display = 'none';
}
}
async function showSourcePane(sourceUrl) {
const pane = document.getElementById('wiki-source-pane');
const frame = document.getElementById('source-frame');
if (!hasSpaceForSourcePane()) {
window.open(sourceUrl, '_blank');
return;
}
// Update current source URL for header controls
currentSourceUrl = sourceUrl;
// Show pane first for immediate feedback
if (pane) pane.style.display = 'block';
// Update header controls
updateSourceControls();
// Determine target without any network requests
let target = sourceUrl;
if (sourceCache.isAvailableLocally(sourceUrl)) {
target = sourceCache.getLocalPath(sourceUrl);
}
// Check if we have cached content (instant load)
if (sourceCache.has(target)) {
const cachedContent = sourceCache.get(target);
const dataUrl = createDataUrl(cachedContent);
if (frame) frame.src = dataUrl;
console.log(`⚡ Instant load from cache: ${target}`);
} else {
// Only make network request if not cached yet
if (target.startsWith('/sources/')) {
console.log(`📥 Loading and caching: ${target}`);
const content = await preloadSource(target);
if (content && frame) {
const dataUrl = createDataUrl(content);
frame.src = dataUrl;
} else if (frame) {
frame.src = target; // Fallback
}
} else {
// External URL - load directly
if (frame) frame.src = target;
}
}
// Update URL
setSourceUrlInHash(sourceUrl);
}
function hideSourcePane() {
const pane = document.getElementById('wiki-source-pane');
if (pane) {
pane.style.display = 'none';
currentSourceUrl = null;
}
removeSourceFromHash();
}
function createSourcePaneHeader() {
const header = document.createElement('div');
header.className = 'source-pane-header';
const title = document.createElement('span');
title.textContent = 'Source';
title.style.fontWeight = 'bold';
title.style.fontSize = '14px';
const controls = document.createElement('div');
controls.className = 'source-pane-controls';
// Original button
const originalBtn = document.createElement('a');
originalBtn.className = 'source-control-btn source-control-original';
originalBtn.target = '_blank';
originalBtn.innerHTML = '↗original';
// Archive button
const archiveBtn = document.createElement('a');
archiveBtn.className = 'source-control-btn source-control-archive';
archiveBtn.target = '_blank';
archiveBtn.innerHTML = '↗archive';
// Close button
const closeBtn = document.createElement('button');
closeBtn.className = 'close-btn';
closeBtn.innerHTML = '×';
closeBtn.addEventListener('click', hideSourcePane);
controls.appendChild(originalBtn);
controls.appendChild(archiveBtn);
controls.appendChild(closeBtn);
header.appendChild(title);
header.appendChild(controls);
return header;
}
/* ---------- main ----------------------------------------------------- */
document.addEventListener('DOMContentLoaded', async () => {
const pane = document.getElementById('wiki-source-pane'); // wrapper