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

Merge branch 'master' of https://github.com/naturalcrit/homebrewery into update-Codemirror

This commit is contained in:
Víctor Losada Hernández
2026-04-06 19:21:46 +02:00
21 changed files with 157 additions and 376 deletions
+28 -2
View File
@@ -85,14 +85,40 @@ pre {
}
.page .df {
font-size: 2em;
vertical-align: middle;
font-size: 2em;
vertical-align: middle;
}
```
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Saturday 4/04/2026 - v3.21.0
{{taskList
##### Gazook89
* [x] Allow custom {{openSans **:fas_table_list: SNIPPETS**}} to be inserted mid-line
##### abquintic
* [x] Move example snippet images out of imgur (for folks without imgur access)
##### 5e-Cleric
* [x] Add auto-suggest to tag entry input box
* [x] Replace all example artwork with
* [x] Added tooltips to the {{openSans :fas_circle_info: **Properties**}} menu
* [x] Removed {{openSans **SYSTEMS**}} checkboxes from {{openSans :fas_circle_info: **Properties**}} menu; instead {{openSans **TAGS**}} should be used for this purpose
* [x] Replace all AI-generated art with public domain art
* [x] Major backend refactor to use Vite
##### A1Asriel (new contributor!)
* [x] Add fix for column breaks on Firefox
Fixes issues [#543](https://github.com/naturalcrit/homebrewery/issues/543), [#2473](https://github.com/naturalcrit/homebrewery/issues/2473), [#3712](https://github.com/naturalcrit/homebrewery/issues/3712)
##### G-Ambatte, abquintic, 5e-Cleric
* [x] Multiple other backend fixes and refactors
}}
### Friday 1/11/2026 - v3.20.1
{{taskList
@@ -11,7 +11,6 @@ import {
scrollPastEnd,
Decoration,
ViewPlugin,
WidgetType,
drawSelection,
dropCursor,
} from '@codemirror/view';
+33 -54
View File
@@ -33,7 +33,7 @@ const INITIAL_CONTENT = dedent`
<link href='/homebrew/bundle.css' type="text/css" rel='stylesheet' />
<link href="${brewRendererStylesUrl}" rel="stylesheet" />
<link href="${headerNavStylesUrl}" rel="stylesheet" />
<base target=_blank>
<base target="_top">
</head><body style='overflow: hidden'><div></div></body></html>`;
@@ -106,7 +106,6 @@ const BrewRenderer = (props)=>{
currentBrewRendererPageNum : 1,
themeBundle : {},
onPageChange : ()=>{},
showToolbar : true,
...props
};
@@ -272,6 +271,14 @@ const BrewRenderer = (props)=>{
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
scrollToHash(window.location.hash);
navigation.addEventListener('navigate', (e)=>{
if(e.hashChange && e.destination.sameDocument){
const dest = e.destination.url.slice(e.destination.url.indexOf('#'));
scrollToHash(dest);
}
});
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
renderPages(); //Make sure page is renderable before showing
setState((prevState)=>({
@@ -301,53 +308,6 @@ const BrewRenderer = (props)=>{
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]);
const toolbarEl = <ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>;
const brewRenderFrameContents = (
<>
<div className='brewRenderer'
onKeyDown={handleControlKeys}
tabIndex={-1}
>
{/* Apply CSS from Style tab and render pages from Markdown tab */}
{state.isMounted
&&
<>
{renderedStyle}
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle} ref={pagesRef}>
{renderedPages}
</div>
</>
}
</div>
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
</>
);
const brewRenderFrameWrapper = (
<>
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
style={{ width: '100%', height: '100%', visibility: state.visibility }}
contentDidMount={frameDidMount}
onClick={()=>{emitClick();}}
>
{brewRenderFrameContents}
</Frame>
</>
);
const brewRenderDivWrapper = (
<>
<div id='BrewRendererFlat'
style={{ width: '100%', height: '100%', visibility: state.visibility }}
>
{brewRenderFrameContents}
</div>
</>
);
if (!props.showToolbar && state.visibility != 'visible') { frameDidMount(); }
return (
<>
{/*render dummy page while iFrame is mounting.*/}
@@ -365,13 +325,32 @@ const BrewRenderer = (props)=>{
<NotificationPopup />
</div>
{props.showToolbar ? toolbarEl : ''}
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
{/*render in iFrame so broken code doesn't crash the site.*/}
{props.showToolbar ? brewRenderFrameWrapper:brewRenderDivWrapper}
{state.isMounted &&
<div id='brewRendered'></div>
}
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
style={{ width: '100%', height: '100%', visibility: state.visibility }}
contentDidMount={frameDidMount}
onClick={()=>{emitClick();}}
>
<div className='brewRenderer'
onKeyDown={handleControlKeys}
tabIndex={-1}
>
{/* Apply CSS from Style tab and render pages from Markdown tab */}
{state.isMounted
&&
<>
{renderedStyle}
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle} ref={pagesRef}>
{renderedPages}
</div>
</>
}
</div>
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
</Frame>
</>
);
};
@@ -104,7 +104,7 @@ const HeaderNavItem = ({ link, text, depth, className })=>{
if(!link || !text) return;
return <li>
<a href={`#${link}`} target='_self' className={`depth-${depth} ${className ?? ''}`}>
<a href={`#${link}`} className={`depth-${depth} ${className ?? ''}`}>
{trimString(text, depth)}
</a>
</li>;
+29 -3
View File
@@ -266,6 +266,29 @@ const Editor = createReactClass({
this.forceUpdate();
},
//temporary fix until cm6 comes next update
attachCodeMirrorListeners : function(cm) {
if(!cm) return;
// detach previous (important on remount / view switch)
if(this._cm) {
this._cm.off('cursorActivity', this._onCursor);
this._cm.off('scroll', this._onScroll);
}
this._cm = cm;
this._onCursor = ()=>{
this.updateCurrentCursorPage(cm.getCursor());
};
this._onScroll = _.throttle(()=>{
const topLine = cm.lineAtHeight(cm.getScrollInfo().top, 'local');
this.updateCurrentViewPage(topLine);
}, 200);
cm.on('cursorActivity', this._onCursor);
cm.on('scroll', this._onScroll);
},
renderEditor : function(){
if(this.isText()){
return <>
@@ -281,7 +304,8 @@ const Editor = createReactClass({
editorTheme={this.state.editorTheme}
renderer={this.props.brew.renderer}
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} />
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}
onReady={this.attachCodeMirrorListeners}/>
</>;
}
if(this.isStyle()){
@@ -297,7 +321,8 @@ const Editor = createReactClass({
editorTheme={this.state.editorTheme}
renderer={this.props.brew.renderer}
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} />
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }}
onReady={this.attachCodeMirrorListeners}/>
</>;
}
if(this.isMeta()){
@@ -328,7 +353,8 @@ const Editor = createReactClass({
editorTheme={this.state.editorTheme}
renderer={this.props.brew.renderer}
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - 25px)` }} />
style={{ height: `calc(100% - 25px)` }}
onReady={this.attachCodeMirrorListeners}/>
</>;
}
},
+1 -3
View File
@@ -9,7 +9,6 @@ import HomePage from './pages/homePage/homePage.jsx';
import EditPage from './pages/editPage/editPage.jsx';
import UserPage from './pages/userPage/userPage.jsx';
import SharePage from './pages/sharePage/sharePage.jsx';
import EmbedPage from './pages/embedPage/embedPage.jsx';
import NewPage from './pages/newPage/newPage.jsx';
import ErrorPage from './pages/errorPage/errorPage.jsx';
import VaultPage from './pages/vaultPage/vaultPage.jsx';
@@ -71,8 +70,7 @@ const Homebrew = (props)=>{
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
<Routes>
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} share={true} />} />
<Route path='/embed/:id' element={<WithRoute el={EmbedPage} brew={brew} share={false} />} />
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} />} />
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={brew} userThemes={userThemes}/>} />
<Route path='/new' element={<WithRoute el={NewPage} userThemes={userThemes}/> } />
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={brews} />} />
-9
View File
@@ -1,9 +0,0 @@
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>;
};
+5 -16
View File
@@ -1,20 +1,9 @@
import React from 'react';
import Nav from './nav.jsx';
import { printCurrentBrew, scrapeBrewHTML, scrapeBrewZip } from '@shared/helpers.js';
import { printCurrentBrew } from '@shared/helpers.js';
export default function(props){
return <Nav.dropdown>
<Nav.item color='grey' icon='fas fa-question-circle'>
export
</Nav.item>
<Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
get PDF
</Nav.item>
<Nav.item onClick={scrapeBrewHTML} color='orange' icon='fas fa-file-code'>
get HTML
</Nav.item>
<Nav.item onClick={scrapeBrewZip} color='orange' icon='fas fa-file-archive'>
get HTML (Zip)
</Nav.item>
</Nav.dropdown>;
export default function(){
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
get PDF
</Nav.item>;
};
+1 -1
View File
@@ -362,7 +362,7 @@ const EditPage = (props)=>{
{renderAutoSaveButton()}
</Nav.dropdown>}
<NewBrewItem />
<PrintNavItem brew={currentBrew}/>
<PrintNavItem />
<HelpNavItem />
<VaultNavItem />
<ShareNavItem brew={currentBrew} />
@@ -1,162 +0,0 @@
import './embedPage.less';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import Headtags from '../../../../vitreum/headtags.js';
import MarkdownLegacy from '@shared/markdownLegacy.js';
import Markdown from '@shared/markdown.js';
const Meta = Headtags.Meta;
import Nav from '@navbar/nav.jsx';
import Navbar from '@navbar/navbar.jsx';
import MetadataNav from '@navbar/metadata.navitem.jsx';
import PrintNavItem from '@navbar/print.navitem.jsx';
import RecentNavItems from '@navbar/recent.navitem.jsx';
const { both: RecentNavItem } = RecentNavItems;
import Account from '@navbar/account.navitem.jsx';
import safeHTML from '../../brewRenderer/safeHTML.js';
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
import _ from 'lodash';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
let renderedPages = [];
let rawPages = [];
const BrewPage = (props)=>{
props = {
contents : '',
index : 0,
...props
};
const pageRef = useRef(null);
const cleanText = safeHTML(props.contents);
return <div className={props.className} id={`p${props.index + 1}`} data-index={props.index} ref={pageRef} style={props.style} {...props.attributes}>
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
</div>;
};
const EmbedPage = (props)=>{
const [displayOptions, setDisplayOptions] = useState({
zoomLevel : 100,
spread : 'single',
startOnRight : true,
pageShadows : true,
rowGap : 5,
columnGap : 10,
});
if(props.renderer == 'legacy') {
rawPages = props.brew.text.split(PAGEBREAK_REGEX_LEGACY);
} else {
rawPages = props.brew.text.split(PAGEBREAK_REGEX_V3);
}
const pagesStyle = {
zoom : `${displayOptions.zoomLevel}%`,
columnGap : `${displayOptions.columnGap}px`,
rowGap : `${displayOptions.rowGap}px`,
overflowY : 'auto'
};
const { brew = DEFAULT_BREW_LOAD, disableMeta = false, share = true } = props;
const [themeBundle, setThemeBundle] = useState({});
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
setCurrentBrewRendererPageNum(pageNumber);
}, []);
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
const P_KEY = 80;
if(e.keyCode === P_KEY) {
printCurrentBrew();
e.stopPropagation();
e.preventDefault();
}
};
useEffect(()=>{
document.addEventListener('keydown', handleControlKeys);
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
};
}, []);
const renderStyle = ()=>{
const themeStyles = themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
const cleanStyle = safeHTML(`${themeStyles} \n\n <style> ${props.brew.style} </style>`);
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: cleanStyle }} />;
};
const renderPage = (pageText, index)=>{
let styles = {
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
// Add more conditions as needed
};
let classes = 'page';
let attributes = {};
if(props.renderer == 'legacy') {
pageText.replace(COLUMNBREAK_REGEX_LEGACY, '```\n````\n'); // Allow Legacy brews to use `\column(break)`
const html = MarkdownLegacy.render(pageText);
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} />;
} 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)); // 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 <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} />;
}
};
const renderPages = ()=>{
if(props.errors && props.errors.length)
return renderedPages;
renderedPages.length = 0;
_.forEach(rawPages, (page, index)=>{
{
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
}
});
return renderedPages;
};
return (
<div>
<Meta name='robots' content='noindex, nofollow' />
{renderStyle()}
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle}>
{renderPages()}
</div>
</div>
);
};
export default EmbedPage;
@@ -1,8 +0,0 @@
.page {
page-break-before : auto;
page-break-after : auto;
}
.homebrew:not(:has(>.sitePage)) {
background: unset !important;
}
+1 -1
View File
@@ -182,7 +182,7 @@ const HomePage =(props)=>{
? <ErrorNavItem error={error} clearError={clearError} />
: renderSaveButton()}
<NewBrewItem />
<PrintNavItem brew={currentBrew}/>
<PrintNavItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem />
+32 -36
View File
@@ -16,7 +16,7 @@ import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
const SharePage = (props)=>{
const { brew = DEFAULT_BREW_LOAD, disableMeta = false, share = true } = props;
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
const [themeBundle, setThemeBundle] = useState({});
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
@@ -66,43 +66,40 @@ const SharePage = (props)=>{
</Nav.item>
);
const showNav = (
<Navbar>
<Nav.section className='titleSection'>
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
</Nav.section>
<Nav.section>
{brew.shareId && (
<>
<PrintNavItem brew={brew}/>
<Nav.dropdown>
<Nav.item color='red' icon='fas fa-code'>
source
</Nav.item>
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${processShareId()}`}>
view
</Nav.item>
{renderEditLink()}
<Nav.item color='blue' icon='fas fa-download' href={`/download/${processShareId()}`}>
download
</Nav.item>
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${processShareId()}`}>
clone to new
</Nav.item>
</Nav.dropdown>
</>
)}
<RecentNavItem brew={brew} storageKey='view' />
<Account />
</Nav.section>
</Navbar>
);
return (
<div className='sharePage sitePage'>
<Meta name='robots' content='noindex, nofollow' />
{share ? showNav : ''}
<Navbar>
<Nav.section className='titleSection'>
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
</Nav.section>
<Nav.section>
{brew.shareId && (
<>
<PrintNavItem />
<Nav.dropdown>
<Nav.item color='red' icon='fas fa-code'>
source
</Nav.item>
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${processShareId()}`}>
view
</Nav.item>
{renderEditLink()}
<Nav.item color='blue' icon='fas fa-download' href={`/download/${processShareId()}`}>
download
</Nav.item>
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${processShareId()}`}>
clone to new
</Nav.item>
</Nav.dropdown>
</>
)}
<RecentNavItem brew={brew} storageKey='view' />
<Account />
</Nav.section>
</Navbar>
<div className='content'>
<BrewRenderer
text={brew.text}
@@ -114,7 +111,6 @@ const SharePage = (props)=>{
onPageChange={handleBrewRendererPageChange}
currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true}
showToolbar={share}
/>
</div>
</div>
+4 -4
View File
@@ -25,18 +25,18 @@
let prefix = '';
if (url.includes('://homebrewery-stage.')) {
if (url && url?.includes('://homebrewery-stage.')) {
prefix = `Stage `;
} else if (url.includes('://homebrewery-pr-')) {
} else if (url?.includes('://homebrewery-pr-')) {
const match = url.match(/pr-(\d+)/);
if (match) prefix = `PR-${match[1]} `;
} else if (url.includes('://localhost')) {
} else if (url?.includes('://localhost')) {
prefix = 'Local ';
}
if (title) {
document.title = `${prefix} - ${title} - The Homebrewery`;
} else {
} else if (prefix) {
document.title = `${prefix} - The Homebrewery`;
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.20.1",
"version": "3.21.0",
"type": "module",
"engines": {
"npm": ">=10.8 <12",
+5 -16
View File
@@ -12,7 +12,7 @@ import _ from 'lodash';
import jwt from 'jwt-simple';
import express from 'express';
import config from './config.js';
import path from 'path';
import path from 'path';
import fs from 'fs-extra';
import api from './homebrew.api.js';
@@ -80,9 +80,10 @@ export default async function createApp(vite) {
const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app
if(!origin || origin === 'null' || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) {
if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) {
callback(null, true);
} else {
console.log(origin, 'not allowed');
callback(new Error('Not allowed by CORS, if you think this is an error, please contact us'));
}
},
@@ -437,9 +438,8 @@ export default async function createApp(vite) {
return next();
}));
const shareEmbedCommon = async(req, res)=>{
//Share Page
app.get('/share/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
const { brew } = req;
req.ogMeta = { ...defaultMetaTags,
title : `${req.brew.title || 'Untitled Brew'} - ${req.brew.authors[0] || 'No author.'}`,
@@ -462,17 +462,6 @@ export default async function createApp(vite) {
brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share');
splitTextStyleAndMetadata(req.brew);
};
//Share Page
app.get('/share/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
await shareEmbedCommon(req,res);
return next();
}));
//Embed Page - More work will be done on this later...
app.get('/embed/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
await shareEmbedCommon(req,res);
return next();
}));
+2 -45
View File
@@ -1,14 +1,6 @@
/* eslint-disable max-lines */
import _ from 'lodash';
import yaml from 'js-yaml';
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.
const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=null, full=true)=>{
@@ -138,7 +130,7 @@ const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{
const themeBundle = res.body;
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
setThemeBundle(themeBundle);
if(setError) { setError(null); }
setError(null);
};
const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
@@ -176,45 +168,10 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
}
};
const scrapeBrew = ()=>{
const htmlBody = `<html>\n${window.frames['BrewRenderer'].contentDocument.documentElement.innerHTML}\n</html>`;
return htmlBody;
};
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!
};
const scrapeBrewHTML = ()=>{
const htmlBody = scrapeBrew();
// Manipulate the body to change all relative path references to full URLs
downloadBlob(htmlBody, 'testDownload.html');
};
export {
splitTextStyleAndMetadata,
printCurrentBrew,
fetchThemeBundle,
brewSnippetsToJSON,
debugTextMismatch,
scrapeBrewHTML,
scrapeBrewZip,
debugTextMismatch
};
+3 -3
View File
@@ -70,9 +70,9 @@ renderer.link = function (token) {
if(title) {
out += ` title="${escape(title)}"`;
}
if(self) {
out += ' target="_self"';
}
// if(self) {
// out += ' target="_self"';
// }
out += `>${text}</a>`;
return out;
};
+3 -3
View File
@@ -34,9 +34,9 @@ renderer.link = function (href, title, text) {
if(title) {
out += ` title="${title}"`;
}
if(self) {
out += ' target="_self"';
}
// if(self) {
// out += ' target="_self"';
// }
out += `>${text}</a>`;
return out;
};
+7 -5
View File
@@ -8,8 +8,10 @@ test('Processes the markdown within an HTML block if its just a class wrapper',
expect(rendered).toBe('<div> <p><em>Bold text</em></p>\n </div>');
});
test('Check markdown is using the custom renderer; specifically that it adds target=_self attribute to internal links in HTML blocks', function() {
const source = '<div>[Has _self Attribute?](#p1)</div>';
const rendered = Markdown.render(source);
expect(rendered).toBe('<div> <p><a href="#p1" target="_self">Has _self Attribute?</a></p>\n </div>');
});
// TEST REMOVED AS IT IS NO LONGER REQUIRED
//
// test('Check markdown is using the custom renderer; specifically that it adds target=_self attribute to internal links in HTML blocks', function() {
// const source = '<div>[Has _self Attribute?](#p1)</div>';
// const rendered = Markdown.render(source);
// expect(rendered).toBe('<div> <p><a href="#p1" target="_self">Has _self Attribute?</a></p>\n </div>');
// });
-1
View File
@@ -34,6 +34,5 @@ export default defineConfig({
fs : {
allow : ['.'],
},
allowedHosts : ['fedora.copy.to']
},
});