0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-25 14:02:40 +00:00

Merge branch 'master' of https://github.com/naturalcrit/homebrewery into clear-invalid-or-unused-css

This commit is contained in:
Víctor Losada Hernández
2023-12-07 21:32:40 +01:00
26 changed files with 2293 additions and 5008 deletions

View File

@@ -5,12 +5,12 @@
version: 2.1
orbs:
node: circleci/node@3.0.0
node: circleci/node@5.1.0
jobs:
build:
docker:
- image: cimg/node:16.11.0
- image: cimg/node:20.8.0
- image: mongo:4.4
working_directory: ~/homebrewery
@@ -27,7 +27,7 @@ jobs:
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: sudo npm install -g npm@8.10.0
- run: sudo npm install -g npm@10.2.0
- node/install-packages:
app-dir: ~/homebrewery
cache-path: node_modules
@@ -45,7 +45,7 @@ jobs:
test:
docker:
- image: cimg/node:16.11.0
- image: cimg/node:20.8.0
working_directory: ~/homebrewery
parallelism: 1

View File

@@ -80,6 +80,56 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Friday 13/10/2023 - v3.10.0
{{taskList
##### G-Ambatte
* [x] Fix user preferred save location being ignored
Fixes issue [#2993](https://github.com/naturalcrit/homebrewery/issues/2993)
* [x] Fix crash to white screen when starting new brews while not signed in
Fixes issue [#2999](https://github.com/naturalcrit/homebrewery/issues/2999)
* [x] Fix FreeBSD install script
Fixes issue [#3005](https://github.com/naturalcrit/homebrewery/issues/3005)
* [x] Fix *"This brew has been changed on another device"* triggering when manually saving during auto-save
Fixes issue [#2641](https://github.com/naturalcrit/homebrewery/issues/2641)
* [x] Fix Firefox different column-flow behavior
Fixes issue [#2982](https://github.com/naturalcrit/homebrewery/issues/2982)
* [x] Fix brew titles being mis-sorted on user page
Fixes issue [#2775](https://github.com/naturalcrit/homebrewery/issues/2775)
* [x] Text Editor themes now available via new drop-down
Fixes issue [#362](https://github.com/naturalcrit/homebrewery/issues/362)
##### 5e-Cleric
* [x] New {{openSans **PHB → {{fas,fa-quote-right}} QUOTE** }} snippet for V3!
Fixes issue [#2920](https://github.com/naturalcrit/homebrewery/issues/2920)
* [x] Several updates and fixes to FAQ and Welcome page
Fixes issue [#2729](https://github.com/naturalcrit/homebrewery/issues/2729),
[#2787](https://github.com/naturalcrit/homebrewery/issues/2787)
##### Gazook89
* [x] Add syntax highlighting for Definition Lists <code>:\:</code>
}}
### Thursday 17/08/2023 - v3.9.2
{{taskList
@@ -119,14 +169,15 @@ Fixes issue [#2943](https://github.com/naturalcrit/homebrewery/issues/2943)
* [x] Exclude cover pages from Table of Content generation (editing on mobile is still not recommended)
Fixes issue [#2920](https://github.com/naturalcrit/homebrewery/issues/2920)
Fixes issue [#2920](https://github.com/naturalcrit/homebrewery/issues/2920)
##### Gazook89
* [x] Adjustments to improve mobile viewing
}}
### Wednesday 28/06/2023 - v3.9.1
{{taskList
@@ -171,6 +222,8 @@ Fixes issue [#2790](https://github.com/naturalcrit/homebrewery/issues/2790)
Fixes issue [#2784](https://github.com/naturalcrit/homebrewery/issues/2784)
}}
\page
### Wednesday 12/04/2023 - v3.8.0
{{taskList
@@ -232,8 +285,6 @@ Fixes issues [#2731](https://github.com/naturalcrit/homebrewery/issues/2731)
}}
\page
### Monday 13/03/2023 - v3.7.2
{{taskList
@@ -314,7 +365,11 @@ Fixes issues [#2603](https://github.com/naturalcrit/homebrewery/issues/2603)
* [x] Add message to refresh the browser if the user is missing an update to the Homebrewery
Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
}}
\page
{{taskList
##### G-Ambatte
* [x] Auto-compile Themes CSS on development server
@@ -324,7 +379,6 @@ Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
* [x] Fix cloned brews inheriting the parent view count
}}
\page
### Friday 23/12/2022 - v3.5.0
{{taskList
@@ -1408,4 +1462,4 @@ Massive changelog incoming:
* Added `phb.standalone.css` plus a build system for creating it
* Added page numbers and footer text
* Page accent now flips each page
* Page accent now flips each page

View File

@@ -1,9 +1,8 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./brewRenderer.less');
const React = require('react');
const createClass = require('create-react-class');
const { useState, useRef, useEffect } = React;
const _ = require('lodash');
const cx = require('classnames');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.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 NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
const Frame = require('react-frame-component').default;
const dedent = require('dedent-tabs').default;
const Themes = require('themes/themes.json');
const PAGE_HEIGHT = 1056;
const PPR_THRESHOLD = 50;
const BrewRenderer = createClass({
displayName : 'BrewRenderer',
getDefaultProps : function() {
return {
text : '',
style : '',
renderer : 'legacy',
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);
}
const INITIAL_CONTENT = dedent`
<!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>`;
return {
viewablePageNumber : 0,
height : 0,
isMounted : false,
//v=====----------------------< Brew Page Component >---------------------=====v//
const BrewPage = (props)=>{
props = {
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() {
window.removeEventListener('resize', this.updateSize);
},
//v=====--------------------< Brew Renderer Component >-------------------=====v//
const renderedPages = [];
let rawPages = [];
componentDidUpdate : function(prevProps) {
if(prevProps.text !== this.props.text) {
let pages;
if(this.props.renderer == 'legacy') {
pages = this.props.text.split('\\page');
} else {
pages = this.props.text.split(/^\\page$/gm);
}
this.setState({
pages : pages,
usePPR : pages.length >= PPR_THRESHOLD
});
}
},
const BrewRenderer = (props)=>{
props = {
text : '',
style : '',
renderer : 'legacy',
theme : '5ePHB',
lang : '',
errors : [],
...props
};
updateSize : function() {
this.setState({
height : this.refs.main.parentNode.clientHeight,
});
},
const [state, setState] = useState({
viewablePageNumber : 0,
height : PAGE_HEIGHT,
isMounted : false,
visibility : 'hidden',
});
handleScroll : function(e){
const target = e.target;
this.setState((prevState)=>({
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * prevState.pages.length)
const mainRef = useRef(null);
if(props.renderer == 'legacy') {
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){
if(!this.state.isMounted) return false;
const handleScroll = (e)=>{
const target = e.target;
setState((prevState)=>({
...prevState,
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * rawPages.length)
}));
};
const viewIndex = this.state.viewablePageNumber;
if(index == viewIndex - 3) return true;
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;
const shouldRender = (index)=>{
if(!state.isMounted) return false;
//Check for style tages
if(pageText.indexOf('<style>') !== -1) return true;
if(Math.abs(index - state.viewablePageNumber) <= 3)
return true;
return false;
},
};
sanitizeScriptTags : function(content) {
const sanitizeScriptTags = (content)=>{
return content
.replace(/<script/ig, '&lt;script')
.replace(/<\/script>/ig, '&lt;/script&gt;');
},
};
renderPageInfo : function(){
return <div className='pageInfo' ref='main'>
const renderPageInfo = ()=>{
return <div className='pageInfo' ref={mainRef}>
<div>
{this.props.renderer}
{props.renderer}
</div>
<div>
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
{state.viewablePageNumber + 1} / {rawPages.length}
</div>
</div>;
},
};
renderPPRmsg : function(){
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){
const renderDummyPage = (index)=>{
return <div className='phb page' id={`p${index + 1}`} key={index}>
<i className='fas fa-spinner fa-spin' />
</div>;
},
};
renderStyle : function() {
if(!this.props.style) return;
const cleanStyle = this.sanitizeScriptTags(this.props.style);
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.sanitizeScriptTags(this.props.style)}\n} </style>` }} />;
const renderStyle = ()=>{
if(!props.style) return;
const cleanStyle = sanitizeScriptTags(props.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>` }} />;
},
};
renderPage : function(pageText, index){
let cleanPageText = this.sanitizeScriptTags(pageText);
if(this.props.renderer == 'legacy')
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(cleanPageText) }} key={index} />;
else {
const renderPage = (pageText, index)=>{
let cleanPageText = sanitizeScriptTags(pageText);
if(props.renderer == 'legacy') {
const html = MarkdownLegacy.render(cleanPageText);
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)
return (
<div className='page' id={`p${index + 1}`} key={index} >
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(cleanPageText) }} />
</div>
);
const html = Markdown.render(cleanPageText);
return <BrewPage className='page' index={index} key={index} contents={html} />;
}
},
};
renderPages : function(){
if(this.state.usePPR){
return _.map(this.state.pages, (page, index)=>{
if(this.shouldRender(page, index) && typeof window !== 'undefined'){
return this.renderPage(page, index);
} else {
return this.renderDummyPage(index);
}
});
}
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);
const renderPages = ()=>{
if(props.errors && props.errors.length)
return renderedPages;
_.forEach(rawPages, (page, index)=>{
if((shouldRender(index) || !renderedPages[index]) && typeof window !== 'undefined'){
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
}
});
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
this.updateSize();
window.addEventListener('resize', this.updateSize);
this.renderPages(); //Make sure page is renderable before showing
this.setState({
updateSize();
window.addEventListener('resize', updateSize);
renderPages(); //Make sure page is renderable before showing
setState((prevState)=>({
...prevState,
isMounted : true,
visibility : 'visible'
});
}));
}, 100);
},
};
emitClick : function(){
// console.log('iFrame clicked');
const emitClick = ()=>{ // Allow clicks inside iFrame to interact with dropdowns, etc. from outside
if(!window || !document) return;
document.dispatchEvent(new MouseEvent('click'));
},
};
render : function(){
//render in iFrame so broken code doesn't crash the site.
//Also render dummy page while iframe is mounting.
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
const themePath = this.props.theme ?? '5ePHB';
const baseThemePath = Themes[rendererPath][themePath].baseTheme;
return (
<React.Fragment>
{!this.state.isMounted
? <div className='brewRenderer' onScroll={this.handleScroll}>
<div className='pages' ref='pages'>
{this.renderDummyPage(1)}
</div>
const rendererPath = props.renderer == 'V3' ? 'V3' : 'Legacy';
const themePath = props.theme ?? '5ePHB';
const baseThemePath = Themes[rendererPath][themePath].baseTheme;
return (
<>
{/*render dummy page while iFrame is mounting.*/}
{!state.isMounted
? <div className='brewRenderer' onScroll={handleScroll}>
<div className='pages'>
{renderDummyPage(1)}
</div>
: null}
</div>
: null}
<Frame id='BrewRenderer' initialContent={this.state.initialContent}
style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
contentDidMount={this.frameDidMount}
onClick={()=>{this.emitClick();}}
>
<div className={'brewRenderer'}
onScroll={this.handleScroll}
style={{ height: this.state.height }}>
{/*render in iFrame so broken code doesn't crash the site.*/}
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
style={{ width: '100%', height: '100%', visibility: state.visibility }}
contentDidMount={frameDidMount}
onClick={()=>{emitClick();}}
>
<div className={'brewRenderer'}
onScroll={handleScroll}
style={{ height: state.height }}>
<ErrorBar errors={this.props.errors} />
<div className='popups'>
<RenderWarnings />
<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>
</>
}
<ErrorBar errors={props.errors} />
<div className='popups'>
<RenderWarnings />
<NotificationPopup />
</div>
</Frame>
{this.renderPageInfo()}
{this.renderPPRmsg()}
</React.Fragment>
);
}
});
<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 */}
{state.isMounted
&&
<>
{renderStyle()}
<div className='pages' lang={`${props.lang || 'en'}`}>
{renderPages()}
</div>
</>
}
</div>
</Frame>
{renderPageInfo()}
</>
);
};
module.exports = BrewRenderer;

View File

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

View File

@@ -149,6 +149,35 @@ const Editor = createClass({
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
}
// definition lists
if(line.includes('::')){
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
let match;
while ((match = regex.exec(line)) != null){
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[0]) }, { line: lineNumber, ch: line.indexOf(match[0]) + match[0].length }, { className: 'define' });
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'term' });
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[2]) }, { line: lineNumber, ch: line.indexOf(match[2]) + match[2].length }, { className: 'definition' });
}
}
// 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}
if(line.includes('{') && line.includes('}')){
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;
@@ -330,6 +359,14 @@ const Editor = createClass({
return this.refs.codeEditor?.undo();
},
foldCode : function(){
return this.refs.codeEditor?.foldAllCode();
},
unfoldCode : function(){
return this.refs.codeEditor?.unfoldAllCode();
},
render : function(){
return (
<div className='editor' ref='main'>
@@ -343,6 +380,8 @@ const Editor = createClass({
theme={this.props.brew.theme}
undo={this.undo}
redo={this.redo}
foldCode={this.foldCode}
unfoldCode={this.unfoldCode}
historySize={this.historySize()}
currentEditorTheme={this.state.editorTheme}
updateEditorTheme={this.updateEditorTheme}

View File

@@ -1,65 +1,85 @@
@import 'themes/codeMirror/customEditorStyles.less';
.editor{
.editor {
position : relative;
width : 100%;
.codeEditor{
.codeEditor {
height : 100%;
.pageLine{
.pageLine {
background : #33333328;
border-top : #339 solid 1px;
border-top : #333399 solid 1px;
}
.editor-page-count{
color : grey;
.editor-page-count {
float : right;
color : grey;
}
.columnSplit{
font-style : italic;
color : grey;
background-color : fade(#299, 15%);
border-bottom : #299 solid 1px;
.columnSplit {
font-style : italic;
color : grey;
background-color : fade(#229999, 15%);
border-bottom : #229999 solid 1px;
}
.block:not(.cm-comment){
color : purple;
.define {
&:not(.term):not(.definition) {
font-weight : bold;
color : #949494;
background : #E5E5E5;
border-radius : 3px;
}
&.term { color : rgb(96, 117, 143); }
&.definition { color : rgb(97, 57, 178); }
}
.block:not(.cm-comment) {
font-weight : bold;
color : purple;
//font-style: italic;
}
.inline-block:not(.cm-comment){
color : red;
.inline-block:not(.cm-comment) {
font-weight : bold;
color : red;
//font-style: italic;
}
.injection:not(.cm-comment){
.injection:not(.cm-comment) {
font-weight : bold;
color : green;
font-weight : bold;
}
.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{
position : absolute;
background-color : @teal;
cursor : pointer;
width : 30px;
height : 30px;
display : flex;
align-items : center;
bottom : 20px;
right : 20px;
z-index : 1000000;
justify-content : center;
.tooltipLeft("Jump to brew page");
.brewJump {
position : absolute;
right : 20px;
bottom : 20px;
z-index : 1000000;
display : flex;
align-items : center;
justify-content : center;
width : 30px;
height : 30px;
cursor : pointer;
background-color : @teal;
.tooltipLeft('Jump to brew page');
}
.editorToolbar{
position: absolute;
top: 5px;
left: 50%;
color: black;
font-size: 13px;
z-index: 9;
span {
padding: 2px 5px;
}
.editorToolbar {
position : absolute;
top : 5px;
left : 50%;
z-index : 9;
font-size : 13px;
color : black;
span { padding : 2px 5px; }
}
}

View File

@@ -37,6 +37,8 @@ const Snippetbar = createClass({
undo : ()=>{},
redo : ()=>{},
historySize : ()=>{},
foldCode : ()=>{},
unfoldCode : ()=>{},
updateEditorTheme : ()=>{},
cursorPos : {}
};
@@ -144,6 +146,22 @@ const Snippetbar = createClass({
renderEditorButtons : function(){
if(!this.props.showEditButtons) return;
let foldButtons;
if(this.props.view == 'text'){
foldButtons =
<>
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
</>
}
return <div className='editors'>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} >
@@ -154,6 +172,7 @@ const Snippetbar = createClass({
<i className='fas fa-redo' />
</div>
<div className='divider'></div>
{foldButtons}
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />

View File

@@ -10,7 +10,6 @@
top : 0px;
right : 0px;
height : @menuHeight;
width : 125px;
justify-content : space-between;
&>div{
height : @menuHeight;
@@ -46,6 +45,22 @@
color : black;
}
}
&.foldAll{
.tooltipLeft('Fold All');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
}
&.unfoldAll{
.tooltipLeft('Unfold All');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
}
&.editorTheme{
.tooltipLeft('Editor Themes');
font-size : 0.75em;

View File

@@ -22,18 +22,18 @@ const errorIndex = (props)=>{
## We can't find this brew in Google Drive!
This file was saved on Google Drive, but this link doesn't work anymore.
${ props.brew.authors?.length > 0
? `Note that this brew belongs to the Homebrewery account **${ props.brew.authors[0] }**,
${ props.brew.account
? `which is
${props.brew.authors?.length > 0
? `Note that this brew belongs to the Homebrewery account **${props.brew.authors[0]}**,
${props.brew.account
? `which is
${props.brew.authors[0] == props.brew.account
? `your account.`
: `not your account (you are currently signed in as **${props.brew.account}**).`
}`
: 'and you are not currently signed in to any account.'
}`
: ''
}
? `your account.`
: `not your account (you are currently signed in as **${props.brew.account}**).`
}`
: 'and you are not currently signed in to any account.'
}`
: ''
}
The Homebrewery cannot delete files from Google Drive on its own, so there
are three most likely possibilities:
:
@@ -75,7 +75,9 @@ const errorIndex = (props)=>{
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}`,
**Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
// User is not signed in; must be a user on the Authors List
'04' : dedent`

View File

@@ -16,9 +16,9 @@ The Homebrewery makes the creation and sharing of authentic looking Fifth-Editio
**Try it!** Simply edit the text on the left and watch it *update live* on the right. Note that not every button is visible on this demo page. Click New {{fas,fa-plus-square}} in the navbar above to start brewing with all the features!
### Editing and Sharing
When you create your own homebrew, you will be given a *edit url* and a *share url*.
When you create a new homebrew document ("brew"), your document will be given a *edit link* and a *share link*.
Any changes you make while on the *edit url* will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew, so be careful about who you share it with.
The *edit link* is where you write your brew. If you edit a brew while logged in, you are added as one of the brew's authors, and no one else can edit that brew until you add them as a new author via the {{fa,fa-info-circle}} **Properties** tab. Brews without any author can still be edited by anyone with the *edit link*, so be careful about who you share it with if you prefer to work without an account.
Anyone with the *share url* will be able to access a read-only version of your homebrew.
@@ -48,57 +48,63 @@ If you want to save ink or have a monochrome printer, add the **PRINT → {{fas,
\column
## New in V3.0.0
We've implemented an extended Markdown-like syntax for block and span elements, plus a few other changes, eliminating the need for HTML tags like `div` and `span` in most cases. No raw HTML tags should be needed in a brew (*but can still be used if you insist*).
## V3 vs Legacy
The Homebrewery has two renderers: Legacy and V3. The V3 renderer is recommended for all users because it is more powerful, more customizable, and continues to receive new feature updates while Legacy does not. However Legacy mode will remain available for older brews and veteran users.
At any time, any individual brew can be changed to your renderer of choice via the {{fa,fa-info-circle}} **Properties** tab on your brew. However, converting between Legacy and V3 may require heavily tweaking the document; while both renderers can use raw HTML, V3 prefers a streamlined curly bracket syntax that avoids the complex HTML structures required by Legacy.
Much of the syntax and styling has changed in V3, so converting a Legacy brew to V3 (or vice-versa) will require tweaking your document. *However*, all brews made prior to the release of v3.0.0 will still render normally, and you may switch between the "Legacy" brew renderer and the newer "V3" renderer via the {{fa,fa-info-circle}} **Properties** button on your brew at any time.
Scroll down to the next page for a brief summary of the changes and new features available in V3!
Scroll down to the next page for a brief summary of the changes and features available in V3!
#### New Things All The Time!
Check out the latest updates in the full changelog [here](/changelog).
### Helping out
Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/Naturalcrit) to help me keep the servers running.
Like this tool? Head over to our [Patreon](https://www.patreon.com/Naturalcrit) to help us keep the servers running.
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
This tool will **always** be free, never have ads, and we will never offer any "premium" features or whatever.
### Bugs, Issues, Suggestions?
Take a quick look at our [Frequently Asked Questions page](/faq) to see if your question has a handy answer.
Need help getting started or just the right look for your brew? Head to [r/Homebrewery](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let us know!
Have an idea to make The Homebrewery better? Or did you find something that wasn't quite right? Check out the [GitHub Repo](https://github.com/naturalcrit/homebrewery/) to report technical issues.
- Check the [Frequently Asked Questions](/faq) page first for quick answers.
- Get help or the right look for your brew by posting on [r/Homebrewery](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) or joining the [Discord Of Many Things](https://discord.gg/by3deKx).
- Report technical issues or provide feedback on the [GitHub Repo](https://github.com/naturalcrit/homebrewery/).
### Legal Junk
The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/homebrewery/blob/master/license). Which means you are free to use The Homebrewery codebase any way that you want, except for claiming that you made it yourself.
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
#### Crediting Me
If you'd like to credit me in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
#### Crediting Us
If you'd like to credit us in your brew, we'd be flattered! Just reference that you made it with The Homebrewery.
### More Homebrew Resources
<a href='https://discord.gg/by3deKx' target='_blank'><img src='/assets/discordOfManyThings.svg' alt='Discord of Many Things Logo' title='Discord of Many Things Logo' style='width:50px; float: right; padding-left: 10px;'/></a>
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/wiki/resources). The <a href='https://discord.gg/by3deKx' target='_blank' title='Discord of Many Things'>Discord of Many Things</a> is another great resource to connect with fellow homebrewers for help and feedback.
[![Discord](/assets/discordOfManyThings.svg){width:50px,float:right,padding-left:10px}](https://discord.gg/by3deKx)
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/wiki/resources). The [Discord Of Many Things](https://discord.gg/by3deKx) is another great resource to connect with fellow homebrewers for help and feedback.
{{position:absolute;top:20px;right:20px;width:auto
<a href='https://discord.gg/by3deKx' target='_blank' title='Discord of Many Things' style='color: black;'><img src='/assets/discord.png' style='height:30px'/></a>
<a href='https://github.com/naturalcrit/homebrewery' target='_blank' title='Github' style='color: black; padding-left: 5px;'><img src='/assets/github.png' style='height:30px'/></a>
<a href='https://patreon.com/NaturalCrit' target='_blank' title='Patreon' style='color: black; padding-left: 5px;'><img src='/assets/patreon.png' style='height:30px'/></a>
<a href='https://www.reddit.com/r/homebrewery/' target='_blank' title='Reddit' style='color: black; padding-left: 5px;'><img src='/assets/reddit.png' style='height:30px'/></a>
[![Discord](/assets/discord.png){height:30px}](https://discord.gg/by3deKx)
[![Github](/assets/github.png){height:30px}](https://github.com/naturalcrit/homebrewery)
[![Patreon](/assets/patreon.png){height:30px}](https://patreon.com/NaturalCrit)
[![Reddit](/assets/reddit.png){height:30px}](https://www.reddit.com/r/homebrewery/)
}}
\page
## Markdown+
The Homebrewery aims to make homebrewing as simple as possible, providing a live editor with Markdown syntax that is more human-readable and faster to write with than raw HTML.
In version 3.0.0, with a goal of adding maximum flexibility without users resorting to complex HTML to accomplish simple tasks, Homebrewery provides an extended verision of Markdown with additional syntax.
**You can enable V3 via the {{fa,fa-info-circle}} Properties button!**
From version 3.0.0, with a goal of adding maximum flexibility without users resorting to complex HTML to accomplish simple tasks, Homebrewery provides an extended verision of Markdown with additional syntax.
### Curly Brackets
The biggest change in V3 is the replacement of `<span></span>` and `<div></div>` with `{{ }}` for a cleaner custom formatting. Inline spans and block elements can be created and given ID's and Classes, as well as css properties, each of which are comma separated with no spaces. Use double quotes if a value requires spaces. Spans and Blocks start the same:
Standard Markdown lacks several equivalences to HTML. Hence, we have introduced `{{ }}` as a replacement for `<span></span>` and `<div></div>` for a cleaner custom formatting. Inline spans and block elements can be created and given ID's and Classes, as well as CSS properties, each of which are comma separated with no spaces. Use double quotes if a value requires spaces. Spans and Blocks start the same:
#### Span
My favorite author is {{pen,#author,color:orange,font-family:"trebuchet ms" Brandon Sanderson}}. The orange text has a class of `pen`, an id of `author`, is colored orange, and given a new font. The first space outside of quotes marks the beginning of the content.
@@ -126,16 +132,17 @@ A blank line can be achieved with a run of one or more `:` alone on a line. More
::
Much nicer than `<br><br><br><br><br>`
### Definition Lists
**Example** :: V3 uses HTML *definition lists* to create "lists" with hanging indents.
### Column Breaks
Column and page breaks with `\column` and `\page`.
\column
### Tables
Tables now allow column & row spanning between cells. This is included in some updated snippets, but a simplified example is given below.
@@ -163,13 +170,13 @@ Using *Curly Injection* you can assign an id, classes, or inline CSS properties
![alt-text](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width:100px,border:"2px solid",border-radius:10px}
\* *When using Imgur-hosted images, use the "direct link", which can be found when you click into your image in the Imgur interace.*
\* *When using Imgur-hosted images, use the "direct link", which can be found when you click into your image in the Imgur interface.*
## Snippets
Homebrewery comes with a series of *code snippets* found at the top of the editor pane that make it easy to create brews as quickly as possible. Just set your cursor where you want the code to appear in the editor pane, choose a snippet, and make the adjustments you need.
## Style Editor Panel
{{fa,fa-paint-brush}} Technically released prior to v3 but still new to many users, check out the new **Style Editor** located on the right side of the Snippet bar. This editor accepts CSS for styling without requiring `<style>` tags-- anything that would have gone inside style tags before can now be placed here, and snippets that insert CSS styles are now located on that tab.
{{fa,fa-paint-brush}} Usually overlooked or unused by some users, the **Style Editor** tab is located on the right side of the Snippet bar. This editor accepts CSS for styling without requiring `<style>` tags-- anything that would have gone inside style tags before can now be placed here, and snippets that insert CSS styles are now located on that tab.
{{pageNumber 2}}
{{footnote PART 2 | BORING STUFF}}

29
faq.md
View File

@@ -62,16 +62,13 @@ pre {
```
# FAQ
{{wide Updated Oct. 11, 2021}}
{{wide Updated Apr. 15, 2023}}
### The site is down for me! Anyone else?
You can check the site status here: [Everyone or Just Me](https://downforeveryoneorjustme.com/homebrewery.naturalcrit.com)
### How do I log out?
Go to https://homebrewery.naturalcrit.com/login, and hit the "*logout*" link.
### Why am I getting an error when trying to save, and my account is linked to Google?
@@ -105,7 +102,7 @@ The best way to avoid this is to leave space at the end of a column equal to one
### Why do I need to manually create a new page? Why doesn't text flow between pages?
A Homebrewery document is at it's core an HTML & CSS document, and currently limited by the specs of those technologies. It is currently not possible to flow content from inside one box ("page") to the inside of another box. It seems likely that someday CSS will add this capability, and if/when that happens, Homebrewery will adopt it as soon as possible.
A Homebrewery document is at its core an HTML & CSS document, and currently limited by the specs of those technologies. It is currently not possible to flow content from inside one box ("page") to the inside of another box. It seems likely that someday CSS will add this capability, and if/when that happens, Homebrewery will adopt it as soon as possible.
### Where do I get images?
The Homebrewery does not provide images for use besides some page elements and example images for snippets. You will need to find your own images for use and be sure you are following the appropriate license requirements.
@@ -120,26 +117,6 @@ The fonts used were originally created for use with the English language, though
### Whenever I click on the "Get PDF" button, instead of getting a download, it opens Print Preview in another tab.
Yes, this is by design. In the print preview, select "Save as PDF" as the Destination, and then click "Save". There will be a normal download dialog where you can save your brew as a PDF.
### The preview window is suddenly gone, I can only see the editor side of the Homebrewery (or the other way around).
1. Press `CTRL`+`SHIFT`+`i` (or right-click and select "Inspect") while in the Homebrewery.
2. Expand...
```
- `body`
- `main`
- `div class="homebrew"`
- `div class="editPage page"`
- `div class="content"`
- `div class="splitPane"`
```
There you will find 3 divs: `div class="pane" [...]`, `div class="divider" [...]`, and `div class="pane" [...]`.
The `class="pane"` looks similar to this: `div class="pane" data-reactid="36" style="flex: 0 0 auto; width: 925px;"`.
Change whatever stands behind width: to something smaller than your display width.
### I have white borders on the bottom/sides of the print preview.
The Homebrewery paper size and your print paper size do not match.
@@ -149,4 +126,4 @@ The Homebrewery defaults to creating US Letter page sizes. If you are printing
### Typing `#### Adhesion` in the text editor doesn't show the header at all in the completed page?
Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.
Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.

5221
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,10 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.9.2",
"version": "3.10.0",
"engines": {
"node": ">=18.16.x"
"npm": "^10.2.x",
"node": "^20.8.x"
},
"repository": {
"type": "git",
@@ -78,11 +79,11 @@
]
},
"dependencies": {
"@babel/core": "^7.22.15",
"@babel/plugin-transform-runtime": "^7.22.15",
"@babel/preset-env": "^7.22.15",
"@babel/preset-react": "^7.22.15",
"@googleapis/drive": "^5.1.0",
"@babel/core": "^7.23.5",
"@babel/plugin-transform-runtime": "^7.23.4",
"@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.4.0",
"body-parser": "^1.20.2",
"classnames": "^2.3.2",
"codemirror": "^5.65.6",
@@ -92,38 +93,37 @@
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7",
"fs-extra": "11.1.1",
"fs-extra": "11.2.0",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "5.1.1",
"marked-extended-tables": "^1.0.6",
"marked-gfm-heading-id": "^3.0.7",
"marked-smartypants-lite": "^1.0.0",
"marked-extended-tables": "^1.0.7",
"marked-gfm-heading-id": "^3.1.2",
"marked-smartypants-lite": "^1.0.1",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4",
"mongoose": "^7.5.0",
"mongoose": "^8.0.2",
"nanoid": "3.3.4",
"nconf": "^0.12.0",
"npm": "^10.0.0",
"nconf": "^0.12.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-frame-component": "^4.1.3",
"react-router-dom": "6.15.0",
"react-router-dom": "6.20.1",
"sanitize-filename": "1.6.3",
"superagent": "^6.1.0",
"superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"eslint": "^8.48.0",
"eslint-plugin-jest": "^27.2.3",
"eslint": "^8.55.0",
"eslint-plugin-jest": "^27.6.0",
"eslint-plugin-react": "^7.33.2",
"jest": "^29.6.4",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0",
"stylelint": "^15.10.3",
"stylelint-config-recess-order": "^4.3.0",
"stylelint": "^15.11.0",
"stylelint-config-recess-order": "^4.4.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.3"

View File

@@ -101,7 +101,10 @@ fs.emptyDirSync('./build');
//v==---------------------------MOVE CM EDITOR THEMES -----------------------------==v//
editorThemeFiles = fs.readdirSync('./node_modules/codemirror/theme');
const editorThemesBuildDir = './build/homebrew/cm-themes';
await fs.copy('./node_modules/codemirror/theme', editorThemesBuildDir);
await fs.copy('./themes/codeMirror/customThemes', editorThemesBuildDir);
editorThemeFiles = fs.readdirSync(editorThemesBuildDir);
const editorThemeFile = './themes/codeMirror/editorThemes.json';
if(fs.existsSync(editorThemeFile)) fs.rmSync(editorThemeFile);
@@ -114,7 +117,7 @@ fs.emptyDirSync('./build');
stream.write('\n]\n');
stream.end();
await fs.copy('./node_modules/codemirror/theme', './build/homebrew/cm-themes');
await fs.copy('./themes/codeMirror', './build/homebrew/codeMirror');
//v==----------------------------- BUNDLE PACKAGES --------------------------------==v//
@@ -151,14 +154,14 @@ fs.emptyDirSync('./build');
// build(bundles);
//
})().catch(console.error);
//In development, set up LiveReload (refreshes browser), and Nodemon (restarts server)
if(isDev){
livereload('./build'); // Install the Chrome extension LiveReload to automatically refresh the browser
watchFile('./server.js', { // Restart server when change detected to this file or any nested directory from here
ignore : ['./build', './client', './themes'], // Ignore folders that are not running server code / avoids unneeded restarts
ext : 'js json' // Extensions to watch (only .js/.json by default)
//watch : ['./server', './themes'], // Watch additional folders if needed
});
}
//In development, set up LiveReload (refreshes browser), and Nodemon (restarts server)
if(isDev){
livereload('./build'); // Install the Chrome extension LiveReload to automatically refresh the browser
watchFile('./server.js', { // Restart server when change detected to this file or any nested directory from here
ignore : ['./build', './client', './themes'], // Ignore folders that are not running server code / avoids unneeded restarts
ext : 'js json' // Extensions to watch (only .js/.json by default)
//watch : ['./server', './themes'], // Watch additional folders if needed
});
}
})().catch(console.error);

View File

@@ -327,14 +327,17 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r
type : 'article'
};
if(req.params.id.length > 12 && !brew._id) {
const googleId = brew.googleId;
const shareId = brew.shareId;
await GoogleActions.increaseView(googleId, shareId, 'share', brew)
.catch((err)=>{next(err);});
} else {
await HomebrewModel.increaseView({ shareId: brew.shareId });
}
// increase visitor view count, do not include visits by author(s)
if(!brew.authors.includes(req.account?.username)){
if(req.params.id.length > 12 && !brew._id) {
const googleId = brew.googleId;
const shareId = brew.shareId;
await GoogleActions.increaseView(googleId, shareId, 'share', brew)
.catch((err)=>{next(err);});
} else {
await HomebrewModel.increaseView({ shareId: brew.shareId });
}
};
sanitizeBrew(req.brew, 'share');
splitTextStyleAndMetadata(req.brew);
return next();

View File

@@ -79,7 +79,7 @@ const api = {
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
const accessError = { name: 'Access Error', status: 401 };
if(req.account){
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title };
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
}
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title };
}

View File

@@ -112,6 +112,10 @@ const CodeEditor = createClass({
'Shift-Tab' : this.dedent,
'Ctrl-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,
'Cmd-I' : this.makeItalic,
'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() {
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 = {
name : 'definitionLists',
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({ renderer: renderer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());

View File

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

View File

@@ -10,6 +10,21 @@
background-image : url(/assets/DMG_background.png);
background-size : cover;
/*TABLES WITHIN NOTES*/
.note table tbody tr:nth-child(odd) {
background:#fff;
}
/*DROP CAP*/
h1 + p::first-letter {
background-image: unset;
color:black;
}
.quote p:first-child::first-line {
all: unset;
}
&:after {
background-image : url(/assets/DMG_footerAccent.png);
height: 58px;
@@ -25,4 +40,4 @@
.partCover {
background-image: @partCoverHeaderDMG;
}
}
}

View File

@@ -8,6 +8,7 @@ const ClassFeatureGen = require('./snippets/classfeature.gen.js');
const CoverPageGen = require('./snippets/coverpage.gen.js');
const TableOfContentsGen = require('./snippets/tableOfContents.gen.js');
const indexGen = require('./snippets/index.gen.js');
const QuoteGen = require('./snippets/quote.gen.js');
const dedent = require('dedent-tabs').default;
@@ -123,6 +124,11 @@ module.exports = [
icon : 'fas fa-mask',
gen : ClassFeatureGen,
},
{
name : 'Quote',
icon : 'fas fa-quote-right',
gen : QuoteGen,
},
{
name : 'Note',
icon : 'fas fa-sticky-note',

View File

@@ -0,0 +1,51 @@
const _ = require("lodash");
const quotes = [
"The sword glinted in the dim light, its edges keen and deadly. As the adventurer reached for it, he couldn't help but feel a surge of excitement mixed with fear. This was no ordinary blade.",
"The dragon's roar shook the ground beneath their feet, and the brave knight stood tall, his sword at the ready. He knew that this would be the battle of his life, but he was determined to emerge victorious.",
"The wizard's laboratory was a sight to behold, filled with bubbling cauldrons, ancient tomes, and strange artifacts from distant lands. As the apprentice gazed around in wonder, she knew that she was about to embark on a journey unlike any other.",
"The tavern was packed with rowdy patrons, their voices raised in song and laughter. The bard took center stage, strumming his lute and launching into a tale of adventure and heroism that had the crowd hanging on his every word.",
"The thief crept through the shadows, his eyes scanning the room for any sign of danger. He knew that one false move could mean the difference between success and failure, and he was determined to come out on top.",
"The elf queen stood atop her castle walls, surveying the kingdom below with a mix of pride and sadness. She knew that the coming war would be brutal, but she was determined to protect her people at all costs.",
"The necromancer's tower loomed in the distance, its dark spires piercing the sky. As the adventurers approached, they could feel the chill of death emanating from within",
"The ranger moved through the forest like a shadow, his senses attuned to every sound and movement around him. He knew that danger lurked behind every tree, but he was ready for whatever came his way.",
"The paladin knelt before the altar, his hands clasped in prayer. He knew that his faith would be tested in the days ahead, but he was ready to face whatever trials lay in store for him.",
"The druid communed with the spirits of nature, his mind merging with the trees, the animals, and the very earth itself. He knew that his power came with a great responsibility, and he was determined to use it for the greater good.",
];
const authors = [
"Unknown",
"James Wyatt",
"Eolande Blackwood",
"Ragnar Ironheart",
"Lyra Nightshade",
"Valtorius Darkstar",
"Isadora Fireheart",
"Theron Shadowbane",
"Lirien Starweaver",
"Drogathar Bonecrusher",
"Kaelen Frostblade",
];
const books = [
"The Blade of Destiny",
"Dragonfire and Steel",
"The Bard's Tale",
"Darkness Rising",
"The Sacred Quest",
"Shadows in the Forest",
"The Starweaver Chronicles",
"Beneath the Bones",
"Moonlit Magic",
"Frost and Fury",
];
module.exports = () => {
return `
{{quote
${_.sample(quotes)}
{{attribution ${_.sample(authors)}, *${_.sample(books)}*}}
}}
\n`;
};

File diff suppressed because it is too large Load Diff

View File

@@ -56,8 +56,6 @@ body {
box-sizing : border-box;
overflow : hidden;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size;
}
//*****************************

View File

@@ -0,0 +1,129 @@
/* Main BG color and normal text color */
.CodeMirror {
background: #293134;
color: #91A6AA;
}
/* Brew BG */
.brewRenderer {
background-color: #293134;
}
/* Blinking cursor */
.CodeMirror-cursor {
border-left: 1px solid #e0e2e4;
}
/* HB DARK NAV START*/
/* Bars at the top */
.snippetBar {
background-color: #2F393C;
color: white;
}
nav {
background-color: #293134;
}
nav .navItem {
background-color: #293134;
}
/* Fix for Homebrewery custom Snippet icons */
.snippetBar .fac {
filter: invert(1);
}
.snippetBar .snippetGroup .dropdown {
background-color: #2F393C;
}
/* HB DARK NAV END */
/* Line number stuff */
.CodeMirror-gutter-elt {
color: #81969A;
}
.CodeMirror-linenumber {
background-color: #293134;
}
.CodeMirror-gutter {
background-color: #293134;
}
/* column splits */
.editor .codeEditor .columnSplit {
font-style: italic;
color: inherit;
background-color:#1f5763;
border-bottom: #299 solid 1px;
}
/* Colors for headings and such */
/* ###Headings */
.cm-s-default .cm-header {
color: #c51b1b;
-webkit-text-stroke-width: 0.1px;
-webkit-text-stroke-color: #000;
}
/* bold points */
.cm-header, .cm-strong {
font-weight: bold;
color: #309dd2;
}
/* Link headings */
.cm-s-default .cm-link {
color: #dd6300;
}
/* links */
.cm-s-default .cm-string {
color: #aa8261;
}
/*@import*/
.cm-s-default .cm-def {
color:#2986cc;
}
/* Bullets and such */
.cm-s-default .cm-variable-2 {
color: #3cbf30;
}
/* blocks */
.editor .codeEditor .block:not(.cm-comment) {
color: #e3e3e3;
}
/* inline blocks */
.editor .codeEditor .inline-block {
color: #e3e3e3;
}
/* Tags (divs) */
.cm-s-default .cm-tag {
color: #e3ff00;
}
.cm-s-default .cm-attribute {
color: #e3ff00;
}
.cm-s-default .cm-atom {
color:#000;
}
.cm-s-default .cm-qualifier{
color:#ee1919;
}
.cm-s-default .cm-comment{
color:#bbc700;
}
.cm-s-default .cm-keyword {
color:#c302df;
background-color:#b1b1b1;
}
.cm-s-default .cm-property.cm-error {
color:#c50202;
}
.CodeMirror-foldmarker {
color:#f0ff00;
}
/* New page */
.editor .codeEditor .pageLine {
background: #000;
color:#000;
border-bottom: 1px solid #fff;
}
.cm-s-default .cm-builtin {
color:#fff;
}

View File

@@ -15,6 +15,7 @@
"cobalt",
"colorforth",
"darcula",
"darkbrewery-v301",
"dracula",
"duotone-dark",
"duotone-light",