Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/claude_code_transcripts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1141,14 +1141,13 @@ def render_message(log_type, message_json, timestamp):
});
"""

# JavaScript to fix relative URLs when served via gisthost.github.io or gistpreview.github.io
# Fixes issue #26: Pagination links broken on gisthost.github.io
# JavaScript to fix relative URLs when served via gist preview hosts.
# Detects gist preview context by the URL query string pattern ?<hex-gist-id>/...
# Fixes issue #26: Pagination links broken on gist preview hosts
GIST_PREVIEW_JS = r"""
(function() {
var hostname = window.location.hostname;
if (hostname !== 'gisthost.github.io' && hostname !== 'gistpreview.github.io') return;
// URL format: https://gisthost.github.io/?GIST_ID/filename.html
var match = window.location.search.match(/^\?([^/]+)/);
// Detect gist preview context by query string pattern: ?<hex-gist-id>/...
var match = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i);
if (!match) return;
var gistId = match[1];

Expand Down Expand Up @@ -1176,7 +1175,7 @@ def render_message(log_type, message_json, timestamp):
}

// Use MutationObserver to catch dynamically added content
// gistpreview.github.io may add content after initial load
// Gist preview hosts may add content after initial load
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
Expand Down Expand Up @@ -1209,7 +1208,7 @@ def render_message(log_type, message_json, timestamp):
startObserving();

// Handle fragment navigation after dynamic content loads
// gisthost.github.io/gistpreview.github.io loads content dynamically, so the browser's
// Gist preview hosts load content dynamically, so the browser's
// native fragment navigation fails because the element doesn't exist yet
function scrollToFragment() {
var hash = window.location.hash;
Expand Down
16 changes: 4 additions & 12 deletions src/claude_code_transcripts/templates/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,13 @@
// Show search box (progressive enhancement)
searchBox.style.display = 'flex';

// Gist preview support - detect if we're on gisthost.github.io or gistpreview.github.io
var hostname = window.location.hostname;
var isGistPreview = hostname === 'gisthost.github.io' || hostname === 'gistpreview.github.io';
var gistId = null;
// Gist preview support - detect by query string pattern: ?<hex-gist-id>/...
var gistMatch = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i);
var isGistPreview = !!gistMatch;
var gistId = gistMatch ? gistMatch[1] : null;
var gistOwner = null;
var gistInfoLoaded = false;

if (isGistPreview) {
// Extract gist ID from URL query string like ?78a436a8a9e7a2e603738b8193b95410/index.html
var queryMatch = window.location.search.match(/^\?([a-f0-9]+)/i);
if (queryMatch) {
gistId = queryMatch[1];
}
}

async function loadGistInfo() {
if (!isGistPreview || !gistId || gistInfoLoaded) return;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,21 +221,13 @@ <h1>Claude Code transcript</h1>
// Show search box (progressive enhancement)
searchBox.style.display = 'flex';

// Gist preview support - detect if we're on gisthost.github.io or gistpreview.github.io
var hostname = window.location.hostname;
var isGistPreview = hostname === 'gisthost.github.io' || hostname === 'gistpreview.github.io';
var gistId = null;
// Gist preview support - detect by query string pattern: ?<hex-gist-id>/...
var gistMatch = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i);
var isGistPreview = !!gistMatch;
var gistId = gistMatch ? gistMatch[1] : null;
var gistOwner = null;
var gistInfoLoaded = false;

if (isGistPreview) {
// Extract gist ID from URL query string like ?78a436a8a9e7a2e603738b8193b95410/index.html
var queryMatch = window.location.search.match(/^\?([a-f0-9]+)/i);
if (queryMatch) {
gistId = queryMatch[1];
}
}

async function loadGistInfo() {
if (!isGistPreview || !gistId || gistInfoLoaded) return;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,21 +212,13 @@ <h1>Claude Code transcript</h1>
// Show search box (progressive enhancement)
searchBox.style.display = 'flex';

// Gist preview support - detect if we're on gisthost.github.io or gistpreview.github.io
var hostname = window.location.hostname;
var isGistPreview = hostname === 'gisthost.github.io' || hostname === 'gistpreview.github.io';
var gistId = null;
// Gist preview support - detect by query string pattern: ?<hex-gist-id>/...
var gistMatch = window.location.search.match(/^\?([a-f0-9]{20,})(\/|$)/i);
var isGistPreview = !!gistMatch;
var gistId = gistMatch ? gistMatch[1] : null;
var gistOwner = null;
var gistInfoLoaded = false;

if (isGistPreview) {
// Extract gist ID from URL query string like ?78a436a8a9e7a2e603738b8193b95410/index.html
var queryMatch = window.location.search.match(/^\?([a-f0-9]+)/i);
if (queryMatch) {
gistId = queryMatch[1];
}
}

async function loadGistInfo() {
if (!isGistPreview || !gistId || gistInfoLoaded) return;
try {
Expand Down
39 changes: 37 additions & 2 deletions tests/test_generate_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,41 @@ def test_gist_preview_js_runs_on_dom_content_loaded(self):
# The JS should listen for DOMContentLoaded
assert "DOMContentLoaded" in GIST_PREVIEW_JS

def test_gist_preview_js_detects_by_query_string_not_hostname(self):
"""Test that GIST_PREVIEW_JS detects gist preview context using the URL
query string pattern (?<hex-gist-id>/...) instead of hardcoded hostnames.

This allows it to work on any gist preview host, not just
gisthost.github.io and gistpreview.github.io.
"""
# Should NOT contain hardcoded hostname checks
assert "gisthost.github.io" not in GIST_PREVIEW_JS
assert "gistpreview.github.io" not in GIST_PREVIEW_JS
# Should use a regex on location.search to detect gist preview context
assert "location.search" in GIST_PREVIEW_JS


class TestSearchJsGistDetection:
"""Tests for gist preview detection in search.js template."""

@pytest.fixture
def search_js_content(self):
search_js_path = (
Path(__file__).parent.parent
/ "src"
/ "claude_code_transcripts"
/ "templates"
/ "search.js"
)
return search_js_path.read_text(encoding="utf-8")

def test_search_js_detects_by_query_string_not_hostname(self, search_js_content):
"""Test that search.js detects gist preview context using the URL
query string pattern instead of hardcoded hostnames."""
assert "gisthost.github.io" not in search_js_content
assert "gistpreview.github.io" not in search_js_content
assert "location.search" in search_js_content


class TestCreateGist:
"""Tests for the create_gist function."""
Expand Down Expand Up @@ -728,9 +763,9 @@ def mock_run(*args, **kwargs):

assert result.exit_code == 0
assert (output_dir / "index.html").exists()
# Verify JS was injected (checks for both domains for backwards compatibility)
# Verify gist preview JS was injected
index_content = (output_dir / "index.html").read_text(encoding="utf-8")
assert "gisthost.github.io" in index_content
assert "location.search" in index_content


class TestContinuationLongTexts:
Expand Down