add export-to-pdf button to read offline the current branch
arnaucube git@arnaucube.com
Thu, 03 Sep 2026 16:25:26 +0200
4 files changed,
53 insertions(+),
2 deletions(-)
M
app.js
→
app.js
@@ -67,7 +67,7 @@ historyEmpty: $('#history-empty'), historyToggle: $('#toggle-history'),
attachments: $('#attachments'), uploadFile: $('#upload-file'), fileInput: $('#file-input'), activeConnection: $('#active-connection'), activeModel: $('#active-model'), activeProvider: $('#active-provider'), conversationSearch: $('#conversation-search'), searchResults: $('#search-results'), - treeZoom: $('#tree-zoom'), shortcutsDialog: $('#shortcuts-dialog') + treeZoom: $('#tree-zoom'), shortcutsDialog: $('#shortcuts-dialog'), exportPdf: $('#export-pdf') }; migrateLegacyStorage();@@ -458,6 +458,7 @@ const previousScroll = scroll ? null : { left: window.scrollX, top: window.scrollY };
const path = pathTo(state.activeId); els.welcome.hidden = path.length > 0; els.conversation.hidden = path.length === 0; + els.exportPdf.hidden = path.length === 0; const fragment = document.createDocumentFragment(); for (const node of path) { const article = document.createElement('article');@@ -1567,6 +1568,14 @@ }
function hideSelectionMenu() { els.selectionMenu.hidden = true; selectedText = null; } + function exportCurrentBranch() { + if (!pathTo(state.activeId).length) { showToast('Select a branch to export'); return; } + const previousTitle = document.title; + document.title = `${state.title || 'Study Lab'} — selected branch`; + addEventListener('afterprint', () => { document.title = previousTitle; }, { once: true }); + window.print(); + } + els.composer.addEventListener('submit', event => { event.preventDefault(); if (activeRequest) { activeRequest.abort(); return; }@@ -1650,6 +1659,7 @@ els.activeConnection.addEventListener('click', openSettings);
$('#open-usage').addEventListener('click', openUsage); $('#refresh-actual-spend').addEventListener('click', refreshActualSpend); $('#theme-toggle').addEventListener('click', toggleTheme); + els.exportPdf.addEventListener('click', exportCurrentBranch); $('#toggle-history').addEventListener('click', toggleHistory); els.conversationSearch.addEventListener('input', () => { clearTimeout(searchTimer);
M
index.html
→
index.html
@@ -35,6 +35,7 @@ <span>Study Lab</span>
</a> </div> <div class="top-actions"> + <button id="export-pdf" class="export-button" type="button" title="Export the selected branch as PDF" hidden>Export PDF</button> <button id="install-app" class="install-button" type="button" hidden>Install app</button> <a class="github-button" href="https://github.com/arnaucube/study-lab" target="_blank" rel="noopener noreferrer" aria-label="Open Study Lab on GitHub" title="View source on GitHub"> <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 .7a11.5 11.5 0 0 0-3.64 22.41c.58.1.79-.25.79-.56v-2.23c-3.22.7-3.9-1.37-3.9-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.16.08 1.78 1.2 1.78 1.2 1.04 1.77 2.72 1.26 3.38.96.1-.75.4-1.26.74-1.55-2.57-.29-5.27-1.28-5.27-5.68 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.16 1.18a10.96 10.96 0 0 1 5.76 0c2.19-1.49 3.16-1.18 3.16-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.41-2.71 5.38-5.29 5.67.42.36.79 1.06.79 2.14v3.17c0 .31.21.67.8.56A11.5 11.5 0 0 0 12 .7Z"/></svg>
M
service-worker.js
→
service-worker.js
@@ -1,4 +1,4 @@
-const CACHE_NAME = 'study-lab-shell-v5'; +const CACHE_NAME = 'study-lab-shell-v6'; const APP_SHELL = [ './', './index.html',
M
styles.css
→
styles.css
@@ -60,6 +60,9 @@ .brand-mark { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50% 48% 44% 52%; background: var(--ink); color: white; font-family: Georgia, serif; font-style: italic; }
.history-toggle { display: grid; place-items: center; width: 34px; height: 34px; border: 1px solid var(--line); border-radius: 9px; background: transparent; cursor: pointer; font-size: 1rem; } .history-toggle:hover { background: var(--card); } .top-actions { display: flex; align-items: center; gap: 8px; } +.export-button { height: 34px; padding: 0 12px; border: 1px solid var(--line); border-radius: 999px; background: var(--card); color: var(--ink); cursor: pointer; font-size: .7rem; font-weight: 750; white-space: nowrap; } +.export-button[hidden] { display: none; } +.export-button:hover { border-color: var(--sage); transform: translateY(-1px); } .install-button { height: 34px; padding: 0 12px; border: 0; border-radius: 999px; background: var(--sage-dark); color: #fff; cursor: pointer; font-size: .7rem; font-weight: 750; white-space: nowrap; } .install-button:hover { filter: brightness(1.08); } .github-button { display: grid; place-items: center; width: 34px; height: 34px; border: 1px solid var(--line); border-radius: 50%; color: var(--ink); background: transparent; transition: transform .15s, background .15s; }@@ -455,3 +458,40 @@ :root[data-theme="dark"] .security-callout { background: #30291f; color: #d1b99e; }
:root[data-theme="dark"] .error-text { color: #f0aaa2; background: #3c2422; } :root[data-theme="dark"] .usage-stat.primary { background: #223029; border-color: #35493f; } :root[data-theme="dark"] .usage-note { background: #1a211e; } + +@media print { + @page { margin: 18mm 16mm; } + + :root, :root[data-theme="dark"] { + --ink: #18201d; + --muted: #5f6864; + --paper: #fff; + --card: #fff; + --line: #d9d9d0; + --sage-dark: #4f6d60; + color-scheme: light; + } + + html, body { height: auto; background: #fff !important; } + body { color: #18201d !important; print-color-adjust: exact; -webkit-print-color-adjust: exact; } + .topbar, .history-panel, .map-panel, .expand-map, .composer-wrap, + .selection-menu, dialog, .toast, .answer-actions { display: none !important; } + .app-shell { min-height: 0; padding: 0; } + .workspace { display: block; min-height: 0; margin: 0; } + .welcome { display: none !important; } + .conversation { display: block; width: 100%; margin: 0; padding: 0; } + .message { margin: 0 0 12mm; } + .question-row { break-after: avoid; page-break-after: avoid; } + .question-bubble { background: #e7ebe7 !important; } + .answer { break-before: avoid; page-break-before: avoid; } + .answer-mark { background: #18201d !important; color: #fff !important; } + .answer-body { font-size: 10.5pt; } + .answer-body pre { white-space: pre-wrap; overflow-wrap: anywhere; background: #202724 !important; color: #f3f5f2 !important; } + .answer-body code { background: #eaeae4 !important; } + .answer-body pre code { background: transparent !important; } + .answer-body .table-scroll { overflow: visible; } + .answer-body table { background: #fff !important; } + .answer-body th { background: #e5e9e4 !important; } + .answer-body tbody tr:nth-child(even) { background: #f3f4f1 !important; } + a { color: inherit; } +}