0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-07 03:22:38 +00:00

Merge branch 'master' into pr/3132

This commit is contained in:
Trevor Buckner
2023-12-04 22:26:05 -05:00
10 changed files with 744 additions and 651 deletions

View File

@@ -1,9 +1,8 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ /*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./brewRenderer.less'); require('./brewRenderer.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const { useState, useRef, useEffect } = React;
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js'); const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
@@ -13,244 +12,214 @@ const ErrorBar = require('./errorBar/errorBar.jsx');
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx'); const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
const NotificationPopup = require('./notificationPopup/notificationPopup.jsx'); const NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
const Frame = require('react-frame-component').default; const Frame = require('react-frame-component').default;
const dedent = require('dedent-tabs').default;
const Themes = require('themes/themes.json'); const Themes = require('themes/themes.json');
const PAGE_HEIGHT = 1056; const PAGE_HEIGHT = 1056;
const PPR_THRESHOLD = 50;
const BrewRenderer = createClass({ const INITIAL_CONTENT = dedent`
displayName : 'BrewRenderer', <!DOCTYPE html><html><head>
getDefaultProps : function() { <link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
return { <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
text : '', <link href='/homebrew/bundle.css' rel='stylesheet' />
style : '', <base target=_blank>
renderer : 'legacy', </head><body style='overflow: hidden'><div></div></body></html>`;
theme : '5ePHB',
lang : '',
errors : []
};
},
getInitialState : function() {
let pages;
if(this.props.renderer == 'legacy') {
pages = this.props.text.split('\\page');
} else {
pages = this.props.text.split(/^\\page$/gm);
}
return { //v=====----------------------< Brew Page Component >---------------------=====v//
viewablePageNumber : 0, const BrewPage = (props)=>{
height : 0, props = {
isMounted : false, contents : '',
index : 0,
...props
};
return <div className={props.className} id={`p${props.index + 1}`} >
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: props.contents }} />
</div>;
};
pages : pages,
usePPR : pages.length >= PPR_THRESHOLD,
visibility : 'hidden',
initialContent : `<!DOCTYPE html><html><head>
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href='/homebrew/bundle.css' rel='stylesheet' />
<base target=_blank>
</head><body style='overflow: hidden'><div></div></body></html>`
};
},
height : 0,
lastRender : <div></div>,
componentWillUnmount : function() { //v=====--------------------< Brew Renderer Component >-------------------=====v//
window.removeEventListener('resize', this.updateSize); const renderedPages = [];
}, let rawPages = [];
componentDidUpdate : function(prevProps) { const BrewRenderer = (props)=>{
if(prevProps.text !== this.props.text) { props = {
let pages; text : '',
if(this.props.renderer == 'legacy') { style : '',
pages = this.props.text.split('\\page'); renderer : 'legacy',
} else { theme : '5ePHB',
pages = this.props.text.split(/^\\page$/gm); lang : '',
} errors : [],
this.setState({ ...props
pages : pages, };
usePPR : pages.length >= PPR_THRESHOLD
});
}
},
updateSize : function() { const [state, setState] = useState({
this.setState({ viewablePageNumber : 0,
height : this.refs.main.parentNode.clientHeight, height : PAGE_HEIGHT,
}); isMounted : false,
}, visibility : 'hidden',
});
handleScroll : function(e){ const mainRef = useRef(null);
const target = e.target;
this.setState((prevState)=>({ if(props.renderer == 'legacy') {
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * prevState.pages.length) rawPages = props.text.split('\\page');
} else {
rawPages = props.text.split(/^\\page$/gm);
}
useEffect(()=>{ // Unmounting steps
return ()=>{window.removeEventListener('resize', updateSize);};
}, []);
const updateSize = ()=>{
setState((prevState)=>({
...prevState,
height : mainRef.current.parentNode.clientHeight,
})); }));
}, };
shouldRender : function(pageText, index){ const handleScroll = (e)=>{
if(!this.state.isMounted) return false; const target = e.target;
setState((prevState)=>({
...prevState,
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * rawPages.length)
}));
};
const viewIndex = this.state.viewablePageNumber; const shouldRender = (index)=>{
if(index == viewIndex - 3) return true; if(!state.isMounted) return false;
if(index == viewIndex - 2) return true;
if(index == viewIndex - 1) return true;
if(index == viewIndex) return true;
if(index == viewIndex + 1) return true;
if(index == viewIndex + 2) return true;
if(index == viewIndex + 3) return true;
//Check for style tages if(Math.abs(index - state.viewablePageNumber) <= 3)
if(pageText.indexOf('<style>') !== -1) return true; return true;
return false; return false;
}, };
sanitizeScriptTags : function(content) { const sanitizeScriptTags = (content)=>{
return content return content
.replace(/<script/ig, '&lt;script') .replace(/<script/ig, '&lt;script')
.replace(/<\/script>/ig, '&lt;/script&gt;'); .replace(/<\/script>/ig, '&lt;/script&gt;');
}, };
renderPageInfo : function(){ const renderPageInfo = ()=>{
return <div className='pageInfo' ref='main'> return <div className='pageInfo' ref={mainRef}>
<div> <div>
{this.props.renderer} {props.renderer}
</div> </div>
<div> <div>
{this.state.viewablePageNumber + 1} / {this.state.pages.length} {state.viewablePageNumber + 1} / {rawPages.length}
</div> </div>
</div>; </div>;
}, };
renderPPRmsg : function(){ const renderDummyPage = (index)=>{
if(!this.state.usePPR) return;
return <div className='ppr_msg'>
Partial Page Renderer is enabled, because your brew is so large. May affect rendering.
</div>;
},
renderDummyPage : function(index){
return <div className='phb page' id={`p${index + 1}`} key={index}> return <div className='phb page' id={`p${index + 1}`} key={index}>
<i className='fas fa-spinner fa-spin' /> <i className='fas fa-spinner fa-spin' />
</div>; </div>;
}, };
renderStyle : function() { const renderStyle = ()=>{
if(!this.props.style) return; if(!props.style) return;
const cleanStyle = this.sanitizeScriptTags(this.props.style); const cleanStyle = sanitizeScriptTags(props.style);
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.sanitizeScriptTags(this.props.style)}\n} </style>` }} />; //return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${sanitizeScriptTags(props.style)}\n} </style>` }} />;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${cleanStyle} </style>` }} />; return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${cleanStyle} </style>` }} />;
}, };
renderPage : function(pageText, index){ const renderPage = (pageText, index)=>{
let cleanPageText = this.sanitizeScriptTags(pageText); let cleanPageText = sanitizeScriptTags(pageText);
if(this.props.renderer == 'legacy') if(props.renderer == 'legacy') {
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(cleanPageText) }} key={index} />; const html = MarkdownLegacy.render(cleanPageText);
else { return <BrewPage className='page phb' index={index} key={index} contents={html} />;
} else {
cleanPageText += `\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) cleanPageText += `\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)
return ( const html = Markdown.render(cleanPageText);
<div className='page' id={`p${index + 1}`} key={index} > return <BrewPage className='page' index={index} key={index} contents={html} />;
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(cleanPageText) }} />
</div>
);
} }
}, };
renderPages : function(){ const renderPages = ()=>{
if(this.state.usePPR){ if(props.errors && props.errors.length)
return _.map(this.state.pages, (page, index)=>{ return renderedPages;
if(this.shouldRender(page, index) && typeof window !== 'undefined'){
return this.renderPage(page, index); _.forEach(rawPages, (page, index)=>{
} else { if((shouldRender(index) || !renderedPages[index]) && typeof window !== 'undefined'){
return this.renderDummyPage(index); renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
}
});
}
if(this.props.errors && this.props.errors.length) return this.lastRender;
this.lastRender = _.map(this.state.pages, (page, index)=>{
if(typeof window !== 'undefined') {
return this.renderPage(page, index);
} else {
return this.renderDummyPage(index);
} }
}); });
return this.lastRender; return renderedPages;
}, };
frameDidMount : function(){ //This triggers when iFrame finishes internal "componentDidMount" const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
this.updateSize(); updateSize();
window.addEventListener('resize', this.updateSize); window.addEventListener('resize', updateSize);
this.renderPages(); //Make sure page is renderable before showing renderPages(); //Make sure page is renderable before showing
this.setState({ setState((prevState)=>({
...prevState,
isMounted : true, isMounted : true,
visibility : 'visible' visibility : 'visible'
}); }));
}, 100); }, 100);
}, };
emitClick : function(){ const emitClick = ()=>{ // Allow clicks inside iFrame to interact with dropdowns, etc. from outside
// console.log('iFrame clicked');
if(!window || !document) return; if(!window || !document) return;
document.dispatchEvent(new MouseEvent('click')); document.dispatchEvent(new MouseEvent('click'));
}, };
render : function(){ const rendererPath = props.renderer == 'V3' ? 'V3' : 'Legacy';
//render in iFrame so broken code doesn't crash the site. const themePath = props.theme ?? '5ePHB';
//Also render dummy page while iframe is mounting. const baseThemePath = Themes[rendererPath][themePath].baseTheme;
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
const themePath = this.props.theme ?? '5ePHB'; return (
const baseThemePath = Themes[rendererPath][themePath].baseTheme; <>
return ( {/*render dummy page while iFrame is mounting.*/}
<React.Fragment> {!state.isMounted
{!this.state.isMounted ? <div className='brewRenderer' onScroll={handleScroll}>
? <div className='brewRenderer' onScroll={this.handleScroll}> <div className='pages'>
<div className='pages' ref='pages'> {renderDummyPage(1)}
{this.renderDummyPage(1)}
</div>
</div> </div>
: null} </div>
: null}
<Frame id='BrewRenderer' initialContent={this.state.initialContent} {/*render in iFrame so broken code doesn't crash the site.*/}
style={{ width: '100%', height: '100%', visibility: this.state.visibility }} <Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
contentDidMount={this.frameDidMount} style={{ width: '100%', height: '100%', visibility: state.visibility }}
onClick={()=>{this.emitClick();}} contentDidMount={frameDidMount}
> onClick={()=>{emitClick();}}
<div className={'brewRenderer'} >
onScroll={this.handleScroll} <div className={'brewRenderer'}
style={{ height: this.state.height }}> onScroll={handleScroll}
style={{ height: state.height }}>
<ErrorBar errors={this.props.errors} /> <ErrorBar errors={props.errors} />
<div className='popups'> <div className='popups'>
<RenderWarnings /> <RenderWarnings />
<NotificationPopup /> <NotificationPopup />
</div>
<link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/>
{baseThemePath &&
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
}
<link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/>
{/* Apply CSS from Style tab and render pages from Markdown tab */}
{this.state.isMounted
&&
<>
{this.renderStyle()}
<div className='pages' ref='pages' lang={`${this.props.lang || 'en'}`}>
{this.renderPages()}
</div>
</>
}
</div> </div>
</Frame> <link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/>
{this.renderPageInfo()} {baseThemePath &&
{this.renderPPRmsg()} <link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
</React.Fragment> }
); <link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/>
}
}); {/* Apply CSS from Style tab and render pages from Markdown tab */}
{state.isMounted
&&
<>
{renderStyle()}
<div className='pages' lang={`${props.lang || 'en'}`}>
{renderPages()}
</div>
</>
}
</div>
</Frame>
{renderPageInfo()}
</>
);
};
module.exports = BrewRenderer; module.exports = BrewRenderer;

View File

@@ -1,46 +1,44 @@
@import (multiple, less) 'shared/naturalcrit/styles/reset.less'; @import (multiple, less) 'shared/naturalcrit/styles/reset.less';
.brewRenderer{ .brewRenderer {
will-change : transform; will-change : transform;
overflow-y : scroll; overflow-y : scroll;
.pages{ .pages {
margin : 30px 0px; margin : 30px 0px;
&>.page{ & > .page {
width : 215.9mm;
height : 279.4mm;
margin-right : auto; margin-right : auto;
margin-bottom : 30px; margin-bottom : 30px;
margin-left : auto; margin-left : auto;
box-shadow : 1px 4px 14px #000; box-shadow : 1px 4px 14px #000000;
} }
} }
} }
.pane{ .pane { position : relative; }
position : relative; .pageInfo {
}
.pageInfo{
position : absolute; position : absolute;
right : 17px; right : 17px;
bottom : 0; bottom : 0;
z-index : 1000; z-index : 1000;
background-color : #333;
font-size : 10px; font-size : 10px;
font-weight : 800; font-weight : 800;
color : white; color : white;
background-color : #333333;
div { div {
display: inline-block; display : inline-block;
padding : 8px 10px; padding : 8px 10px;
&:not(:last-child){ &:not(:last-child) { border-right : 1px solid #666666; }
border-right: 1px solid #666;
}
} }
} }
.ppr_msg{ .ppr_msg {
position : absolute; position : absolute;
left : 0px;
bottom : 0; bottom : 0;
left : 0px;
z-index : 1000; z-index : 1000;
padding : 8px 10px; padding : 8px 10px;
background-color : #333;
font-size : 10px; font-size : 10px;
font-weight : 800; font-weight : 800;
color : white; color : white;
background-color : #333333;
} }

View File

@@ -160,6 +160,24 @@ const Editor = createClass({
} }
} }
// Superscript
if(line.includes('\^')) {
const regex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/g;
let match;
while ((match = regex.exec(line)) != null) {
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 1 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 1 }, { className: 'superscript' });
}
}
// Subscript
if(line.includes('^^')) {
const regex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/g;
let match;
while ((match = regex.exec(line)) != null) {
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 2 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 2 }, { className: 'subscript' });
}
}
// Highlight injectors {style} // Highlight injectors {style}
if(line.includes('{') && line.includes('}')){ if(line.includes('{') && line.includes('}')){
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm; const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;

View File

@@ -43,6 +43,18 @@
font-weight : bold; font-weight : bold;
color : green; color : green;
} }
.superscript:not(.cm-comment) {
font-weight : bold;
color : goldenrod;
vertical-align : super;
font-size : 0.9em;
}
.subscript:not(.cm-comment) {
font-weight : bold;
color : rgb(123, 123, 15);
vertical-align : sub;
font-size : 0.9em;
}
} }
.brewJump { .brewJump {

907
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -79,10 +79,10 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.23.2", "@babel/core": "^7.23.5",
"@babel/plugin-transform-runtime": "^7.23.2", "@babel/plugin-transform-runtime": "^7.23.4",
"@babel/preset-env": "^7.23.2", "@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.22.15", "@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.4.0", "@googleapis/drive": "^8.4.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"classnames": "^2.3.2", "classnames": "^2.3.2",
@@ -93,38 +93,37 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7", "express-static-gzip": "2.1.7",
"fs": "^0.0.1-security", "fs-extra": "11.2.0",
"fs-extra": "11.1.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "5.1.1", "marked": "5.1.1",
"marked-extended-tables": "^1.0.7", "marked-extended-tables": "^1.0.7",
"marked-gfm-heading-id": "^3.1.0", "marked-gfm-heading-id": "^3.1.2",
"marked-smartypants-lite": "^1.0.1", "marked-smartypants-lite": "^1.0.1",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4", "moment": "^2.29.4",
"mongoose": "^8.0.0", "mongoose": "^8.0.2",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.1", "nconf": "^0.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-frame-component": "^4.1.3", "react-frame-component": "^4.1.3",
"react-router-dom": "6.18.0", "react-router-dom": "6.20.1",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"superagent": "^8.1.2", "superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.53.0", "eslint": "^8.55.0",
"eslint-plugin-jest": "^27.6.0", "eslint-plugin-jest": "^27.6.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^15.11.0", "stylelint": "^15.11.0",
"stylelint-config-recess-order": "^4.3.0", "stylelint-config-recess-order": "^4.4.0",
"stylelint-config-recommended": "^13.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3", "stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.3" "supertest": "^6.3.3"

View File

@@ -112,6 +112,10 @@ const CodeEditor = createClass({
'Shift-Tab' : this.dedent, 'Shift-Tab' : this.dedent,
'Ctrl-B' : this.makeBold, 'Ctrl-B' : this.makeBold,
'Cmd-B' : this.makeBold, 'Cmd-B' : this.makeBold,
'Shift-Ctrl-=' : this.makeSuper,
'Shift-Cmd-=' : this.makeSuper,
'Ctrl-=' : this.makeSub,
'Cmd-=' : this.makeSub,
'Ctrl-I' : this.makeItalic, 'Ctrl-I' : this.makeItalic,
'Cmd-I' : this.makeItalic, 'Cmd-I' : this.makeItalic,
'Ctrl-U' : this.makeUnderline, 'Ctrl-U' : this.makeUnderline,
@@ -219,6 +223,25 @@ const CodeEditor = createClass({
} }
}, },
makeSuper : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 1) === '^' && selection.slice(-1) === '^';
this.codeMirror.replaceSelection(t ? selection.slice(1, -1) : `^${selection}^`, 'around');
if(selection.length === 0){
const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 1 });
}
},
makeSub : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '^^' && selection.slice(-2) === '^^';
this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `^^${selection}^^`, 'around');
if(selection.length === 0){
const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 2 });
}
},
makeNbsp : function() { makeNbsp : function() {
this.codeMirror.replaceSelection('&nbsp;', 'end'); this.codeMirror.replaceSelection('&nbsp;', 'end');
}, },

View File

@@ -206,6 +206,34 @@ const mustacheInjectBlock = {
} }
}; };
const superSubScripts = {
name : 'superSubScript',
level : 'inline',
start(src) { return src.match(/\^/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const superRegex = /^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/m;
const subRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m;
let isSuper = false;
let match = subRegex.exec(src);
if(!match){
match = superRegex.exec(src);
if(match)
isSuper = true;
}
if(match?.length) {
return {
type : 'superSubScript', // Should match "name" above
raw : match[0], // Text to consume from the source
tag : isSuper ? 'sup' : 'sub',
tokens : this.lexer.inlineTokens(match[1])
};
}
},
renderer(token) {
return `<${token.tag}>${this.parser.parseInline(token.tokens)}</${token.tag}>`;
}
};
const definitionLists = { const definitionLists = {
name : 'definitionLists', name : 'definitionLists',
level : 'block', level : 'block',
@@ -238,7 +266,7 @@ const definitionLists = {
} }
}; };
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] }); Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
Marked.use(mustacheInjectBlock); Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, mangle: false }); Marked.use({ renderer: renderer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite()); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());

View File

@@ -40,7 +40,7 @@ body {
-webkit-column-gap : 1cm; -webkit-column-gap : 1cm;
-moz-column-gap : 1cm; -moz-column-gap : 1cm;
} }
.phb{ .phb, .page{
.useColumns(); .useColumns();
counter-increment : phb-page-numbers; counter-increment : phb-page-numbers;
position : relative; position : relative;
@@ -59,6 +59,9 @@ body {
page-break-before : always; page-break-before : always;
page-break-after : always; page-break-after : always;
contain : size; contain : size;
}
.phb{
//***************************** //*****************************
// * BASE // * BASE
// *****************************/ // *****************************/

View File

@@ -443,6 +443,8 @@ body { counter-reset : phb-page-numbers; }
tr { background-color : transparent; } tr { background-color : transparent; }
td,th { padding : 0px; } td,th { padding : 0px; }
} }
//indent fix after bulleted lists
:is(ul,ol) + p { text-indent : 0; }
:last-child { margin-bottom : 0; } :last-child { margin-bottom : 0; }
} }