diff options
| author | 2025-08-06 21:49:13 +0200 | |
|---|---|---|
| committer | 2025-08-06 21:49:13 +0200 | |
| commit | 149136fbe252cca4cb8dcdb463df135094bc0f87 (patch) | |
| tree | 9ca659760b0be19b71bff5c41cdc351a376e9cab /p/scripts | |
| parent | 9faf2c1fa3c8c3ed82ff4c95733a018d0f9977a7 (diff) | |
Improve sharing via Print (#7728)
List of changes:
* The temporary document for printing is now in an `<iframe>` instead of a new tab
* The whole `<head>` element is copied to the temporary document, except for `<script>` tags to copy over the `<meta>` tags as well
* URLs that contain the instance base URL are now removed from the printed PDF
* The saved filename (PDF) will now default to the article title
* `<details>` is auto expanded
* Styling:
* The main document's `<html>` class is copied over to preserve some styling that might use those classes
* Instead of writing `content_el.innerHTML` to the temporary document, `content_el.outerHTML` is now written instead to apply the styles that select `.content`
* `.dropdown-menu` is now hidden in the printed document, because it can't be expanded anyway
* Headers and footers are hidden in the printed document
* The printed document will now display correctly all the time, by waiting for it to load before calling `print()`
* Before, the stylesheets might've not finished loading and the document was broken
* Better browser support on mobile for this feature
* Before, the document would fail to print on Chrome Mobile
Tested on:
* Firefox - both desktop and mobile, works ✅
* Chrome - both desktop and mobile, works ✅
* Opera - desktop, works (same as Chrome) ✅
* Brave - both desktop and mobile (same as Chrome), works ✅
* Safari - both desktop and mobile, works✅
* Microsoft Edge - both desktop and mobile, works ✅
* GNOME Web - desktop, works ✅
* SeaMonkey - desktop, works ✅
Known issues:
* Images may not finish loading the first time the print dialog is opened
TODO:
* [x] Test on Safari
* [x] Try to fix GNOME Web
Diffstat (limited to 'p/scripts')
| -rw-r--r-- | p/scripts/main.js | 88 |
1 files changed, 75 insertions, 13 deletions
diff --git a/p/scripts/main.js b/p/scripts/main.js index d1752624e..1e35c84fa 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -37,6 +37,7 @@ function xmlHttpRequestJson(req) { // <Global context> /* eslint-disable no-var */ var context; +var prevTitle; /* eslint-enable no-var */ (function parseJsonVars() { @@ -155,9 +156,10 @@ function incUnreadsFeed(article, feed_id, nb) { } } - let isCurrentView = false; // Update unread: title - document.title = document.title.replace(/^((?:\([\s0-9]+\) )?)/, function (m, p1) { + let isCurrentView = false; + const currentTitle = prevTitle || document.title; + const newTitle = currentTitle.replace(/^((?:\([\s0-9]+\) )?)/, function (m, p1) { const feed = document.getElementById(feed_id); if (article || (feed && feed.closest('.active'))) { isCurrentView = true; @@ -169,6 +171,11 @@ function incUnreadsFeed(article, feed_id, nb) { return p1; } }); + if (prevTitle) { + prevTitle = newTitle; + } else { + document.title = newTitle; + } return isCurrentView; } @@ -1235,24 +1242,79 @@ function init_stream(stream) { el = ev.target.closest('.item.share > button[data-type="print"]'); if (el) { // Print - const tmp_window = window.open(); - for (let i = 0; i < document.styleSheets.length; i++) { - tmp_window.document.writeln('<link href="' + document.styleSheets[i].href + '" rel="stylesheet" type="text/css" />'); - } + const html = document.documentElement; + const head = document.head.cloneNode(true); + head.querySelectorAll('script').forEach(js => js.remove()); + const flux_content = el.closest('.flux_content'); let content_el = null; if (flux_content) { - content_el = el.closest('.flux_content').querySelector('.content'); + content_el = el.closest('.flux_content').querySelector('.content').cloneNode(true); } if (content_el === null) { - content_el = el.closest('.flux').querySelector('.flux_content .content'); + content_el = el.closest('.flux').querySelector('.flux_content .content').cloneNode(true); } + + content_el.querySelectorAll('a').forEach(link => { + // Avoid leaking the instance URL in PDFs + if (link.href.startsWith(location.origin)) { + link.removeAttribute('href'); + } + }); + + content_el.querySelectorAll('details').forEach(el => el.setAttribute('open', 'open')); + + const articleTitle = content_el.querySelector('.title a').innerText; + prevTitle = document.title; + + // Chrome uses the parent's title to get the PDF save filename + document.title = articleTitle; + + // Firefox uses the iframe's title to get the PDF save filename + // Note: Firefox Mobile saves PDFs with a filename that looks like: `temp[19 random digits].PDF` regardless of title + head.querySelector('title').innerText = articleTitle; + loadLazyImages(content_el); - tmp_window.document.writeln(content_el.innerHTML); - tmp_window.document.close(); - tmp_window.focus(); - tmp_window.print(); - tmp_window.close(); + + const print_frame = document.createElement('iframe'); + print_frame.style.display = 'none'; + print_frame.srcdoc = ` + <!DOCTYPE html> + <html class="${html.getAttribute('class')}"> + <head> + ${head.innerHTML} + </head> + <body> + ${content_el.outerHTML} + </body> + </html> + `; + document.body.prepend(print_frame); + + function afterPrint() { + print_frame.remove(); + document.title = prevTitle; + prevTitle = ''; + + window.removeEventListener('focus', afterPrint); + } + + print_frame.onload = () => { + const tmp_window = print_frame.contentWindow; + + // Needed for Chrome + tmp_window.matchMedia('print').onchange = (e) => { + // UA check is needed to not trigger on Chrome Mobile + if (!e.matches && !navigator.userAgent.includes('Mobi')) { + afterPrint(); + } + }; + tmp_window.print(); + }; + + // Needed for Firefox and Chrome Mobile + window.addEventListener('focus', afterPrint); + return false; } |
