mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-03-06 15:38:33 -05:00
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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2796,13 +2796,30 @@ html.slider-active {
|
||||
/*============*/
|
||||
|
||||
@media print {
|
||||
/* This hides the headers and footers in the printed document on Chrome */
|
||||
/* Supported since Chrome 131: https://developer.chrome.com/release-notes/131#page_margin_boxes */
|
||||
|
||||
@page {
|
||||
/* Firefox and Safari do not support those yet */
|
||||
/* See: https://developer.mozilla.org/en-US/docs/Web/CSS/@page#browser_compatibility */
|
||||
|
||||
@top-left { content: ''; }
|
||||
|
||||
@top-right { content: ''; }
|
||||
|
||||
@bottom-left { content: ''; }
|
||||
|
||||
@bottom-right { content: ''; }
|
||||
}
|
||||
|
||||
.header, .aside,
|
||||
.nav_menu, .day,
|
||||
.flux_header,
|
||||
.flux_content .bottom,
|
||||
.pagination,
|
||||
#stream-footer,
|
||||
#nav_entries {
|
||||
#nav_entries,
|
||||
.dropdown-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -2796,13 +2796,30 @@ html.slider-active {
|
||||
/*============*/
|
||||
|
||||
@media print {
|
||||
/* This hides the headers and footers in the printed document on Chrome */
|
||||
/* Supported since Chrome 131: https://developer.chrome.com/release-notes/131#page_margin_boxes */
|
||||
|
||||
@page {
|
||||
/* Firefox and Safari do not support those yet */
|
||||
/* See: https://developer.mozilla.org/en-US/docs/Web/CSS/@page#browser_compatibility */
|
||||
|
||||
@top-left { content: ''; }
|
||||
|
||||
@top-right { content: ''; }
|
||||
|
||||
@bottom-left { content: ''; }
|
||||
|
||||
@bottom-right { content: ''; }
|
||||
}
|
||||
|
||||
.header, .aside,
|
||||
.nav_menu, .day,
|
||||
.flux_header,
|
||||
.flux_content .bottom,
|
||||
.pagination,
|
||||
#stream-footer,
|
||||
#nav_entries {
|
||||
#nav_entries,
|
||||
.dropdown-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user