fix(mobile): implement navigation toggle for document viewer
Add mobile-specific navigation pattern to resolve catch-22 UX issue where users couldn't see documents without scrolling but didn't know to scroll. Changes: - Add mobile CSS to toggle between sidebar and document viewer - Add back button to return to document list on mobile - Add document-active body class to manage navigation state - Update GitHub repository links to correct URL 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b076b801c4
commit
41a13ad43e
2 changed files with 137 additions and 31 deletions
|
|
@ -3,8 +3,11 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
|
<meta http-equiv="Pragma" content="no-cache">
|
||||||
|
<meta http-equiv="Expires" content="0">
|
||||||
<title>Framework Documentation | Tractatus AI Safety</title>
|
<title>Framework Documentation | Tractatus AI Safety</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="/favicon-new.svg">
|
||||||
|
|
||||||
<!-- PWA Manifest -->
|
<!-- PWA Manifest -->
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
|
@ -14,9 +17,11 @@
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="Tractatus">
|
<meta name="apple-mobile-web-app-title" content="Tractatus">
|
||||||
<link rel="apple-touch-icon" href="/images/tractatus-icon.svg">
|
<link rel="apple-touch-icon" href="/images/tractatus-icon-new.svg">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/css/fonts.css">
|
||||||
<link rel="stylesheet" href="/css/tailwind.css?v=0.1.0.1760254958072">
|
<link rel="stylesheet" href="/css/tailwind.css?v=0.1.0.1760254958072">
|
||||||
|
<link rel="stylesheet" href="/css/tractatus-theme.min.css">
|
||||||
<style>
|
<style>
|
||||||
html { scroll-behavior: smooth; }
|
html { scroll-behavior: smooth; }
|
||||||
|
|
||||||
|
|
@ -430,6 +435,38 @@
|
||||||
#search-modal-content::-webkit-scrollbar-thumb:hover {
|
#search-modal-content::-webkit-scrollbar-thumb:hover {
|
||||||
background: #94a3b8;
|
background: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile document viewer navigation */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
/* Initially show sidebar, hide main content */
|
||||||
|
aside {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
main#document-viewer-main {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When document is active, hide sidebar and show content */
|
||||||
|
body.document-active aside {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.document-active main#document-viewer-main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Back button styling (mobile only) */
|
||||||
|
#back-to-docs-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
#back-to-docs-btn {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50">
|
<body class="bg-gray-50">
|
||||||
|
|
@ -539,7 +576,7 @@
|
||||||
GitHub
|
GitHub
|
||||||
</h3>
|
</h3>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<a href="https://github.com/AgenticGovernance/tractatus-framework"
|
<a href="https://github.com/AgenticGovernance/tractatus"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition group">
|
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition group">
|
||||||
|
|
@ -551,7 +588,7 @@
|
||||||
<div class="text-xs text-gray-500">Source code, examples & contributions</div>
|
<div class="text-xs text-gray-500">Source code, examples & contributions</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/AgenticGovernance/tractatus-framework#readme"
|
<a href="https://github.com/AgenticGovernance/tractatus#readme"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition group">
|
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition group">
|
||||||
|
|
@ -569,7 +606,17 @@
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="lg:col-span-3">
|
<main id="document-viewer-main" class="lg:col-span-3">
|
||||||
|
<!-- Back to Documents Button (Mobile Only) -->
|
||||||
|
<button id="back-to-docs-btn"
|
||||||
|
class="mb-4 items-center gap-2 px-4 py-2 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 transition"
|
||||||
|
aria-label="Back to documents list">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium text-gray-700">Back to Documents</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div id="document-content" class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
|
<div id="document-content" class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
|
||||||
<div class="text-center py-12">
|
<div class="text-center py-12">
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -811,13 +858,13 @@
|
||||||
<!-- Version Management & PWA -->
|
<!-- Version Management & PWA -->
|
||||||
<script src="/js/version-manager.js"></script>
|
<script src="/js/version-manager.js"></script>
|
||||||
|
|
||||||
<script src="/js/components/document-cards.js?v=0.1.0.1760254958072"></script>
|
<script src="/js/components/document-cards.js?v=0.1.0.1760825529624"></script>
|
||||||
<script src="/js/docs-app.js?v=0.1.0.1760254958072"></script>
|
<script src="/js/docs-app.js?v=0.1.0.1760825529624"></script>
|
||||||
<script src="/js/docs-search-enhanced.js?v=0.1.0.1760254958072"></script>
|
<script src="/js/docs-search-enhanced.js?v=0.1.0.1760825529624"></script>
|
||||||
|
|
||||||
<!-- Internationalization -->
|
<!-- Internationalization -->
|
||||||
<script src="/js/i18n-simple.js?v=0.1.0.1760643941"></script>
|
<script src="/js/i18n-simple.js?v=1760818106"></script>
|
||||||
<script src="/js/components/language-selector.js?v=0.1.0.1760643941"></script>
|
<script src="/js/components/language-selector.js?v=1760818106"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -133,15 +133,23 @@ function groupDocuments(docs) {
|
||||||
function renderDocLink(doc, isHighlighted = false) {
|
function renderDocLink(doc, isHighlighted = false) {
|
||||||
const highlightClass = isHighlighted ? 'text-blue-700 bg-blue-50 border border-blue-200' : '';
|
const highlightClass = isHighlighted ? 'text-blue-700 bg-blue-50 border border-blue-200' : '';
|
||||||
|
|
||||||
// Determine if PDF download is available
|
// Determine if PDF download is available and get PDF path
|
||||||
// Check if metadata indicates PDF existence (presence in slugs that typically have PDFs)
|
// First check if document has explicit download_formats.pdf
|
||||||
// Documents with download_formats.pdf, markdown files, or most docs should have PDFs
|
let pdfPath = null;
|
||||||
// API Reference docs and technical diagrams might not have PDFs
|
let hasPDF = false;
|
||||||
const hasPDF = !doc.slug.includes('api-reference-complete') &&
|
|
||||||
!doc.slug.includes('openapi-specification') &&
|
if (doc.download_formats && doc.download_formats.pdf) {
|
||||||
!doc.slug.includes('api-javascript-examples') &&
|
pdfPath = doc.download_formats.pdf;
|
||||||
!doc.slug.includes('api-python-examples') &&
|
hasPDF = true;
|
||||||
!doc.slug.includes('technical-architecture-diagram');
|
} else if (!doc.slug.includes('api-reference-complete') &&
|
||||||
|
!doc.slug.includes('openapi-specification') &&
|
||||||
|
!doc.slug.includes('api-javascript-examples') &&
|
||||||
|
!doc.slug.includes('api-python-examples') &&
|
||||||
|
!doc.slug.includes('technical-architecture-diagram')) {
|
||||||
|
// Fallback to default /downloads/ path for documents that typically have PDFs
|
||||||
|
pdfPath = `/downloads/${doc.slug}.pdf`;
|
||||||
|
hasPDF = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Add download button styling
|
// Add download button styling
|
||||||
const paddingClass = hasPDF ? 'pr-10' : 'pr-3';
|
const paddingClass = hasPDF ? 'pr-10' : 'pr-3';
|
||||||
|
|
@ -153,7 +161,7 @@ function renderDocLink(doc, isHighlighted = false) {
|
||||||
<div class="font-medium text-gray-900">${doc.title}</div>
|
<div class="font-medium text-gray-900">${doc.title}</div>
|
||||||
</button>
|
</button>
|
||||||
${hasPDF ? `
|
${hasPDF ? `
|
||||||
<a href="/downloads/${doc.slug}.pdf"
|
<a href="${pdfPath}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="doc-download-link"
|
class="doc-download-link"
|
||||||
|
|
@ -308,12 +316,37 @@ async function loadDocuments() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for URL parameter to auto-expand category
|
// Check for URL parameter to auto-load document or category
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const docParam = urlParams.get('doc');
|
||||||
const categoryParam = urlParams.get('category');
|
const categoryParam = urlParams.get('category');
|
||||||
|
|
||||||
// Auto-expand and navigate to category from URL parameter
|
// Priority 1: Load specific document by slug if provided
|
||||||
if (categoryParam && grouped[categoryParam] && grouped[categoryParam].length > 0) {
|
if (docParam) {
|
||||||
|
const doc = documents.find(d => d.slug === docParam);
|
||||||
|
if (doc) {
|
||||||
|
// Find and expand the category containing this document
|
||||||
|
const docCategory = categorizeDocument(doc);
|
||||||
|
if (docCategory) {
|
||||||
|
const categoryDocsEl = listEl.querySelector(`.category-docs[data-category="${docCategory}"]`);
|
||||||
|
const categoryArrowEl = listEl.querySelector(`.category-toggle[data-category="${docCategory}"] .category-arrow`);
|
||||||
|
|
||||||
|
if (categoryDocsEl) {
|
||||||
|
categoryDocsEl.style.display = 'block';
|
||||||
|
if (categoryArrowEl) {
|
||||||
|
categoryArrowEl.style.transform = 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the requested document
|
||||||
|
loadDocument(docParam);
|
||||||
|
} else {
|
||||||
|
console.warn(`Document with slug "${docParam}" not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Priority 2: Load category if provided but no specific document
|
||||||
|
else if (categoryParam && grouped[categoryParam] && grouped[categoryParam].length > 0) {
|
||||||
// Expand the specified category
|
// Expand the specified category
|
||||||
const categoryDocsEl = listEl.querySelector(`.category-docs[data-category="${categoryParam}"]`);
|
const categoryDocsEl = listEl.querySelector(`.category-docs[data-category="${categoryParam}"]`);
|
||||||
const categoryArrowEl = listEl.querySelector(`.category-toggle[data-category="${categoryParam}"] .category-arrow`);
|
const categoryArrowEl = listEl.querySelector(`.category-toggle[data-category="${categoryParam}"] .category-arrow`);
|
||||||
|
|
@ -330,7 +363,9 @@ async function loadDocuments() {
|
||||||
if (firstDoc) {
|
if (firstDoc) {
|
||||||
loadDocument(firstDoc.slug);
|
loadDocument(firstDoc.slug);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
// Priority 3: Default behavior
|
||||||
|
else {
|
||||||
// Default: Auto-load first document in "Getting Started" category (order: 1)
|
// Default: Auto-load first document in "Getting Started" category (order: 1)
|
||||||
const gettingStartedDocs = grouped['getting-started'] || [];
|
const gettingStartedDocs = grouped['getting-started'] || [];
|
||||||
if (gettingStartedDocs.length > 0) {
|
if (gettingStartedDocs.length > 0) {
|
||||||
|
|
@ -403,12 +438,21 @@ async function loadDocument(slug) {
|
||||||
// Fallback to traditional view with header
|
// Fallback to traditional view with header
|
||||||
const hasToC = currentDocument.toc && currentDocument.toc.length > 0;
|
const hasToC = currentDocument.toc && currentDocument.toc.length > 0;
|
||||||
|
|
||||||
// Check if PDF is available (same logic as sidebar)
|
// Check if PDF is available and get PDF path
|
||||||
const hasPDF = !currentDocument.slug.includes('api-reference-complete') &&
|
let pdfPath = null;
|
||||||
!currentDocument.slug.includes('openapi-specification') &&
|
let hasPDF = false;
|
||||||
!currentDocument.slug.includes('api-javascript-examples') &&
|
|
||||||
!currentDocument.slug.includes('api-python-examples') &&
|
if (currentDocument.download_formats && currentDocument.download_formats.pdf) {
|
||||||
!currentDocument.slug.includes('technical-architecture-diagram');
|
pdfPath = currentDocument.download_formats.pdf;
|
||||||
|
hasPDF = true;
|
||||||
|
} else if (!currentDocument.slug.includes('api-reference-complete') &&
|
||||||
|
!currentDocument.slug.includes('openapi-specification') &&
|
||||||
|
!currentDocument.slug.includes('api-javascript-examples') &&
|
||||||
|
!currentDocument.slug.includes('api-python-examples') &&
|
||||||
|
!currentDocument.slug.includes('technical-architecture-diagram')) {
|
||||||
|
pdfPath = `/downloads/${currentDocument.slug}.pdf`;
|
||||||
|
hasPDF = true;
|
||||||
|
}
|
||||||
|
|
||||||
let headerHTML = `
|
let headerHTML = `
|
||||||
<div class="flex items-center justify-between mb-6 pb-4 border-b border-gray-200">
|
<div class="flex items-center justify-between mb-6 pb-4 border-b border-gray-200">
|
||||||
|
|
@ -425,7 +469,7 @@ async function loadDocument(slug) {
|
||||||
</button>
|
</button>
|
||||||
` : ''}
|
` : ''}
|
||||||
${hasPDF ? `
|
${hasPDF ? `
|
||||||
<a href="/downloads/${currentDocument.slug}.pdf"
|
<a href="${pdfPath}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="p-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded transition"
|
class="p-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded transition"
|
||||||
|
|
@ -462,6 +506,9 @@ async function loadDocument(slug) {
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
// Mobile navigation: Add document-active class to show document view
|
||||||
|
document.body.classList.add('document-active');
|
||||||
|
|
||||||
// Scroll to top
|
// Scroll to top
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
|
||||||
|
|
@ -564,3 +611,15 @@ if (modal) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile navigation: Back to documents button
|
||||||
|
const backButton = document.getElementById('back-to-docs-btn');
|
||||||
|
if (backButton) {
|
||||||
|
backButton.addEventListener('click', function() {
|
||||||
|
// Remove document-active class to show sidebar
|
||||||
|
document.body.classList.remove('document-active');
|
||||||
|
|
||||||
|
// Scroll to top
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue