Skip to content

Commit e26aa10

Browse files
committed
Fix pagination links broken on gistpreview.github.io
The JavaScript that rewrites relative URLs for gistpreview.github.io now handles dynamic content and SPA-style navigation: - Skip already-rewritten links (starting with '?') to prevent double-rewriting when JS runs multiple times - Use MutationObserver to catch dynamically added links - Run on DOMContentLoaded as fallback for DOM timing issues - Add longer delay retry (2s) for slow-loading content Fixes #26
1 parent 6be0003 commit e26aa10

File tree

2 files changed

+94
-10
lines changed

2 files changed

+94
-10
lines changed

src/claude_code_transcripts/__init__.py

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,24 +1048,71 @@ def render_message(log_type, message_json, timestamp):
10481048
"""
10491049

10501050
# JavaScript to fix relative URLs when served via gistpreview.github.io
1051+
# Fixes issue #26: Pagination links broken on gistpreview.github.io
10511052
GIST_PREVIEW_JS = r"""
10521053
(function() {
10531054
if (window.location.hostname !== 'gistpreview.github.io') return;
10541055
// URL format: https://gistpreview.github.io/?GIST_ID/filename.html
10551056
var match = window.location.search.match(/^\?([^/]+)/);
10561057
if (!match) return;
10571058
var gistId = match[1];
1058-
document.querySelectorAll('a[href]').forEach(function(link) {
1059-
var href = link.getAttribute('href');
1060-
// Skip external links and anchors
1061-
if (href.startsWith('http') || href.startsWith('#') || href.startsWith('//')) return;
1062-
// Handle anchor in relative URL (e.g., page-001.html#msg-123)
1063-
var parts = href.split('#');
1064-
var filename = parts[0];
1065-
var anchor = parts.length > 1 ? '#' + parts[1] : '';
1066-
link.setAttribute('href', '?' + gistId + '/' + filename + anchor);
1059+
1060+
function rewriteLinks(root) {
1061+
(root || document).querySelectorAll('a[href]').forEach(function(link) {
1062+
var href = link.getAttribute('href');
1063+
// Skip already-rewritten links (issue #26 fix)
1064+
if (href.startsWith('?')) return;
1065+
// Skip external links and anchors
1066+
if (href.startsWith('http') || href.startsWith('#') || href.startsWith('//')) return;
1067+
// Handle anchor in relative URL (e.g., page-001.html#msg-123)
1068+
var parts = href.split('#');
1069+
var filename = parts[0];
1070+
var anchor = parts.length > 1 ? '#' + parts[1] : '';
1071+
link.setAttribute('href', '?' + gistId + '/' + filename + anchor);
1072+
});
1073+
}
1074+
1075+
// Run immediately
1076+
rewriteLinks();
1077+
1078+
// Also run on DOMContentLoaded in case DOM isn't ready yet
1079+
if (document.readyState === 'loading') {
1080+
document.addEventListener('DOMContentLoaded', function() { rewriteLinks(); });
1081+
}
1082+
1083+
// Use MutationObserver to catch dynamically added content
1084+
// gistpreview.github.io may add content after initial load
1085+
var observer = new MutationObserver(function(mutations) {
1086+
mutations.forEach(function(mutation) {
1087+
mutation.addedNodes.forEach(function(node) {
1088+
if (node.nodeType === 1) { // Element node
1089+
rewriteLinks(node);
1090+
// Also check if the node itself is a link
1091+
if (node.tagName === 'A' && node.getAttribute('href')) {
1092+
var href = node.getAttribute('href');
1093+
if (!href.startsWith('?') && !href.startsWith('http') &&
1094+
!href.startsWith('#') && !href.startsWith('//')) {
1095+
var parts = href.split('#');
1096+
var filename = parts[0];
1097+
var anchor = parts.length > 1 ? '#' + parts[1] : '';
1098+
node.setAttribute('href', '?' + gistId + '/' + filename + anchor);
1099+
}
1100+
}
1101+
}
1102+
});
1103+
});
10671104
});
10681105
1106+
// Start observing once body exists
1107+
function startObserving() {
1108+
if (document.body) {
1109+
observer.observe(document.body, { childList: true, subtree: true });
1110+
} else {
1111+
setTimeout(startObserving, 10);
1112+
}
1113+
}
1114+
startObserving();
1115+
10691116
// Handle fragment navigation after dynamic content loads
10701117
// gistpreview.github.io loads content dynamically, so the browser's
10711118
// native fragment navigation fails because the element doesn't exist yet
@@ -1084,7 +1131,7 @@ def render_message(log_type, message_json, timestamp):
10841131
// Try immediately in case content is already loaded
10851132
if (!scrollToFragment()) {
10861133
// Retry with increasing delays to handle dynamic content loading
1087-
var delays = [100, 300, 500, 1000];
1134+
var delays = [100, 300, 500, 1000, 2000];
10881135
delays.forEach(function(delay) {
10891136
setTimeout(scrollToFragment, delay);
10901137
});

tests/test_generate_html.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,43 @@ def test_handles_empty_directory(self, output_dir):
466466
inject_gist_preview_js(output_dir)
467467
# Should complete without error
468468

469+
def test_gist_preview_js_skips_already_rewritten_links(self):
470+
"""Test that GIST_PREVIEW_JS skips links that have already been rewritten.
471+
472+
When navigating between pages on gistpreview.github.io, the JS may run
473+
multiple times. Links that have already been rewritten to the
474+
?GIST_ID/filename.html format should be skipped to avoid double-rewriting.
475+
476+
This fixes issue #26 where pagination links break on later pages.
477+
"""
478+
# The JS should check if href already starts with '?'
479+
assert "href.startsWith('?')" in GIST_PREVIEW_JS
480+
481+
def test_gist_preview_js_uses_mutation_observer(self):
482+
"""Test that GIST_PREVIEW_JS uses MutationObserver for dynamic content.
483+
484+
gistpreview.github.io loads content dynamically. When navigating between
485+
pages via SPA-style navigation, new content is inserted without a full
486+
page reload. The JS needs to use MutationObserver to detect and rewrite
487+
links in dynamically added content.
488+
489+
This fixes issue #26 where pagination links break on later pages.
490+
"""
491+
# The JS should use MutationObserver
492+
assert "MutationObserver" in GIST_PREVIEW_JS
493+
494+
def test_gist_preview_js_runs_on_dom_content_loaded(self):
495+
"""Test that GIST_PREVIEW_JS runs on DOMContentLoaded.
496+
497+
The script is injected at the end of the body, but in some cases
498+
(especially on gistpreview.github.io), the DOM might not be fully ready
499+
when the script runs. We should also run on DOMContentLoaded as a fallback.
500+
501+
This fixes issue #26 where pagination links break on later pages.
502+
"""
503+
# The JS should listen for DOMContentLoaded
504+
assert "DOMContentLoaded" in GIST_PREVIEW_JS
505+
469506

470507
class TestCreateGist:
471508
"""Tests for the create_gist function."""

0 commit comments

Comments
 (0)