I’ll build this as a focused print helper chunk for RX Theme: safe, dependency-free vanilla JavaScript, advanced enough for WordPress pages, but not dangerous to performance
I’ll build this as a focused print helper chunk for RX Theme: safe, dependency-free vanilla JavaScript, advanced enough for WordPress pages, but not dangerous to performance.
Create this file:
assets/static-js/static-chunks-js/chunk-020-print.js
Paste this full advanced print-system code:
/*!
* RX Theme - Advanced Print Chunk
* File: assets/static-js/static-chunks-js/chunk-020-print.js
* Purpose: Smart article/page printing for RX Theme
* Author: RX Theme
*/
(function () {
'use strict';
/**
* RX Advanced Print System
*
* Features:
* - Smart print button support
* - Article-only print mode
* - Before/after print lifecycle
* - Lazy image preparation before printing
* - Expand accordions/details before print
* - Add printed source URL
* - Add print date
* - Hide unwanted UI
* - Improve table print layout
* - Add readable link URLs after links
* - Optional keyboard shortcut Ctrl/Cmd + P hook
* - Custom events for future RX Theme integration
*/
const RXPrint = {
version: '1.0.0',
selectors: {
printButton: '[data-rx-print], .rx-print-button, .js-rx-print',
printableArea: '[data-rx-print-area], .rx-print-area, article, main',
noPrint: '[data-no-print], .no-print, .rx-no-print',
expandOnPrint: '[data-rx-expand-print], details',
lazyImage: 'img[loading="lazy"], img[data-src], img[data-lazy-src]',
table: 'table',
link: 'a[href]',
heading: 'h1, h2, h3',
iframe: 'iframe',
ad: '.ad, .ads, .advertisement, .rx-ad, [data-ad], ins.adsbygoogle',
share: '.share, .sharing, .social-share, .rx-share',
comments: '#comments, .comments-area, .rx-comments',
navigation: 'nav, .navigation, .pagination, .breadcrumbs',
sidebar: 'aside, .sidebar, .widget-area'
},
options: {
debug: false,
articleOnly: true,
addSourceUrl: true,
addPrintedDate: true,
addCopyright: true,
expandDetails: true,
prepareImages: true,
addLinkUrls: true,
removeEmptyBlocks: true,
hideAds: true,
hideSidebars: true,
hideNavigation: false,
hideComments: true,
printDelay: 250,
imageTimeout: 2500,
siteName: document.title || 'RX Theme',
copyrightText: 'Printed from RX Theme',
sourceLabel: 'Source',
dateLabel: 'Printed on'
},
state: {
initialized: false,
printing: false,
originalTitle: document.title,
openedDetails: [],
generatedNodes: [],
hiddenNodes: [],
processedLinks: [],
processedTables: [],
processedImages: []
},
init() {
if (this.state.initialized) return;
this.mergeUserConfig();
this.bindPrintButtons();
this.bindPrintEvents();
this.bindKeyboardShortcut();
this.injectPrintStyles();
this.markPageReady();
this.state.initialized = true;
this.log('RX Print initialized');
},
mergeUserConfig() {
if (window.rxPrintConfig && typeof window.rxPrintConfig === 'object') {
this.options = {
...this.options,
...window.rxPrintConfig
};
}
},
log(...args) {
if (this.options.debug) {
console.log('[RX Print]', ...args);
}
},
emit(name, detail = {}) {
const event = new CustomEvent(`rx:print:${name}`, {
detail: {
version: this.version,
...detail
}
});
document.dispatchEvent(event);
},
bindPrintButtons() {
const buttons = document.querySelectorAll(this.selectors.printButton);
buttons.forEach((button) => {
if (button.dataset.rxPrintBound === '1') return;
button.dataset.rxPrintBound = '1';
if (!button.getAttribute('type') && button.tagName === 'BUTTON') {
button.setAttribute('type', 'button');
}
button.addEventListener('click', (event) => {
event.preventDefault();
const targetSelector = button.getAttribute('data-rx-print-target');
this.print({
targetSelector,
button
});
});
});
},
bindPrintEvents() {
window.addEventListener('beforeprint', () => {
this.beforePrint();
});
window.addEventListener('afterprint', () => {
this.afterPrint();
});
},
bindKeyboardShortcut() {
document.addEventListener(
'keydown',
(event) => {
const isPrintShortcut =
(event.ctrlKey || event.metaKey) &&
String(event.key).toLowerCase() === 'p';
if (!isPrintShortcut) return;
this.emit('shortcut', {
originalEvent: event
});
/**
* Do not prevent default.
* Browser print dialog should remain natural.
* beforeprint event will still prepare the page.
*/
},
{ passive: true }
);
},
async print(context = {}) {
if (this.state.printing) return;
this.state.printing = true;
this.emit('requested', context);
try {
await this.prepare(context);
setTimeout(() => {
window.print();
}, this.options.printDelay);
} catch (error) {
console.error('[RX Print] Print preparation failed:', error);
window.print();
} finally {
setTimeout(() => {
this.state.printing = false;
}, 1000);
}
},
async prepare(context = {}) {
this.emit('prepare-start', context);
this.preparePrintArea(context);
this.hideUnwantedElements();
this.expandPrintableDetails();
this.prepareTables();
this.prepareLinks();
this.prepareIframes();
this.removeEmptyBlocks();
if (this.options.prepareImages) {
await this.prepareImages();
}
this.addPrintMeta();
this.updateDocumentTitle();
this.emit('prepare-end', context);
},
beforePrint() {
this.emit('before');
/**
* Browser shortcut printing may not call print().
* So prepare again safely.
*/
if (!document.documentElement.classList.contains('rx-print-prepared')) {
this.prepare();
}
document.documentElement.classList.add('rx-is-printing');
},
afterPrint() {
this.emit('after');
this.cleanup();
document.documentElement.classList.remove('rx-is-printing');
document.documentElement.classList.remove('rx-print-prepared');
document.title = this.state.originalTitle;
this.state.printing = false;
},
preparePrintArea(context = {}) {
const selector = context.targetSelector;
let area = null;
if (selector) {
area = document.querySelector(selector);
}
if (!area) {
area = document.querySelector(this.selectors.printableArea);
}
if (!area) return;
document.body.classList.add('rx-print-article-mode');
area.classList.add('rx-print-active-area');
area.setAttribute('data-rx-print-active', 'true');
this.emit('area-detected', {
area
});
},
hideUnwantedElements() {
const selectors = [
this.selectors.noPrint
];
if (this.options.hideAds) selectors.push(this.selectors.ad);
if (this.options.hideComments) selectors.push(this.selectors.comments);
if (this.options.hideSidebars) selectors.push(this.selectors.sidebar);
if (this.options.hideNavigation) selectors.push(this.selectors.navigation);
const combinedSelector = selectors.join(',');
document.querySelectorAll(combinedSelector).forEach((node) => {
if (node.dataset.rxPrintHidden === '1') return;
node.dataset.rxPrintHidden = '1';
node.classList.add('rx-print-force-hide');
this.state.hiddenNodes.push(node);
});
},
expandPrintableDetails() {
if (!this.options.expandDetails) return;
const details = document.querySelectorAll(this.selectors.expandOnPrint);
details.forEach((node) => {
if (node.tagName === 'DETAILS') {
if (!node.open) {
node.open = true;
node.dataset.rxPrintWasClosed = '1';
this.state.openedDetails.push(node);
}
}
node.classList.add('rx-print-expanded');
});
},
prepareTables() {
const tables = document.querySelectorAll(this.selectors.table);
tables.forEach((table, index) => {
if (table.dataset.rxPrintTableReady === '1') return;
table.dataset.rxPrintTableReady = '1';
table.classList.add('rx-print-table');
if (!table.parentElement.classList.contains('rx-print-table-wrap')) {
const wrapper = document.createElement('div');
wrapper.className = 'rx-print-table-wrap';
wrapper.dataset.rxGeneratedPrintNode = '1';
table.parentNode.insertBefore(wrapper, table);
wrapper.appendChild(table);
this.state.generatedNodes.push(wrapper);
}
const caption = table.querySelector('caption');
if (!caption) {
table.setAttribute('aria-label', table.getAttribute('aria-label') || `Table ${index + 1}`);
}
this.state.processedTables.push(table);
});
},
prepareLinks() {
if (!this.options.addLinkUrls) return;
const links = document.querySelectorAll(this.selectors.link);
links.forEach((link) => {
if (link.dataset.rxPrintLinkReady === '1') return;
const href = link.getAttribute('href');
if (!href) return;
if (href.startsWith('#')) return;
if (href.startsWith('javascript:')) return;
if (href.startsWith('mailto:')) return;
if (href.startsWith('tel:')) return;
link.dataset.rxPrintLinkReady = '1';
try {
const absoluteUrl = new URL(href, window.location.href).href;
link.setAttribute('data-rx-print-url', absoluteUrl);
link.classList.add('rx-print-link-with-url');
this.state.processedLinks.push(link);
} catch (error) {
this.log('Invalid link skipped:', href);
}
});
},
prepareIframes() {
const iframes = document.querySelectorAll(this.selectors.iframe);
iframes.forEach((iframe) => {
if (iframe.dataset.rxPrintIframeReady === '1') return;
iframe.dataset.rxPrintIframeReady = '1';
const src = iframe.getAttribute('src');
if (!src) return;
const fallback = document.createElement('p');
fallback.className = 'rx-print-iframe-source';
fallback.dataset.rxGeneratedPrintNode = '1';
try {
fallback.textContent = `Embedded content: ${new URL(src, window.location.href).href}`;
} catch (error) {
fallback.textContent = `Embedded content: ${src}`;
}
iframe.insertAdjacentElement('afterend', fallback);
this.state.generatedNodes.push(fallback);
});
},
async prepareImages() {
const images = Array.from(document.querySelectorAll(this.selectors.lazyImage));
if (!images.length) return;
const tasks = images.map((img) => this.prepareSingleImage(img));
await Promise.race([
Promise.allSettled(tasks),
this.wait(this.options.imageTimeout)
]);
},
prepareSingleImage(img) {
return new Promise((resolve) => {
if (!img || img.dataset.rxPrintImageReady === '1') {
resolve();
return;
}
const dataSrc = img.getAttribute('data-src') || img.getAttribute('data-lazy-src');
if (dataSrc && !img.getAttribute('src')) {
img.setAttribute('src', dataSrc);
}
img.loading = 'eager';
img.decoding = 'sync';
img.dataset.rxPrintImageReady = '1';
img.classList.add('rx-print-image-ready');
this.state.processedImages.push(img);
if (img.complete) {
resolve();
return;
}
const done = () => resolve();
img.addEventListener('load', done, { once: true });
img.addEventListener('error', done, { once: true });
setTimeout(done, this.options.imageTimeout);
});
},
removeEmptyBlocks() {
if (!this.options.removeEmptyBlocks) return;
const blocks = document.querySelectorAll(
'.rx-print-active-area p, .rx-print-active-area div, article p, article div'
);
blocks.forEach((block) => {
if (block.children.length > 0) return;
const text = block.textContent.replace(/\s+/g, '').trim();
if (!text) {
block.classList.add('rx-print-empty-block');
}
});
},
addPrintMeta() {
const activeArea =
document.querySelector('.rx-print-active-area') ||
document.querySelector('article') ||
document.querySelector('main');
if (!activeArea) return;
if (activeArea.querySelector('.rx-print-meta-box')) return;
const meta = document.createElement('div');
meta.className = 'rx-print-meta-box';
meta.dataset.rxGeneratedPrintNode = '1';
const items = [];
if (this.options.addPrintedDate) {
const date = new Date();
items.push(
`${this.escapeText(this.options.dateLabel)}: ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
);
}
if (this.options.addSourceUrl) {
items.push(`${this.escapeText(this.options.sourceLabel)}: ${window.location.href}`);
}
if (this.options.addCopyright) {
items.push(this.options.copyrightText);
}
meta.innerHTML = items
.map((item) => `<p>${this.escapeText(item)}</p>`)
.join('');
activeArea.appendChild(meta);
this.state.generatedNodes.push(meta);
},
updateDocumentTitle() {
const heading = document.querySelector('h1');
const siteName = this.options.siteName || 'RX Theme';
if (heading && heading.textContent.trim()) {
document.title = `${heading.textContent.trim()} - ${siteName}`;
}
},
cleanup() {
this.state.openedDetails.forEach((details) => {
if (details.dataset.rxPrintWasClosed === '1') {
details.open = false;
delete details.dataset.rxPrintWasClosed;
}
});
this.state.hiddenNodes.forEach((node) => {
node.classList.remove('rx-print-force-hide');
delete node.dataset.rxPrintHidden;
});
this.state.processedLinks.forEach((link) => {
link.classList.remove('rx-print-link-with-url');
});
document.querySelectorAll('.rx-print-active-area').forEach((node) => {
node.classList.remove('rx-print-active-area');
node.removeAttribute('data-rx-print-active');
});
document.querySelectorAll('.rx-print-expanded').forEach((node) => {
node.classList.remove('rx-print-expanded');
});
document.querySelectorAll('.rx-print-empty-block').forEach((node) => {
node.classList.remove('rx-print-empty-block');
});
this.state.generatedNodes.forEach((node) => {
if (!node || !node.parentNode) return;
/**
* If wrapper contains a table, unwrap safely.
*/
if (node.classList.contains('rx-print-table-wrap')) {
const table = node.querySelector('table');
if (table && node.parentNode) {
node.parentNode.insertBefore(table, node);
}
}
node.remove();
});
document.body.classList.remove('rx-print-article-mode');
this.state.openedDetails = [];
this.state.generatedNodes = [];
this.state.hiddenNodes = [];
this.state.processedLinks = [];
this.state.processedTables = [];
this.state.processedImages = [];
},
injectPrintStyles() {
if (document.getElementById('rx-print-dynamic-style')) return;
const style = document.createElement('style');
style.id = 'rx-print-dynamic-style';
style.textContent = `
@media print {
html.rx-is-printing,
html.rx-is-printing body {
background: #ffffff !important;
}
body.rx-print-article-mode > *:not(.rx-print-active-area):not(script):not(style) {
display: none !important;
}
.rx-print-force-hide,
.rx-print-empty-block,
[data-no-print],
.no-print,
.rx-no-print {
display: none !important;
}
.rx-print-active-area {
display: block !important;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background: #ffffff !important;
color: #000000 !important;
box-shadow: none !important;
}
.rx-print-active-area *,
article *,
main * {
box-shadow: none !important;
text-shadow: none !important;
}
.rx-print-active-area h1,
.rx-print-active-area h2,
.rx-print-active-area h3,
article h1,
article h2,
article h3 {
color: #000000 !important;
page-break-after: avoid;
break-after: avoid;
}
.rx-print-active-area p,
article p,
main p {
orphans: 3;
widows: 3;
}
.rx-print-active-area img,
article img,
main img {
max-width: 100% !important;
height: auto !important;
page-break-inside: avoid;
break-inside: avoid;
}
.rx-print-table-wrap {
width: 100% !important;
overflow: visible !important;
page-break-inside: avoid;
break-inside: avoid;
}
table.rx-print-table {
width: 100% !important;
border-collapse: collapse !important;
font-size: 12px !important;
page-break-inside: auto;
}
table.rx-print-table th,
table.rx-print-table td {
border: 1px solid #000000 !important;
padding: 6px !important;
vertical-align: top !important;
}
table.rx-print-table thead {
display: table-header-group;
}
table.rx-print-table tfoot {
display: table-footer-group;
}
.rx-print-link-with-url::after {
content: " (" attr(data-rx-print-url) ")";
font-size: 90%;
word-break: break-all;
}
.rx-print-meta-box {
margin-top: 32px !important;
padding-top: 12px !important;
border-top: 1px solid #000000 !important;
font-size: 12px !important;
color: #000000 !important;
}
.rx-print-meta-box p {
margin: 4px 0 !important;
}
.rx-print-iframe-source {
display: block !important;
font-size: 12px !important;
border: 1px solid #000000 !important;
padding: 8px !important;
word-break: break-all !important;
}
iframe,
video,
audio,
canvas,
svg.rx-no-print-svg {
max-width: 100% !important;
}
a {
color: #000000 !important;
text-decoration: underline !important;
}
@page {
margin: 14mm;
}
}
`;
document.head.appendChild(style);
},
markPageReady() {
document.documentElement.classList.add('rx-print-js-ready');
},
wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
},
escapeText(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
};
window.RXPrint = RXPrint;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => RXPrint.init(), {
once: true
});
} else {
RXPrint.init();
}
})();
Use print button like this anywhere in your theme:
<button type="button" class="rx-print-button" data-rx-print>
Print Article
</button>
For exact article area:
<article class="rx-print-area">
<!-- article content -->
</article>
For elements you never want in print:
<div class="rx-no-print">
This will not print
</div>
Optional config before loading the chunk:
<script>
window.rxPrintConfig = {
debug: false,
articleOnly: true,
addSourceUrl: true,
addPrintedDate: true,
addCopyright: true,
copyrightText: 'Printed from rxharun.com'
};
</script>
Best enqueue name for WordPress:
wp_enqueue_script(
'rx-theme-print-chunk',
get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-020-print.js',
array(),
RX_THEME_VERSION,
true
);
This file should stay as a separate print chunk, not inside your main JavaScript, because print logic is not needed on every millisecond of first page load.
Create this file:
assets/static-js/static-chunks-js/chunk-020-print.js
Paste this full advanced print-system code:
/*!
* RX Theme - Advanced Print Chunk
* File: assets/static-js/static-chunks-js/chunk-020-print.js
* Purpose: Smart article/page printing for RX Theme
* Author: RX Theme
*/
(function () {
'use strict';
/**
* RX Advanced Print System
*
* Features:
* - Smart print button support
* - Article-only print mode
* - Before/after print lifecycle
* - Lazy image preparation before printing
* - Expand accordions/details before print
* - Add printed source URL
* - Add print date
* - Hide unwanted UI
* - Improve table print layout
* - Add readable link URLs after links
* - Optional keyboard shortcut Ctrl/Cmd + P hook
* - Custom events for future RX Theme integration
*/
const RXPrint = {
version: '1.0.0',
selectors: {
printButton: '[data-rx-print], .rx-print-button, .js-rx-print',
printableArea: '[data-rx-print-area], .rx-print-area, article, main',
noPrint: '[data-no-print], .no-print, .rx-no-print',
expandOnPrint: '[data-rx-expand-print], details',
lazyImage: 'img[loading="lazy"], img[data-src], img[data-lazy-src]',
table: 'table',
link: 'a[href]',
heading: 'h1, h2, h3',
iframe: 'iframe',
ad: '.ad, .ads, .advertisement, .rx-ad, [data-ad], ins.adsbygoogle',
share: '.share, .sharing, .social-share, .rx-share',
comments: '#comments, .comments-area, .rx-comments',
navigation: 'nav, .navigation, .pagination, .breadcrumbs',
sidebar: 'aside, .sidebar, .widget-area'
},
options: {
debug: false,
articleOnly: true,
addSourceUrl: true,
addPrintedDate: true,
addCopyright: true,
expandDetails: true,
prepareImages: true,
addLinkUrls: true,
removeEmptyBlocks: true,
hideAds: true,
hideSidebars: true,
hideNavigation: false,
hideComments: true,
printDelay: 250,
imageTimeout: 2500,
siteName: document.title || 'RX Theme',
copyrightText: 'Printed from RX Theme',
sourceLabel: 'Source',
dateLabel: 'Printed on'
},
state: {
initialized: false,
printing: false,
originalTitle: document.title,
openedDetails: [],
generatedNodes: [],
hiddenNodes: [],
processedLinks: [],
processedTables: [],
processedImages: []
},
init() {
if (this.state.initialized) return;
this.mergeUserConfig();
this.bindPrintButtons();
this.bindPrintEvents();
this.bindKeyboardShortcut();
this.injectPrintStyles();
this.markPageReady();
this.state.initialized = true;
this.log('RX Print initialized');
},
mergeUserConfig() {
if (window.rxPrintConfig && typeof window.rxPrintConfig === 'object') {
this.options = {
...this.options,
...window.rxPrintConfig
};
}
},
log(...args) {
if (this.options.debug) {
console.log('[RX Print]', ...args);
}
},
emit(name, detail = {}) {
const event = new CustomEvent(`rx:print:${name}`, {
detail: {
version: this.version,
...detail
}
});
document.dispatchEvent(event);
},
bindPrintButtons() {
const buttons = document.querySelectorAll(this.selectors.printButton);
buttons.forEach((button) => {
if (button.dataset.rxPrintBound === '1') return;
button.dataset.rxPrintBound = '1';
if (!button.getAttribute('type') && button.tagName === 'BUTTON') {
button.setAttribute('type', 'button');
}
button.addEventListener('click', (event) => {
event.preventDefault();
const targetSelector = button.getAttribute('data-rx-print-target');
this.print({
targetSelector,
button
});
});
});
},
bindPrintEvents() {
window.addEventListener('beforeprint', () => {
this.beforePrint();
});
window.addEventListener('afterprint', () => {
this.afterPrint();
});
},
bindKeyboardShortcut() {
document.addEventListener(
'keydown',
(event) => {
const isPrintShortcut =
(event.ctrlKey || event.metaKey) &&
String(event.key).toLowerCase() === 'p';
if (!isPrintShortcut) return;
this.emit('shortcut', {
originalEvent: event
});
/**
* Do not prevent default.
* Browser print dialog should remain natural.
* beforeprint event will still prepare the page.
*/
},
{ passive: true }
);
},
async print(context = {}) {
if (this.state.printing) return;
this.state.printing = true;
this.emit('requested', context);
try {
await this.prepare(context);
setTimeout(() => {
window.print();
}, this.options.printDelay);
} catch (error) {
console.error('[RX Print] Print preparation failed:', error);
window.print();
} finally {
setTimeout(() => {
this.state.printing = false;
}, 1000);
}
},
async prepare(context = {}) {
this.emit('prepare-start', context);
this.preparePrintArea(context);
this.hideUnwantedElements();
this.expandPrintableDetails();
this.prepareTables();
this.prepareLinks();
this.prepareIframes();
this.removeEmptyBlocks();
if (this.options.prepareImages) {
await this.prepareImages();
}
this.addPrintMeta();
this.updateDocumentTitle();
this.emit('prepare-end', context);
},
beforePrint() {
this.emit('before');
/**
* Browser shortcut printing may not call print().
* So prepare again safely.
*/
if (!document.documentElement.classList.contains('rx-print-prepared')) {
this.prepare();
}
document.documentElement.classList.add('rx-is-printing');
},
afterPrint() {
this.emit('after');
this.cleanup();
document.documentElement.classList.remove('rx-is-printing');
document.documentElement.classList.remove('rx-print-prepared');
document.title = this.state.originalTitle;
this.state.printing = false;
},
preparePrintArea(context = {}) {
const selector = context.targetSelector;
let area = null;
if (selector) {
area = document.querySelector(selector);
}
if (!area) {
area = document.querySelector(this.selectors.printableArea);
}
if (!area) return;
document.body.classList.add('rx-print-article-mode');
area.classList.add('rx-print-active-area');
area.setAttribute('data-rx-print-active', 'true');
this.emit('area-detected', {
area
});
},
hideUnwantedElements() {
const selectors = [
this.selectors.noPrint
];
if (this.options.hideAds) selectors.push(this.selectors.ad);
if (this.options.hideComments) selectors.push(this.selectors.comments);
if (this.options.hideSidebars) selectors.push(this.selectors.sidebar);
if (this.options.hideNavigation) selectors.push(this.selectors.navigation);
const combinedSelector = selectors.join(',');
document.querySelectorAll(combinedSelector).forEach((node) => {
if (node.dataset.rxPrintHidden === '1') return;
node.dataset.rxPrintHidden = '1';
node.classList.add('rx-print-force-hide');
this.state.hiddenNodes.push(node);
});
},
expandPrintableDetails() {
if (!this.options.expandDetails) return;
const details = document.querySelectorAll(this.selectors.expandOnPrint);
details.forEach((node) => {
if (node.tagName === 'DETAILS') {
if (!node.open) {
node.open = true;
node.dataset.rxPrintWasClosed = '1';
this.state.openedDetails.push(node);
}
}
node.classList.add('rx-print-expanded');
});
},
prepareTables() {
const tables = document.querySelectorAll(this.selectors.table);
tables.forEach((table, index) => {
if (table.dataset.rxPrintTableReady === '1') return;
table.dataset.rxPrintTableReady = '1';
table.classList.add('rx-print-table');
if (!table.parentElement.classList.contains('rx-print-table-wrap')) {
const wrapper = document.createElement('div');
wrapper.className = 'rx-print-table-wrap';
wrapper.dataset.rxGeneratedPrintNode = '1';
table.parentNode.insertBefore(wrapper, table);
wrapper.appendChild(table);
this.state.generatedNodes.push(wrapper);
}
const caption = table.querySelector('caption');
if (!caption) {
table.setAttribute('aria-label', table.getAttribute('aria-label') || `Table ${index + 1}`);
}
this.state.processedTables.push(table);
});
},
prepareLinks() {
if (!this.options.addLinkUrls) return;
const links = document.querySelectorAll(this.selectors.link);
links.forEach((link) => {
if (link.dataset.rxPrintLinkReady === '1') return;
const href = link.getAttribute('href');
if (!href) return;
if (href.startsWith('#')) return;
if (href.startsWith('javascript:')) return;
if (href.startsWith('mailto:')) return;
if (href.startsWith('tel:')) return;
link.dataset.rxPrintLinkReady = '1';
try {
const absoluteUrl = new URL(href, window.location.href).href;
link.setAttribute('data-rx-print-url', absoluteUrl);
link.classList.add('rx-print-link-with-url');
this.state.processedLinks.push(link);
} catch (error) {
this.log('Invalid link skipped:', href);
}
});
},
prepareIframes() {
const iframes = document.querySelectorAll(this.selectors.iframe);
iframes.forEach((iframe) => {
if (iframe.dataset.rxPrintIframeReady === '1') return;
iframe.dataset.rxPrintIframeReady = '1';
const src = iframe.getAttribute('src');
if (!src) return;
const fallback = document.createElement('p');
fallback.className = 'rx-print-iframe-source';
fallback.dataset.rxGeneratedPrintNode = '1';
try {
fallback.textContent = `Embedded content: ${new URL(src, window.location.href).href}`;
} catch (error) {
fallback.textContent = `Embedded content: ${src}`;
}
iframe.insertAdjacentElement('afterend', fallback);
this.state.generatedNodes.push(fallback);
});
},
async prepareImages() {
const images = Array.from(document.querySelectorAll(this.selectors.lazyImage));
if (!images.length) return;
const tasks = images.map((img) => this.prepareSingleImage(img));
await Promise.race([
Promise.allSettled(tasks),
this.wait(this.options.imageTimeout)
]);
},
prepareSingleImage(img) {
return new Promise((resolve) => {
if (!img || img.dataset.rxPrintImageReady === '1') {
resolve();
return;
}
const dataSrc = img.getAttribute('data-src') || img.getAttribute('data-lazy-src');
if (dataSrc && !img.getAttribute('src')) {
img.setAttribute('src', dataSrc);
}
img.loading = 'eager';
img.decoding = 'sync';
img.dataset.rxPrintImageReady = '1';
img.classList.add('rx-print-image-ready');
this.state.processedImages.push(img);
if (img.complete) {
resolve();
return;
}
const done = () => resolve();
img.addEventListener('load', done, { once: true });
img.addEventListener('error', done, { once: true });
setTimeout(done, this.options.imageTimeout);
});
},
removeEmptyBlocks() {
if (!this.options.removeEmptyBlocks) return;
const blocks = document.querySelectorAll(
'.rx-print-active-area p, .rx-print-active-area div, article p, article div'
);
blocks.forEach((block) => {
if (block.children.length > 0) return;
const text = block.textContent.replace(/\s+/g, '').trim();
if (!text) {
block.classList.add('rx-print-empty-block');
}
});
},
addPrintMeta() {
const activeArea =
document.querySelector('.rx-print-active-area') ||
document.querySelector('article') ||
document.querySelector('main');
if (!activeArea) return;
if (activeArea.querySelector('.rx-print-meta-box')) return;
const meta = document.createElement('div');
meta.className = 'rx-print-meta-box';
meta.dataset.rxGeneratedPrintNode = '1';
const items = [];
if (this.options.addPrintedDate) {
const date = new Date();
items.push(
`${this.escapeText(this.options.dateLabel)}: ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
);
}
if (this.options.addSourceUrl) {
items.push(`${this.escapeText(this.options.sourceLabel)}: ${window.location.href}`);
}
if (this.options.addCopyright) {
items.push(this.options.copyrightText);
}
meta.innerHTML = items
.map((item) => `<p>${this.escapeText(item)}</p>`)
.join('');
activeArea.appendChild(meta);
this.state.generatedNodes.push(meta);
},
updateDocumentTitle() {
const heading = document.querySelector('h1');
const siteName = this.options.siteName || 'RX Theme';
if (heading && heading.textContent.trim()) {
document.title = `${heading.textContent.trim()} - ${siteName}`;
}
},
cleanup() {
this.state.openedDetails.forEach((details) => {
if (details.dataset.rxPrintWasClosed === '1') {
details.open = false;
delete details.dataset.rxPrintWasClosed;
}
});
this.state.hiddenNodes.forEach((node) => {
node.classList.remove('rx-print-force-hide');
delete node.dataset.rxPrintHidden;
});
this.state.processedLinks.forEach((link) => {
link.classList.remove('rx-print-link-with-url');
});
document.querySelectorAll('.rx-print-active-area').forEach((node) => {
node.classList.remove('rx-print-active-area');
node.removeAttribute('data-rx-print-active');
});
document.querySelectorAll('.rx-print-expanded').forEach((node) => {
node.classList.remove('rx-print-expanded');
});
document.querySelectorAll('.rx-print-empty-block').forEach((node) => {
node.classList.remove('rx-print-empty-block');
});
this.state.generatedNodes.forEach((node) => {
if (!node || !node.parentNode) return;
/**
* If wrapper contains a table, unwrap safely.
*/
if (node.classList.contains('rx-print-table-wrap')) {
const table = node.querySelector('table');
if (table && node.parentNode) {
node.parentNode.insertBefore(table, node);
}
}
node.remove();
});
document.body.classList.remove('rx-print-article-mode');
this.state.openedDetails = [];
this.state.generatedNodes = [];
this.state.hiddenNodes = [];
this.state.processedLinks = [];
this.state.processedTables = [];
this.state.processedImages = [];
},
injectPrintStyles() {
if (document.getElementById('rx-print-dynamic-style')) return;
const style = document.createElement('style');
style.id = 'rx-print-dynamic-style';
style.textContent = `
@media print {
html.rx-is-printing,
html.rx-is-printing body {
background: #ffffff !important;
}
body.rx-print-article-mode > *:not(.rx-print-active-area):not(script):not(style) {
display: none !important;
}
.rx-print-force-hide,
.rx-print-empty-block,
[data-no-print],
.no-print,
.rx-no-print {
display: none !important;
}
.rx-print-active-area {
display: block !important;
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background: #ffffff !important;
color: #000000 !important;
box-shadow: none !important;
}
.rx-print-active-area *,
article *,
main * {
box-shadow: none !important;
text-shadow: none !important;
}
.rx-print-active-area h1,
.rx-print-active-area h2,
.rx-print-active-area h3,
article h1,
article h2,
article h3 {
color: #000000 !important;
page-break-after: avoid;
break-after: avoid;
}
.rx-print-active-area p,
article p,
main p {
orphans: 3;
widows: 3;
}
.rx-print-active-area img,
article img,
main img {
max-width: 100% !important;
height: auto !important;
page-break-inside: avoid;
break-inside: avoid;
}
.rx-print-table-wrap {
width: 100% !important;
overflow: visible !important;
page-break-inside: avoid;
break-inside: avoid;
}
table.rx-print-table {
width: 100% !important;
border-collapse: collapse !important;
font-size: 12px !important;
page-break-inside: auto;
}
table.rx-print-table th,
table.rx-print-table td {
border: 1px solid #000000 !important;
padding: 6px !important;
vertical-align: top !important;
}
table.rx-print-table thead {
display: table-header-group;
}
table.rx-print-table tfoot {
display: table-footer-group;
}
.rx-print-link-with-url::after {
content: " (" attr(data-rx-print-url) ")";
font-size: 90%;
word-break: break-all;
}
.rx-print-meta-box {
margin-top: 32px !important;
padding-top: 12px !important;
border-top: 1px solid #000000 !important;
font-size: 12px !important;
color: #000000 !important;
}
.rx-print-meta-box p {
margin: 4px 0 !important;
}
.rx-print-iframe-source {
display: block !important;
font-size: 12px !important;
border: 1px solid #000000 !important;
padding: 8px !important;
word-break: break-all !important;
}
iframe,
video,
audio,
canvas,
svg.rx-no-print-svg {
max-width: 100% !important;
}
a {
color: #000000 !important;
text-decoration: underline !important;
}
@page {
margin: 14mm;
}
}
`;
document.head.appendChild(style);
},
markPageReady() {
document.documentElement.classList.add('rx-print-js-ready');
},
wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
},
escapeText(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
};
window.RXPrint = RXPrint;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => RXPrint.init(), {
once: true
});
} else {
RXPrint.init();
}
})();
Use print button like this anywhere in your theme:
<button type="button" class="rx-print-button" data-rx-print>
Print Article
</button>
For exact article area:
<article class="rx-print-area">
<!-- article content -->
</article>
For elements you never want in print:
<div class="rx-no-print">
This will not print
</div>
Optional config before loading the chunk:
<script>
window.rxPrintConfig = {
debug: false,
articleOnly: true,
addSourceUrl: true,
addPrintedDate: true,
addCopyright: true,
copyrightText: 'Printed from rxharun.com'
};
</script>
Best enqueue name for WordPress:
wp_enqueue_script(
'rx-theme-print-chunk',
get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-020-print.js',
array(),
RX_THEME_VERSION,
true
);
This file should stay as a separate print chunk, not inside your main JavaScript, because print logic is not needed on every millisecond of first page load.