From a2e5688edeb3d44300bfe4cc2c8d927c529f04fe Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Tue, 28 Apr 2026 22:21:35 -0500 Subject: [PATCH 1/5] Add lazy loading to images --- shared/markdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/markdown.js b/shared/markdown.js index d2a108e01..05a564254 100644 --- a/shared/markdown.js +++ b/shared/markdown.js @@ -83,7 +83,7 @@ renderer.image = function (token) { if(href === null) return text; - let out = `${text} Date: Tue, 28 Apr 2026 22:51:27 -0500 Subject: [PATCH 2/5] Update mustache-syntax.test.js --- tests/markdown/mustache-syntax.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index 95ca2f58d..378263e58 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -324,7 +324,7 @@ describe('Injection: When an injection tag follows an element', ()=>{ it('Renders an image element with injected style', function() { const source = '![alt text](https://i.imgur.com/hMna6G0.png){position:absolute}'; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

alt text

'); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

alt text

'); }); it('Renders an element modified by only the first of two consecutive injections', function() { @@ -343,19 +343,19 @@ describe('Injection: When an injection tag follows an element', ()=>{ it('Renders an image with added attributes', function() { const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); it('Renders an image with "=" in the url, and added attributes', function() { const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png?auth=12345&height=1024) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); it('Renders an image and added attributes with "=" in the value, ', function() { const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e,otherUrl="url?auth=12345"}`; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); }); From d04f401c90f944fd9ba561ee8e78946e2ff9effd Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Wed, 29 Apr 2026 23:36:58 -0500 Subject: [PATCH 3/5] Fix lazy loading Print issues When initiating the print dialog, it first grabs all img elements with `loading="lazy"` attribute, flips that to `eager`, and then waits for every image to load before resolving a promise and opening the Print dialog. --- shared/helpers.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/shared/helpers.js b/shared/helpers.js index d2a9c8b73..e8b5596b6 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -105,11 +105,25 @@ const splitTextStyleAndMetadata = (brew)=>{ if(typeof brew.tags === 'string') brew.tags = brew.tags ? [brew.tags] : []; }; -const printCurrentBrew = ()=>{ +const printCurrentBrew = async ()=>{ if(window.typeof !== 'undefined') { + const iframeDoc = window.frames['BrewRenderer'].contentDocument; + + // get all img elements with lazy loading (currently only elements generated through MarkedJS) + const lazyImages = [...iframeDoc.querySelectorAll('img[loading="lazy"]')]; + lazyImages.forEach((img)=>{ img.loading = 'eager'; }); + + // waits for images to load before resolving promise and opening print dialog + await Promise.all( + lazyImages + .filter((img)=>!img.complete) + .map((img)=>new Promise((resolve)=>{ img.onload = resolve; img.onerror = resolve; })) + ); + window.frames['BrewRenderer'].contentWindow.print(); + //Force DOM reflow; Print dialog causes a repaint, and @media print CSS somehow makes out-of-view pages disappear - const node = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer').item(0); + const node = iframeDoc.getElementsByClassName('brewRenderer').item(0); node.style.display='none'; node.offsetHeight; // accessing this is enough to trigger a reflow node.style.display=''; From 9af2577c6e9282069774bff200955273575a530a Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Thu, 30 Apr 2026 21:05:08 -0500 Subject: [PATCH 4/5] Add print cycle events and loading msg Since the print cycle now loads all images not-yet-loaded (due to lazy loading), there can be a moment of time where it appears pressing Get PDF is doing nothing, depending on connection speed. To add a "loading" message, a custom event is fired at the start and end of the print cycle (before the print dialog comes up). --- client/homebrew/navbar/print.navitem.jsx | 20 ++++++++++-- shared/helpers.js | 39 ++++++++++++++---------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/client/homebrew/navbar/print.navitem.jsx b/client/homebrew/navbar/print.navitem.jsx index ea262cf03..e669214b3 100644 --- a/client/homebrew/navbar/print.navitem.jsx +++ b/client/homebrew/navbar/print.navitem.jsx @@ -1,9 +1,25 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import Nav from './nav.jsx'; import { printCurrentBrew } from '@shared/helpers.js'; export default function(){ + const [printing, setPrinting] = useState(false); + + // listen for print cycle events to display "loading" message since it can take some time. + useEffect(()=>{ + document.addEventListener('print:startprep', handlePrintStartPrep); + document.addEventListener('print:finishedprep', handlePrintPrepFinished); + return ()=>{ + document.removeEventListener('print:startprep', handlePrintStartPrep); + document.removeEventListener('print:finishedprep', handlePrintPrepFinished); + } + }, []); + + const handlePrintStartPrep = ()=>{ setPrinting(true); }; + + const handlePrintPrepFinished = ()=>{ setPrinting(false); }; + return - get PDF + {printing ? 'loading' : 'get PDF'} ; }; diff --git a/shared/helpers.js b/shared/helpers.js index e8b5596b6..eeb09daf4 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -107,26 +107,33 @@ const splitTextStyleAndMetadata = (brew)=>{ const printCurrentBrew = async ()=>{ if(window.typeof !== 'undefined') { - const iframeDoc = window.frames['BrewRenderer'].contentDocument; + // fire a custom event for the print cycle + document.dispatchEvent(new CustomEvent('print:startprep')); + try { + const iframeDoc = window.frames['BrewRenderer'].contentDocument; - // get all img elements with lazy loading (currently only elements generated through MarkedJS) - const lazyImages = [...iframeDoc.querySelectorAll('img[loading="lazy"]')]; - lazyImages.forEach((img)=>{ img.loading = 'eager'; }); + // get all img elements with lazy loading (currently only elements generated through MarkedJS) + const lazyImages = [...iframeDoc.querySelectorAll('img[loading="lazy"]')]; + lazyImages.forEach((img)=>{ img.loading = 'eager'; }); - // waits for images to load before resolving promise and opening print dialog - await Promise.all( - lazyImages - .filter((img)=>!img.complete) - .map((img)=>new Promise((resolve)=>{ img.onload = resolve; img.onerror = resolve; })) - ); + // waits for images to load before resolving promise and opening print dialog + await Promise.all( + lazyImages + .filter((img)=>!img.complete) + .map((img)=>new Promise((resolve)=>{ img.onload = resolve; img.onerror = resolve; })) + ); - window.frames['BrewRenderer'].contentWindow.print(); + window.frames['BrewRenderer'].contentWindow.print(); - //Force DOM reflow; Print dialog causes a repaint, and @media print CSS somehow makes out-of-view pages disappear - const node = iframeDoc.getElementsByClassName('brewRenderer').item(0); - node.style.display='none'; - node.offsetHeight; // accessing this is enough to trigger a reflow - node.style.display=''; + //Force DOM reflow; Print dialog causes a repaint, and @media print CSS somehow makes out-of-view pages disappear + const node = iframeDoc.getElementsByClassName('brewRenderer').item(0); + node.style.display='none'; + node.offsetHeight; // accessing this is enough to trigger a reflow + node.style.display=''; + } finally { + // when lazy load images have all been loaded, and the doc re-rendered for print preview, emit 'finished' event. + document.dispatchEvent(new CustomEvent('print:finishedprep')); + } } }; From 9935f54080f78a79cd2fe75c048e0038dc4cdf23 Mon Sep 17 00:00:00 2001 From: Gazook89 Date: Thu, 30 Apr 2026 21:11:33 -0500 Subject: [PATCH 5/5] Update variables.test.js --- tests/markdown/variables.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js index 884553703..ad23c87c1 100644 --- a/tests/markdown/variables.test.js +++ b/tests/markdown/variables.test.js @@ -315,21 +315,21 @@ describe('Normal Links and Images', ()=>{ const source = `![alt text](url)`; const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` -

alt text

`.trimReturns()); +

alt text

`.trimReturns()); }); it('Renders normal images with a title', function() { const source = 'An image ![alt text](url "and title")!'; const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` -

An image alt text!

`.trimReturns()); +

An image alt text!

`.trimReturns()); }); it('Applies curly injectors to images', function() { const source = `![alt text](url){width:100px}`; const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` -

alt text

`.trimReturns()); +

alt text

`.trimReturns()); }); it('Renders normal links', function() { @@ -438,25 +438,25 @@ describe('Regression Tests', ()=>{ it('Handle Extra spaces in image alt-text 1', function(){ const source='![ where is my image??](http://i.imgur.com/hMna6G0.png)'; const rendered = Markdown.render(source).trimReturns(); - expect(rendered).toBe('

\"where

'); + expect(rendered).toBe('

\"where

'); }); it('Handle Extra spaces in image alt-text 2', function(){ const source='![where is my image??](http://i.imgur.com/hMna6G0.png)'; const rendered = Markdown.render(source).trimReturns(); - expect(rendered).toBe('

\"where

'); + expect(rendered).toBe('

\"where

'); }); it('Handle Extra spaces in image alt-text 3', function(){ const source='![where is my image?? ](http://i.imgur.com/hMna6G0.png)'; const rendered = Markdown.render(source).trimReturns(); - expect(rendered).toBe('

\"where

'); + expect(rendered).toBe('

\"where

'); }); it('Handle Extra spaces in image alt-text 4', function(){ const source='![where is my image??](http://i.imgur.com/hMna6G0.png){height=20%,width=20%}'; const rendered = Markdown.render(source).trimReturns(); - expect(rendered).toBe('

\"where

'); + expect(rendered).toBe('

\"where

'); }); });