Skip to content

Commit 0154c2b

Browse files
authored
Fix pagination links broken on gistpreview.github.io (#32)
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 https://gisthost.github.io/?3581c145202388b43862c93176e9f8ad
1 parent b7fed22 commit 0154c2b

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,6 +1048,7 @@ def render_message(log_type, message_json, timestamp):
10481048
"""
10491049

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

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)