chunk-020-print.js

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, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
    }
  };

  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, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
    }
  };

  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.

Leave a Reply

Your email address will not be published. Required fields are marked *