From 2791c2259b2414a20081da8bac6f02c6a97d4228 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Sun, 8 Feb 2026 19:48:57 -0600 Subject: [PATCH] Rework HTML Download options to use a DOM snatch and grab Still requires path manipulation. Stubs the same for Zipfiles. --- client/homebrew/navbar/print.navitem.jsx | 6 +- server/app.js | 24 +---- shared/helpers.js | 115 ++++++----------------- 3 files changed, 32 insertions(+), 113 deletions(-) diff --git a/client/homebrew/navbar/print.navitem.jsx b/client/homebrew/navbar/print.navitem.jsx index a3f2887cd..271e17027 100644 --- a/client/homebrew/navbar/print.navitem.jsx +++ b/client/homebrew/navbar/print.navitem.jsx @@ -1,6 +1,6 @@ import React from 'react'; import Nav from './nav.jsx'; -import { printCurrentBrew } from '../../../shared/helpers.js'; +import { printCurrentBrew, scrapeBrewHTML, scrapeBrewZip } from '../../../shared/helpers.js'; export default function(props){ return @@ -10,10 +10,10 @@ export default function(props){ get PDF - + get HTML - + get HTML (Zip) ; diff --git a/server/app.js b/server/app.js index 2cdf6ca0b..3ecadcf0e 100644 --- a/server/app.js +++ b/server/app.js @@ -27,9 +27,8 @@ import asyncHandler from 'express-async-handler'; import templateFn from '../client/template.js'; import { model as HomebrewModel } from './homebrew.model.js'; -import { DEFAULT_BREW } from './brewDefaults.js'; -import { splitTextStyleAndMetadata, - simulateRender } from '../shared/helpers.js'; +import { DEFAULT_BREW } from './brewDefaults.js'; +import { splitTextStyleAndMetadata } from '../shared/helpers.js'; //==== Middleware Imports ====// import contentNegotiation from './middleware/content-negotiation.js'; @@ -240,25 +239,6 @@ app.get('/source/:id', asyncHandler(getBrew('share')), (req, res)=>{ res.status(200).send(text); }); -//Export the Brew as HTML -app.get('/export/:mode/:id', asyncHandler(getBrew('admin')), asyncHandler(simulateRender), (req, res)=>{ - - const id = req.params.id; - const mode = req.params.mode; - const { brew } = req; - sanitizeBrew(brew, 'share'); - const prefix = 'HB - '; - - let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); - if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; }; - // res.set({ - // 'Cache-Control' : 'no-cache', - // 'Content-Type' : 'text/plain', - // 'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.html` - // }); - res.status(200).send(brew.html); -}); - //Download brew source page app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ diff --git a/shared/helpers.js b/shared/helpers.js index 8936235d1..91ad3bfb9 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -176,99 +176,37 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{ } }; -const simulateRenderPage = (pageText, index, renderer)=>{ - - let styles = {}; - - let classes = 'page'; - let attributes = {}; - - if(renderer == 'legacy') { - pageText.replace(COLUMNBREAK_REGEX_LEGACY, '```\n````\n'); // Allow Legacy brews to use `\column(break)` - // const html = MarkdownLegacy.render(pageText); - const html = "Markdown Legacy currently unsupported" - - return `
\n${html}\n
\n`; - } else { - if(pageText.startsWith('\\page')) { - const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens; - const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags; - if(injectedTags) { - styles = { ...styles, ...injectedTags.styles }; - styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)).join(''); // Convert CSS to camelCase for React - classes = [classes, injectedTags.classes].join(' ').trim(); - attributes = injectedTags.attributes; - } - pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line - } - - // DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME. - pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear) - - const html = Markdown.render(pageText, index); - - return `
\n${html}\n
`; - } +const scrapeBrew = ()=>{ + const htmlBody = `\n${window.frames['BrewRenderer'].contentDocument.documentElement.innerHTML}\n`; + return htmlBody; }; -const simulateRender = async (req, res, next)=>{ - let htmlHead = ''; - let htmlStyles = ''; - let htmlBody = ''; - let errorMsg = {}; - // Build HTML similar to the BrewRender ? - - const setError = (error)=>{ - errorMsg = error; +const downloadBlob = (brewHtml, fileName)=>{ + const blob = new Blob([brewHtml], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = fileName || 'download'; + const clickHandler = ()=>{ + setTimeout(()=>{ + URL.revokeObjectURL(url); + removeEventListener('click', clickHandler); + }, 150); }; + a.addEventListener('click', clickHandler, false); + a.click(); +}; +const scrapeBrewZip = ()=>{ + const htmlBody = scrapeBrew(); + // DO STUFF! +}; - splitTextStyleAndMetadata(req.brew); - - const PORT = req.header('host').indexOf[':'] > -1 ? req.header('host').split(':')[1] : '8000'; - - const themeRes = await request - .get(`http://localhost:${PORT}/api/theme/${req.brew.renderer}/${req.brew.theme}`) - .set('Homebrewery-Version', packageJSON.version) - .catch((err)=>{ - setError(err); - }); - - const htmlThemeBundle = themeRes.body.styles.map((style)=>``).join('\n\n'); - - // Create Head - htmlHead += ` - - - - ${req.brew.title} - `; - - htmlStyles = `\t
\n` + - `\t\t${htmlThemeBundle}\n` + - `\t\t\n` + - `\t
`; - - let rawPages = []; - let renderedPages = []; - - if(req.brew.renderer == 'legacy') { - rawPages = req.brew.text.split(PAGEBREAK_REGEX_LEGACY); - } else { - rawPages = req.brew.text.split(PAGEBREAK_REGEX_V3); - } - - _.forEach(rawPages, (page, index)=>{ - renderedPages[index] = simulateRenderPage(page, index, req.brew.renderer); - }); - - htmlBody = `
${renderedPages.join('\n')}\n\n${htmlHead}\n\n\t
\n\t\t
\n\t\t\t
\n` + - `\t\t\t\t${htmlStyles}\n${htmlBody}\n\t\t\t
\n\t\t
\n\t
\n\n`; - req.brew.html = result; - next(); +const scrapeBrewHTML = ()=>{ + const htmlBody = scrapeBrew(); + // Manipulate the body to change all relative path references to full URLs + downloadBlob(htmlBody, 'testDownload.html'); }; export { @@ -277,5 +215,6 @@ export { fetchThemeBundle, brewSnippetsToJSON, debugTextMismatch, - simulateRender, + scrapeBrewHTML, + scrapeBrewZip, };