0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-05-07 18:48:39 +00:00

Rework HTML Download options to use a DOM snatch and grab

Still requires path manipulation.

Stubs the same for Zipfiles.
This commit is contained in:
David Bolack
2026-02-08 19:48:57 -06:00
parent f16c8b7663
commit 2791c2259b
3 changed files with 32 additions and 113 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import Nav from './nav.jsx'; import Nav from './nav.jsx';
import { printCurrentBrew } from '../../../shared/helpers.js'; import { printCurrentBrew, scrapeBrewHTML, scrapeBrewZip } from '../../../shared/helpers.js';
export default function(props){ export default function(props){
return <Nav.dropdown> return <Nav.dropdown>
@@ -10,10 +10,10 @@ export default function(props){
<Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'> <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
get PDF get PDF
</Nav.item> </Nav.item>
<Nav.item color='orange' icon='fas fa-file-code' href={`/export/slimHTML/${props?.brew?.editId || props?.brew?.shareId}`}> <Nav.item onClick={scrapeBrewHTML} color='orange' icon='fas fa-file-code'>
get HTML get HTML
</Nav.item> </Nav.item>
<Nav.item color='orange' icon='fas fa-file-archive' href={`/export/zipHTML/${props?.brew?.editId || props?.brew?.shareId}`}> <Nav.item onClick={scrapeBrewZip} color='orange' icon='fas fa-file-archive'>
get HTML (Zip) get HTML (Zip)
</Nav.item> </Nav.item>
</Nav.dropdown>; </Nav.dropdown>;
+2 -22
View File
@@ -27,9 +27,8 @@ import asyncHandler from 'express-async-handler';
import templateFn from '../client/template.js'; import templateFn from '../client/template.js';
import { model as HomebrewModel } from './homebrew.model.js'; import { model as HomebrewModel } from './homebrew.model.js';
import { DEFAULT_BREW } from './brewDefaults.js'; import { DEFAULT_BREW } from './brewDefaults.js';
import { splitTextStyleAndMetadata, import { splitTextStyleAndMetadata } from '../shared/helpers.js';
simulateRender } from '../shared/helpers.js';
//==== Middleware Imports ====// //==== Middleware Imports ====//
import contentNegotiation from './middleware/content-negotiation.js'; 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); 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 //Download brew source page
app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{
+27 -88
View File
@@ -176,99 +176,37 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
} }
}; };
const simulateRenderPage = (pageText, index, renderer)=>{ const scrapeBrew = ()=>{
const htmlBody = `<html>\n${window.frames['BrewRenderer'].contentDocument.documentElement.innerHTML}\n</html>`;
let styles = {}; return htmlBody;
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 `<div className='page phb' index=${index} key=${index} style=${styles}>\n${html}\n</div>\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&nbsp;\n\\column\n&nbsp;`; //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 `<div class=${classes} id=p${index} key=${index} style=${styles} ${attributes}>\n${html}\n</div>`;
}
}; };
const simulateRender = async (req, res, next)=>{ const downloadBlob = (brewHtml, fileName)=>{
let htmlHead = ''; const blob = new Blob([brewHtml], { type: 'text/plain' });
let htmlStyles = ''; const url = URL.createObjectURL(blob);
let htmlBody = ''; const a = document.createElement('a');
let errorMsg = {}; a.href = url;
// Build HTML similar to the BrewRender ? a.download = fileName || 'download';
const clickHandler = ()=>{
const setError = (error)=>{ setTimeout(()=>{
errorMsg = error; 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 scrapeBrewHTML = ()=>{
const htmlBody = scrapeBrew();
const PORT = req.header('host').indexOf[':'] > -1 ? req.header('host').split(':')[1] : '8000'; // Manipulate the body to change all relative path references to full URLs
downloadBlob(htmlBody, 'testDownload.html');
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)=>`<style>${style}</style>`).join('\n\n');
// Create Head
htmlHead += ` <head>
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css">
<link href="/homebrew/bundle.css" type="text/css" rel="stylesheet">
<base target="_blank">
<title>${req.brew.title}</title>
</head>`;
htmlStyles = `\t<div style="display:none;">\n` +
`\t\t${htmlThemeBundle}\n` +
`\t\t<style>\n${req.brew.style}\n</style>\n` +
`\t</div>`;
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 = `<div class="pages recto single" lang="en" style="zoom: 100%; gap: 5px 10px;">${renderedPages.join('\n')}\n</div`;
const result = `<html>\n${htmlHead}\n<body>\n\t<div>\n\t\t<div class="frame-content">\n\t\t\t<div class="brewRenderer">\n` +
`\t\t\t\t${htmlStyles}\n${htmlBody}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</body>\n</html>`;
req.brew.html = result;
next();
}; };
export { export {
@@ -277,5 +215,6 @@ export {
fetchThemeBundle, fetchThemeBundle,
brewSnippetsToJSON, brewSnippetsToJSON,
debugTextMismatch, debugTextMismatch,
simulateRender, scrapeBrewHTML,
scrapeBrewZip,
}; };