mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-05-07 18:48:39 +00:00
Initial Commit
Adds menu items for "regular", zipped, and inline output. Currently only displays inline output with *no* URL massaging ( all relative path references are still relative ) Displays, not downloads
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const Nav = require('client/homebrew/navbar/nav.jsx');
|
||||||
|
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||||
|
|
||||||
|
module.exports = function(props){
|
||||||
|
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||||
|
get PDF
|
||||||
|
</Nav.item>;
|
||||||
|
};
|
||||||
@@ -2,8 +2,22 @@ const React = require('react');
|
|||||||
const Nav = require('client/homebrew/navbar/nav.jsx');
|
const Nav = require('client/homebrew/navbar/nav.jsx');
|
||||||
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||||
|
|
||||||
module.exports = function(){
|
module.exports = function(props){
|
||||||
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
return <Nav.dropdown>
|
||||||
get PDF
|
<Nav.item color='grey' icon='fas fa-question-circle'>
|
||||||
</Nav.item>;
|
export
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||||
|
get PDF
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item color='orange' icon='fas fa-file-code' href={`/export/slimHTML/${props?.brew?.editId || props?.brew?.shareId}`}>
|
||||||
|
get HTML (Slim)
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item color='orange' icon='fas fa-file-archive' href={`/export/zipHTML/${props?.brew?.editId || props?.brew?.shareId}`}>
|
||||||
|
get HTML (Zip)
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item color='orange' icon='far fa-file-code' href={`/export/inlineHTML/${props?.brew?.editId || props?.brew?.shareId}`}>
|
||||||
|
get HTML (Inline)
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.dropdown>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ const EditPage = (props)=>{
|
|||||||
{renderAutoSaveButton()}
|
{renderAutoSaveButton()}
|
||||||
</Nav.dropdown>}
|
</Nav.dropdown>}
|
||||||
<NewBrewItem />
|
<NewBrewItem />
|
||||||
<PrintNavItem />
|
<PrintNavItem brew={currentBrew}/>
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
<VaultNavItem />
|
<VaultNavItem />
|
||||||
<ShareNavItem brew={currentBrew} />
|
<ShareNavItem brew={currentBrew} />
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ const HomePage =(props)=>{
|
|||||||
? <ErrorNavItem error={error} clearError={clearError} />
|
? <ErrorNavItem error={error} clearError={clearError} />
|
||||||
: renderSaveButton()}
|
: renderSaveButton()}
|
||||||
<NewBrewItem />
|
<NewBrewItem />
|
||||||
<PrintNavItem />
|
<PrintNavItem brew={currentBrew}/>
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
<VaultNavItem />
|
<VaultNavItem />
|
||||||
<RecentNavItem />
|
<RecentNavItem />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import AccountNavItem from 'client/homebrew/navbar/account.navitem.js
|
|||||||
import ErrorNavItem from 'client/homebrew/navbar/error-navitem.jsx';
|
import ErrorNavItem from 'client/homebrew/navbar/error-navitem.jsx';
|
||||||
import HelpNavItem from 'client/homebrew/navbar/help.navitem.jsx';
|
import HelpNavItem from 'client/homebrew/navbar/help.navitem.jsx';
|
||||||
import VaultNavItem from 'client/homebrew/navbar/vault.navitem.jsx';
|
import VaultNavItem from 'client/homebrew/navbar/vault.navitem.jsx';
|
||||||
import PrintNavItem from 'client/homebrew/navbar/print.navitem.jsx';
|
import PDFNavItem from 'client/homebrew/navbar/pdf.navitem.jsx';
|
||||||
import { both as RecentNavItem } from 'client/homebrew/navbar/recent.navitem.jsx';
|
import { both as RecentNavItem } from 'client/homebrew/navbar/recent.navitem.jsx';
|
||||||
|
|
||||||
// Page specific imports
|
// Page specific imports
|
||||||
@@ -229,7 +229,7 @@ const NewPage = (props)=>{
|
|||||||
? <ErrorNavItem error={error} clearError={clearError} />
|
? <ErrorNavItem error={error} clearError={clearError} />
|
||||||
: renderSaveButton()}
|
: renderSaveButton()}
|
||||||
<NewBrewItem />
|
<NewBrewItem />
|
||||||
<PrintNavItem />
|
<PDFNavItem />
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
<VaultNavItem />
|
<VaultNavItem />
|
||||||
<RecentNavItem />
|
<RecentNavItem />
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const SharePage = (props)=>{
|
|||||||
<Nav.section>
|
<Nav.section>
|
||||||
{brew.shareId && (
|
{brew.shareId && (
|
||||||
<>
|
<>
|
||||||
<PrintNavItem />
|
<PrintNavItem brew={currentBrew}/>
|
||||||
<Nav.dropdown>
|
<Nav.dropdown>
|
||||||
<Nav.item color='red' icon='fas fa-code'>
|
<Nav.item color='red' icon='fas fa-code'>
|
||||||
source
|
source
|
||||||
|
|||||||
+32
-10
@@ -25,10 +25,11 @@ import serveCompressedStaticAssets from './static-assets.mv.js';
|
|||||||
import sanitizeFilename from 'sanitize-filename';
|
import sanitizeFilename from 'sanitize-filename';
|
||||||
import asyncHandler from 'express-async-handler';
|
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 } from '../shared/helpers.js';
|
import { splitTextStyleAndMetadata,
|
||||||
|
simulateRender } from '../shared/helpers.js';
|
||||||
|
|
||||||
//==== Middleware Imports ====//
|
//==== Middleware Imports ====//
|
||||||
import contentNegotiation from './middleware/content-negotiation.js';
|
import contentNegotiation from './middleware/content-negotiation.js';
|
||||||
@@ -47,6 +48,14 @@ const sanitizeBrew = (brew, accessType)=>{
|
|||||||
return brew;
|
return brew;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const encodeRFC3986ValueChars = (str)=>{
|
||||||
|
return (
|
||||||
|
encodeURIComponent(str)
|
||||||
|
.replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
app.set('trust proxy', 1 /* number of proxies between user and server */);
|
app.set('trust proxy', 1 /* number of proxies between user and server */);
|
||||||
|
|
||||||
app.use('/', serveCompressedStaticAssets(`build`));
|
app.use('/', serveCompressedStaticAssets(`build`));
|
||||||
@@ -231,19 +240,32 @@ 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)=>{
|
||||||
const { brew } = req;
|
const { brew } = req;
|
||||||
sanitizeBrew(brew, 'share');
|
sanitizeBrew(brew, 'share');
|
||||||
const prefix = 'HB - ';
|
const prefix = 'HB - ';
|
||||||
|
|
||||||
const encodeRFC3986ValueChars = (str)=>{
|
|
||||||
return (
|
|
||||||
encodeURIComponent(str)
|
|
||||||
.replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
|
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
|
||||||
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
|
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
|
||||||
res.set({
|
res.set({
|
||||||
|
|||||||
+105
-1
@@ -1,6 +1,14 @@
|
|||||||
|
/* eslint-disable max-lines */
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import request from '../client/homebrew/utils/request-middleware.js';
|
import request from '../client/homebrew/utils/request-middleware.js';
|
||||||
|
import Markdown from '../shared/markdown.js';
|
||||||
|
import packageJSON from '../package.json' with { type: 'json' };
|
||||||
|
|
||||||
|
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
|
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
|
||||||
|
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
|
||||||
|
|
||||||
|
|
||||||
// Convert the templates from a brew to a Snippets Structure.
|
// Convert the templates from a brew to a Snippets Structure.
|
||||||
const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=null, full=true)=>{
|
const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=null, full=true)=>{
|
||||||
@@ -168,10 +176,106 @@ 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 `<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 \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 `<div class=${classes} id=p${index} key=${index} style=${styles} ${attributes}>\n${html}\n</div>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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)=>`<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 {
|
||||||
splitTextStyleAndMetadata,
|
splitTextStyleAndMetadata,
|
||||||
printCurrentBrew,
|
printCurrentBrew,
|
||||||
fetchThemeBundle,
|
fetchThemeBundle,
|
||||||
brewSnippetsToJSON,
|
brewSnippetsToJSON,
|
||||||
debugTextMismatch
|
debugTextMismatch,
|
||||||
|
simulateRender,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user