0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-03-22 06:48:11 +00:00

Merge branch 'master' into codeMirror-skipExtraKeys

This commit is contained in:
David Bolack
2025-12-13 20:08:44 -06:00
58 changed files with 3875 additions and 3845 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import diceFont from '../../../themes/fonts/iconFonts/diceFont.js';
import elderberryInn from '../../../themes/fonts/iconFonts/elderberryInn.js';
import fontAwesome from '../../../themes/fonts/iconFonts/fontAwesome.js';
import gameIcons from '../../../themes/fonts/iconFonts/gameIcons.js';
import diceFont from 'themes/fonts/iconFonts/diceFont.js';
import elderberryInn from 'themes/fonts/iconFonts/elderberryInn.js';
import fontAwesome from 'themes/fonts/iconFonts/fontAwesome.js';
import gameIcons from 'themes/fonts/iconFonts/gameIcons.js';
const emojis = {
...diceFont,

View File

@@ -3,7 +3,7 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
import Dialog from '../../../client/components/dialog.jsx';
import Dialog from '../dialog.jsx';
const RenderWarnings = createClass({
displayName : 'RenderWarnings',
@@ -25,7 +25,7 @@ const RenderWarnings = createClass({
if(!isChrome){
return <li key='chrome'>
<em>Built for Chrome </em> <br />
Other browsers have not been tested for compatiblilty. If you
Other browsers have not been tested for compatibility. If you
experience issues with your document not rendering or printing
properly, please try using the latest version of Chrome before
submitting a bug report.

View File

@@ -4,13 +4,13 @@ const React = require('react');
const { useState, useRef, useMemo, useEffect } = React;
const _ = require('lodash');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
import Markdown from 'naturalcrit/markdown.js';
const MarkdownLegacy = require('markdownLegacy.js');
import Markdown from 'markdown.js';
const ErrorBar = require('./errorBar/errorBar.jsx');
const ToolBar = require('./toolBar/toolBar.jsx');
//TODO: move to the brew renderer
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
const RenderWarnings = require('client/components/renderWarnings/renderWarnings.jsx');
const NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
const Frame = require('react-frame-component').default;
const dedent = require('dedent-tabs').default;

View File

@@ -1,7 +1,7 @@
require('./notificationPopup.less');
import React, { useEffect, useState } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
import Dialog from '../../../components/dialog.jsx';

View File

@@ -4,9 +4,9 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const dedent = require('dedent-tabs').default;
import Markdown from '../../../shared/naturalcrit/markdown.js';
import Markdown from '../../../shared/markdown.js';
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
const CodeEditor = require('client/components/codeEditor/codeEditor.jsx');
const SnippetBar = require('./snippetbar/snippetbar.jsx');
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
@@ -58,7 +58,7 @@ const Editor = createClass({
return {
editorTheme : this.props.editorTheme,
view : 'text', //'text', 'style', 'meta', 'snippet'
snippetbarHeight : 25
snippetBarHeight : 26,
};
},
@@ -85,7 +85,15 @@ const Editor = createClass({
editorTheme : editorTheme
});
}
this.setState({ snippetbarHeight: document.querySelector('.editor > .snippetBar').offsetHeight });
const snippetBar = document.querySelector('.editor > .snippetBar');
if (!snippetBar) return;
this.resizeObserver = new ResizeObserver(entries => {
const height = document.querySelector('.editor > .snippetBar').offsetHeight;
this.setState({ snippetBarHeight: height });
});
this.resizeObserver.observe(snippetBar);
},
componentDidUpdate : function(prevProps, prevState, snapshot) {
@@ -108,6 +116,10 @@ const Editor = createClass({
}
},
componentWillUnmount() {
if (this.resizeObserver) this.resizeObserver.disconnect();
},
handleControlKeys : function(e){
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
const LEFTARROW_KEY = 37;
@@ -408,11 +420,7 @@ const Editor = createClass({
},
//Called when there are changes to the editor's dimensions
update : function(){
const snipHeight = document.querySelector('.editor > .snippetBar').offsetHeight;
if(snipHeight !== this.state.snippetbarHeight)
this.setState({ snippetbarHeight: snipHeight });
},
update : function(){},
updateEditorTheme : function(newTheme){
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
@@ -437,7 +445,7 @@ const Editor = createClass({
onChange={this.props.onBrewChange('text')}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} />
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} />
</>;
}
if(this.isStyle()){
@@ -451,7 +459,7 @@ const Editor = createClass({
enableFolding={true}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} />
style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} />
</>;
}
if(this.isMeta()){
@@ -468,7 +476,6 @@ const Editor = createClass({
userThemes={this.props.userThemes}/>
</>;
}
if(this.isSnip()){
if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; }
return <>
@@ -481,7 +488,7 @@ const Editor = createClass({
enableFolding={true}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} />
style={{ height: `calc(100% -${this.state.snippetBarHeight}px)` }} />
</>;
}
},

View File

@@ -14,7 +14,7 @@
.snippets {
display : flex;
justify-content : flex-start;
min-width : 432.18px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
min-width : 499.35px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
}
.editors {
@@ -237,7 +237,7 @@
}
}
@container editor (width < 683px) {
@container editor (width < 750px) {
.snippetBar {
.editors {
flex : 1;

View File

@@ -1,6 +1,6 @@
const React = require('react');
const createClass = require('create-react-class');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const request = require('superagent');
const Account = createClass({
@@ -70,7 +70,7 @@ const Account = createClass({
{global.account.username}
</Nav.item>
<Nav.item
href={`/user/${encodeURI(global.account.username)}`}
href={`/user/${encodeURIComponent(global.account.username)}`}
color='yellow'
icon='fas fa-beer'
>

View File

@@ -1,6 +1,6 @@
require('./error-navitem.less');
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const ErrorNavItem = ({ error = '', clearError })=>{
const response = error.response;

View File

@@ -1,7 +1,7 @@
const React = require('react');
const dedent = require('dedent-tabs').default;
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
module.exports = function(props){
return <Nav.dropdown>

View File

@@ -2,7 +2,7 @@ const React = require('react');
const createClass = require('create-react-class');
const Moment = require('moment');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const MetadataNav = createClass({
@@ -32,7 +32,7 @@ const MetadataNav = createClass({
return <>
{this.props.brew.authors.map((author, idx, arr)=>{
const spacer = arr.length - 1 == idx ? <></> : <span>, </span>;
return <span key={idx}><a className='userPageLink' href={`/user/${author}`}>{author}</a>{spacer}</span>;
return <span key={idx}><a className='userPageLink' href={`/user/${encodeURIComponent(author)}`}>{author}</a>{spacer}</span>;
})}
</>;
},

View File

@@ -5,7 +5,7 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
const NaturalCritIcon = require('client/components/svg/naturalcrit-d20.svg.jsx');
const Nav = {
base : createClass({

View File

@@ -2,7 +2,7 @@ require('./navbar.less');
const React = require('react');
const createClass = require('create-react-class');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const PatreonNavItem = require('./patreon.navitem.jsx');
const Navbar = createClass({

View File

@@ -1,21 +1,18 @@
const React = require('react');
const _ = require('lodash');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const { splitTextStyleAndMetadata } = require('../../../shared/helpers.js'); // Importing the function from helpers.js
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
const BREWKEY = 'HB_newPage_content';
const STYLEKEY = 'HB_newPage_style';
const METAKEY = 'HB_newPage_meta';
const NewBrew = ()=>{
const handleFileChange = (e)=>{
const file = e.target.files[0];
if(!file) return;
const currentNew = localStorage.getItem(BREWKEY);
if(currentNew && !confirm(
`You have some text in the new brew space, if you load a file that text will be lost, are you sure you want to load the file?`
)) return;
if(!confirmLocalStorageChange()) return;
const reader = new FileReader();
reader.onload = (e)=>{
@@ -37,12 +34,35 @@ const NewBrew = ()=>{
alert(`This file is invalid: ${!type ? 'Missing file extension' :`.${type} files are not supported`}. Only .txt files exported from the Homebrewery are allowed.`);
console.log(file);
};
reader.readAsText(file);
};
const confirmLocalStorageChange = ()=>{
const currentText = localStorage.getItem(BREWKEY);
const currentStyle = localStorage.getItem(STYLEKEY);
const currentMeta = localStorage.getItem(METAKEY);
// TRUE if no data in any local storage key
// TRUE if data in any local storage key AND approval given
// FALSE if data in any local storage key AND approval declined
return (!(currentText || currentStyle || currentMeta) || confirm(
`You have made changes in the new brew space. If you continue, that information will be PERMANENTLY LOST.\nAre you sure you wish to continue?`
));
};
const clearLocalStorage = ()=>{
if(!confirmLocalStorageChange()) return;
localStorage.removeItem(BREWKEY);
localStorage.removeItem(STYLEKEY);
localStorage.removeItem(METAKEY);
window.location.href = '/new';
return;
};
return (
<Nav.dropdown>
@@ -50,24 +70,31 @@ const NewBrew = ()=>{
className='new'
color='purple'
icon='fa-solid fa-plus-square'>
new
new
</Nav.item>
<Nav.item
className='fromBlank'
className='new'
href='/new'
newTab={true}
color='purple'
icon='fa-solid fa-file'>
from blank
resume draft
</Nav.item>
<Nav.item
className='fromBlank'
newTab={true}
color='yellow'
icon='fa-solid fa-file-circle-plus'
onClick={()=>{ clearLocalStorage(); }}>
from blank
</Nav.item>
<Nav.item
className='fromFile'
color='purple'
color='green'
icon='fa-solid fa-upload'
onClick={()=>{ document.getElementById('uploadTxt').click(); }}>
<input id='uploadTxt' className='newFromLocal' type='file' onChange={handleFileChange} style={{ display: 'none' }} />
from file
from file
</Nav.item>
</Nav.dropdown>
);

View File

@@ -1,5 +1,5 @@
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
module.exports = function(props){
return <Nav.item

View File

@@ -1,5 +1,5 @@
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const { printCurrentBrew } = require('../../../shared/helpers.js');
module.exports = function(){

View File

@@ -3,7 +3,7 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const Moment = require('moment');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const EDIT_KEY = 'HB_nav_recentlyEdited';
const VIEW_KEY = 'HB_nav_recentlyViewed';

View File

@@ -1,6 +1,6 @@
import React from 'react';
import dedent from 'dedent-tabs';
import Nav from 'naturalcrit/nav/nav.jsx';
import Nav from 'client/homebrew/navbar/nav.jsx';
const getShareId = (brew)=>(
brew.googleId && !brew.stubbed

View File

@@ -1,6 +1,6 @@
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
module.exports = function (props) {
return (

View File

@@ -1,7 +1,7 @@
const React = require('react');
const moment = require('moment');
const UIPage = require('../basePages/uiPage/uiPage.jsx');
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
const NaturalCritIcon = require('client/components/svg/naturalcrit-d20.svg.jsx');
let SAVEKEY = '';

View File

@@ -143,7 +143,7 @@ const BrewItem = ({
<span title="Username contained an email address; hidden to protect user's privacy">
{author}
</span>
) : (<a href={`/user/${author}`}>{author}</a>)}
) : (<a href={`/user/${encodeURIComponent(author)}`}>{author}</a>)}
{index < brew.authors.length - 1 && ', '}
</React.Fragment>
))}

View File

@@ -2,12 +2,12 @@ require('./uiPage.less');
const React = require('react');
const createClass = require('create-react-class');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../../navbar/navbar.jsx');
const NewBrewItem = require('../../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../../navbar/help.navitem.jsx');
const RecentNavItem = require('../../../navbar/recent.navitem.jsx').both;
const Account = require('../../../navbar/account.navitem.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const Navbar = require('client/homebrew/navbar/navbar.jsx');
const NewBrewItem = require('client/homebrew/navbar/newbrew.navitem.jsx');
const HelpNavItem = require('client/homebrew/navbar/help.navitem.jsx');
const RecentNavItem = require('client/homebrew/navbar/recent.navitem.jsx').both;
const Account = require('client/homebrew/navbar/account.navitem.jsx');
const UIPage = createClass({

View File

@@ -4,7 +4,7 @@ import './editPage.less';
// Common imports
import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
import _ from 'lodash';
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
@@ -14,15 +14,15 @@ import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
import Nav from 'client/homebrew/navbar/nav.jsx';
import Navbar from 'client/homebrew/navbar/navbar.jsx';
import NewBrewItem from 'client/homebrew/navbar/newbrew.navitem.jsx';
import AccountNavItem from 'client/homebrew/navbar/account.navitem.jsx';
import ErrorNavItem from 'client/homebrew/navbar/error-navitem.jsx';
import HelpNavItem from 'client/homebrew/navbar/help.navitem.jsx';
import VaultNavItem from 'client/homebrew/navbar/vault.navitem.jsx';
import PrintNavItem from 'client/homebrew/navbar/print.navitem.jsx';
import { both as RecentNavItem } from 'client/homebrew/navbar/recent.navitem.jsx';
// Page specific imports
import { Meta } from 'vitreum/headtags';
@@ -30,7 +30,7 @@ import { md5 } from 'hash-wasm';
import { gzipSync, strToU8 } from 'fflate';
import { makePatches, stringifyPatches } from '@sanity/diff-match-patch';
import ShareNavItem from '../../navbar/share.navitem.jsx';
import ShareNavItem from 'client/homebrew/navbar/share.navitem.jsx';
import LockNotification from './lockNotification/lockNotification.jsx';
import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js';
import googleDriveIcon from '../../googleDrive.svg';
@@ -119,6 +119,10 @@ const EditPage = (props)=>{
if(autoSaveEnabled) trySave(false, hasChange);
}, [currentBrew]);
useEffect(()=>{
trySave(true);
}, [saveGoogle]);
const handleSplitMove = ()=>{
editorRef.current?.update();
};
@@ -179,7 +183,6 @@ const EditPage = (props)=>{
const toggleGoogleStorage = ()=>{
setSaveGoogle((prev)=>!prev);
setError(null);
trySave(true);
};
const trySave = (immediate = false, hasChanges = true)=>{

View File

@@ -1,7 +1,7 @@
require('./errorPage.less');
const React = require('react');
const UIPage = require('../basePages/uiPage/uiPage.jsx');
import Markdown from '../../../../shared/naturalcrit/markdown.js';
import Markdown from '../../../../shared/markdown.js';
const ErrorIndex = require('./errors/errorIndex.js');
const ErrorPage = ({ brew })=>{

View File

@@ -96,7 +96,7 @@ const errorIndex = (props)=>{
**Brew Title:** ${escape(props.brew.brewTitle) || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${encodeURIComponent(author)})`;}).join(', ') || 'Unable to list authors'}
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
@@ -111,7 +111,7 @@ const errorIndex = (props)=>{
**Brew Title:** ${escape(props.brew.brewTitle) || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${encodeURIComponent(author)})`;}).join(', ') || 'Unable to list authors'}
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
@@ -222,7 +222,7 @@ const errorIndex = (props)=>{
**Brew Title:** ${escape(props.brew.brewTitle)}
**Brew Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}`,
**Brew Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${encodeURIComponent(author)})`;}).join(', ') || 'Unable to list authors'}`,
// ####### Admin page error #######
'52' : dedent`

View File

@@ -4,7 +4,7 @@ import './homePage.less';
// Common imports
import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
@@ -14,15 +14,15 @@ import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
import Nav from 'client/homebrew/navbar/nav.jsx';
import Navbar from 'client/homebrew/navbar/navbar.jsx';
import NewBrewItem from 'client/homebrew/navbar/newbrew.navitem.jsx';
import AccountNavItem from 'client/homebrew/navbar/account.navitem.jsx';
import ErrorNavItem from 'client/homebrew/navbar/error-navitem.jsx';
import HelpNavItem from 'client/homebrew/navbar/help.navitem.jsx';
import VaultNavItem from 'client/homebrew/navbar/vault.navitem.jsx';
import PrintNavItem from 'client/homebrew/navbar/print.navitem.jsx';
import { both as RecentNavItem } from 'client/homebrew/navbar/recent.navitem.jsx';
// Page specific imports
import { Meta } from 'vitreum/headtags';
@@ -53,8 +53,9 @@ const HomePage =(props)=>{
const [isSaving , setIsSaving] = useState(false);
const [autoSaveEnabled , setAutoSaveEnable] = useState(false);
const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
const unsavedChangesRef = useRef(unsavedChanges);
useEffect(()=>{
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
@@ -70,12 +71,20 @@ const HomePage =(props)=>{
};
document.addEventListener('keydown', handleControlKeys);
window.onbeforeunload = ()=>{
if(unsavedChangesRef.current)
return 'You have unsaved changes!';
};
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
window.onbeforeunload = null;
};
}, []);
useEffect(()=>{
unsavedChangesRef.current = unsavedChanges;
}, [unsavedChanges]);
const save = ()=>{
request.post('/api')
.send(currentBrew)

View File

@@ -4,7 +4,7 @@ import './newPage.less';
// Common imports
import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
@@ -14,15 +14,15 @@ import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
import Nav from 'client/homebrew/navbar/nav.jsx';
import Navbar from 'client/homebrew/navbar/navbar.jsx';
import NewBrewItem from 'client/homebrew/navbar/newbrew.navitem.jsx';
import AccountNavItem from 'client/homebrew/navbar/account.navitem.jsx';
import ErrorNavItem from 'client/homebrew/navbar/error-navitem.jsx';
import HelpNavItem from 'client/homebrew/navbar/help.navitem.jsx';
import VaultNavItem from 'client/homebrew/navbar/vault.navitem.jsx';
import PrintNavItem from 'client/homebrew/navbar/print.navitem.jsx';
import { both as RecentNavItem } from 'client/homebrew/navbar/recent.navitem.jsx';
// Page specific imports
import { Meta } from 'vitreum/headtags';
@@ -56,6 +56,10 @@ const NewPage = (props)=>{
const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
// const saveTimeout = useRef(null);
// const warnUnsavedTimeout = useRef(null);
const trySaveRef = useRef(trySave); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew
const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges
useEffect(()=>{
loadBrew();
@@ -114,6 +118,11 @@ const NewPage = (props)=>{
if(autoSaveEnabled) trySave(false, hasChange);
}, [currentBrew]);
useEffect(()=>{
trySaveRef.current = trySave;
unsavedChangesRef.current = unsavedChanges;
});
const handleSplitMove = ()=>{
editorRef.current.update();
};
@@ -141,7 +150,7 @@ const NewPage = (props)=>{
}
};
const save = async ()=>{
const trySave = async ()=>{
setIsSaving(true);
const updatedBrew = { ...currentBrew };
@@ -190,7 +199,7 @@ const NewPage = (props)=>{
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
return <Nav.item className='save' onClick={save} color='blue' icon='fas fa-save'>save now</Nav.item>;
return <Nav.item className='save' onClick={trySave} color='blue' icon='fas fa-save'>save now</Nav.item>;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)

View File

@@ -1,12 +1,15 @@
.newPage {
.navItem.save {
.fadeInRight();
.transition(opacity);
background-color : @orange;
transition:all 0.2s;
&:hover { background-color : @green; }
&.neverSaved {
.fadeOutRight();
translate:-100%;
opacity: 0;
background-color :#333;
cursor:auto;
}
}
}

View File

@@ -3,12 +3,12 @@ const React = require('react');
const { useState, useEffect, useCallback } = React;
const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const MetadataNav = require('../../navbar/metadata.navitem.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const Navbar = require('client/homebrew/navbar/navbar.jsx');
const MetadataNav = require('client/homebrew/navbar/metadata.navitem.jsx');
const PrintNavItem = require('client/homebrew/navbar/print.navitem.jsx');
const RecentNavItem = require('client/homebrew/navbar/recent.navitem.jsx').both;
const Account = require('client/homebrew/navbar/account.navitem.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');

View File

@@ -4,14 +4,14 @@ const _ = require('lodash');
const ListPage = require('../basePages/listPage/listPage.jsx');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const VaultNavitem = require('../../navbar/vault.navitem.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const Navbar = require('client/homebrew/navbar/navbar.jsx');
const RecentNavItem = require('client/homebrew/navbar/recent.navitem.jsx').both;
const Account = require('client/homebrew/navbar/account.navitem.jsx');
const NewBrew = require('client/homebrew/navbar/newbrew.navitem.jsx');
const HelpNavItem = require('client/homebrew/navbar/help.navitem.jsx');
const ErrorNavItem = require('client/homebrew/navbar/error-navitem.jsx');
const VaultNavitem = require('client/homebrew/navbar/vault.navitem.jsx');
const UserPage = (props)=>{
props = {

View File

@@ -5,12 +5,12 @@ require('./vaultPage.less');
const React = require('react');
const { useState, useEffect, useRef } = React;
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const Nav = require('client/homebrew/navbar/nav.jsx');
const Navbar = require('client/homebrew/navbar/navbar.jsx');
const RecentNavItem = require('client/homebrew/navbar/recent.navitem.jsx').both;
const Account = require('client/homebrew/navbar/account.navitem.jsx');
const NewBrew = require('client/homebrew/navbar/newbrew.navitem.jsx');
const HelpNavItem = require('client/homebrew/navbar/help.navitem.jsx');
const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const ErrorIndex = require('../errorPage/errors/errorIndex.js');

2343
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.19.3",
"version": "3.20.0",
"type": "module",
"engines": {
"npm": "^10.8.x",
@@ -44,7 +44,9 @@
"phb": "node --experimental-require-module scripts/phb.js",
"prod": "set NODE_ENV=production && npm run build",
"postinstall": "npm run build",
"start": "node --experimental-require-module server.js"
"start": "node --experimental-require-module server.js",
"docker:build": "docker build -t ${DOCKERID}/homebrewery:$npm_package_version .",
"docker:publish": "docker login && docker push ${DOCKERID}/homebrewery:$npm_package_version"
},
"author": "stolksdorf",
"license": "MIT",
@@ -86,16 +88,16 @@
"@babel/core": "^7.28.4",
"@babel/plugin-transform-runtime": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.27.1",
"@babel/preset-react": "^7.28.5",
"@babel/runtime": "^7.28.4",
"@dmsnell/diff-match-patch": "^1.1.0",
"@googleapis/drive": "^18.0.0",
"@googleapis/drive": "^19.2.0",
"@sanity/diff-match-patch": "^3.2.0",
"body-parser": "^2.2.0",
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.7",
"core-js": "^3.46.0",
"core-js": "^3.47.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
@@ -107,28 +109,29 @@
"fs-extra": "11.3.2",
"hash-wasm": "^4.12.0",
"idb-keyval": "^6.2.2",
"js-yaml": "^4.1.0",
"js-yaml": "^4.1.1",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "15.0.12",
"marked-alignment-paragraphs": "^1.0.0",
"marked-definition-lists": "^1.0.1",
"marked-emoji": "^2.0.1",
"marked-emoji": "^2.0.2",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.1.2",
"marked-gfm-heading-id": "^4.1.3",
"marked-nonbreaking-spaces": "^1.0.1",
"marked-smartypants-lite": "^1.0.3",
"marked-subsuper-text": "^1.0.4",
"marked-variables": "^1.0.4",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.19.1",
"mongoose": "^8.20.0",
"nanoid": "5.1.6",
"nconf": "^0.13.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
"react-router": "^7.9.4",
"react-router": "^7.9.6",
"romans": "^3.1.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.2.1",
@@ -138,8 +141,8 @@
"devDependencies": {
"@stylistic/stylelint-plugin": "^4.0.0",
"babel-plugin-transform-import-meta": "^2.3.3",
"eslint": "^9.37.0",
"eslint-plugin-jest": "^29.0.1",
"eslint": "^9.39.1",
"eslint-plugin-jest": "^29.1.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.4.0",
"jest": "^30.2.0",

View File

@@ -6,6 +6,7 @@ import config from './config.js';
let serviceAuth;
let clientEmail;
if(!config.get('service_account')){
const reset = '\x1b[0m'; // Reset to default style
const yellow = '\x1b[33m'; // yellow color
@@ -15,6 +16,10 @@ if(!config.get('service_account')){
JSON.parse(config.get('service_account')) :
config.get('service_account');
if(keys?.client_email) {
clientEmail = keys.client_email;
}
try {
serviceAuth = googleDrive.auth.fromJSON(keys);
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];
@@ -227,14 +232,30 @@ const GoogleActions = {
if(!obj) return;
if(clientEmail) {
await drive.permissions.create({
resource : {
type : 'user',
emailAddress : clientEmail,
role : 'writer'
},
fileId : obj.data.id,
fields : 'id',
})
.catch((err)=>{
console.log('Error adding Service Account permissions on Google Drive file');
console.error(err);
});
}
await drive.permissions.create({
resource : { type : 'anyone',
role : 'writer' },
role : 'writer' },
fileId : obj.data.id,
fields : 'id',
})
.catch((err)=>{
console.log('Error updating permissions');
console.log('Error adding "Anyone" permissions on Google Drive file');
console.error(err);
});

View File

@@ -4,7 +4,7 @@ import { model as HomebrewModel } from './homebrew.model.js';
import express from 'express';
import zlib from 'zlib';
import GoogleActions from './googleActions.js';
import Markdown from '../shared/naturalcrit/markdown.js';
import Markdown from '../shared/markdown.js';
import yaml from 'js-yaml';
import asyncHandler from 'express-async-handler';
import { nanoid } from 'nanoid';

View File

@@ -1,117 +1,29 @@
/* eslint-disable max-depth */
/* eslint-disable max-lines */
import _ from 'lodash';
import { Parser as MathParser } from 'expr-eval';
import { marked as Marked } from 'marked';
import MarkedExtendedTables from 'marked-extended-tables';
import MarkedDefinitionLists from 'marked-definition-lists';
import MarkedAlignedParagraphs from 'marked-alignment-paragraphs';
import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces';
import MarkedSubSuperText from 'marked-subsuper-text';
import { markedVariables,
setMarkedVariablePage,
setMarkedVariable,
getMarkedVariable } from 'marked-variables';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
import { romanize } from 'romans';
import writtenNumber from 'written-number';
//Icon fonts included so they can appear in emoji autosuggest dropdown
import diceFont from '../../themes/fonts/iconFonts/diceFont.js';
import elderberryInn from '../../themes/fonts/iconFonts/elderberryInn.js';
import gameIcons from '../../themes/fonts/iconFonts/gameIcons.js';
import fontAwesome from '../../themes/fonts/iconFonts/fontAwesome.js';
import diceFont from '../themes/fonts/iconFonts/diceFont.js';
import elderberryInn from '../themes/fonts/iconFonts/elderberryInn.js';
import gameIcons from '../themes/fonts/iconFonts/gameIcons.js';
import fontAwesome from '../themes/fonts/iconFonts/fontAwesome.js';
const renderer = new Marked.Renderer();
const tokenizer = new Marked.Tokenizer();
//Limit math features to simple items
const mathParser = new MathParser({
operators : {
// These default to true, but are included to be explicit
add : true,
subtract : true,
multiply : true,
divide : true,
power : true,
round : true,
floor : true,
ceil : true,
abs : true,
sin : false, cos : false, tan : false, asin : false, acos : false,
atan : false, sinh : false, cosh : false, tanh : false, asinh : false,
acosh : false, atanh : false, sqrt : false, cbrt : false, log : false,
log2 : false, ln : false, lg : false, log10 : false, expm1 : false,
log1p : false, trunc : false, join : false, sum : false, indexOf : false,
'-' : false, '+' : false, exp : false, not : false, length : false,
'!' : false, sign : false, random : false, fac : false, min : false,
max : false, hypot : false, pyt : false, pow : false, atan2 : false,
'if' : false, gamma : false, roundTo : false, map : false, fold : false,
filter : false,
remainder : false, factorial : false,
comparison : false, concatenate : false,
logical : false, assignment : false,
array : false, fndef : false
}
});
// Add sign function
mathParser.functions.sign = function (a) {
if(a >= 0) return '+';
return '-';
};
// Add signed function
mathParser.functions.signed = function (a) {
if(a >= 0) return `+${a}`;
return `${a}`;
};
// Add Roman numeral functions
mathParser.functions.toRomans = function (a) {
return romanize(a);
};
mathParser.functions.toRomansUpper = function (a) {
return romanize(a).toUpperCase();
};
mathParser.functions.toRomansLower = function (a) {
return romanize(a).toLowerCase();
};
// Add character functions
mathParser.functions.toChar = function (a) {
if(a <= 0) return a;
const genChars = function (i) {
return (i > 26 ? genChars(Math.floor((i - 1) / 26)) : '') + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[(i - 1) % 26];
};
return genChars(a);
};
mathParser.functions.toCharUpper = function (a) {
return mathParser.functions.toChar(a).toUpperCase();
};
mathParser.functions.toCharLower = function (a) {
return mathParser.functions.toChar(a).toLowerCase();
};
// Add word functions
mathParser.functions.toWords = function (a) {
return writtenNumber(a);
};
mathParser.functions.toWordsUpper = function (a) {
return mathParser.functions.toWords(a).toUpperCase();
};
mathParser.functions.toWordsLower = function (a) {
return mathParser.functions.toWords(a).toLowerCase();
};
mathParser.functions.toWordsCaps = function (a) {
const words = mathParser.functions.toWords(a).split(' ');
return words.map((word)=>{
return word.replace(/(?:^|\b|\s)(\w)/g, function(w, index) {
return index === 0 ? w.toLowerCase() : w.toUpperCase();
});
}).join(' ');
};
// Normalize variable names; trim edge spaces and shorten blocks of whitespace to 1 space
const normalizeVarNames = (label)=>{
return label.trim().replace(/\s+/g, ' ');
};
//Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (token) {
let html = token.text;
@@ -119,7 +31,12 @@ renderer.html = function (token) {
const openTag = html.substring(0, html.indexOf('>')+1);
html = html.substring(html.indexOf('>')+1);
html = html.substring(0, html.lastIndexOf('</div>'));
return `${openTag} ${Marked.parse(html)} </div>`;
// Repeat the markdown processing for content inside the div, minus the preprocessing and postprocessing hooks which should only run once globally
const opts = Marked.defaults;
const tokens = Marked.lexer(html, opts);
Marked.walkTokens(tokens, opts.walkTokens);
return `${openTag} ${Marked.parser(tokens, opts)} </div>`;
}
return html;
};
@@ -411,248 +328,6 @@ const forcedParagraphBreaks = {
}
};
//v=====--------------------< Variable Handling >-------------------=====v// 242 lines
const replaceVar = function(input, hoist=false, allowUnresolved=false) {
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
const match = regex.exec(input);
const prefix = match[1];
const label = normalizeVarNames(match[2]); // Ensure the label name is normalized as it should be in the var stack.
//v=====--------------------< HANDLE MATH >-------------------=====v//
const mathRegex = /[a-z]+\(|[+\-*/^(),]/g;
const matches = label.split(mathRegex);
const mathVars = matches.filter((match)=>isNaN(match))?.map((s)=>s.trim()); // Capture any variable names
let replacedLabel = label;
if(prefix[0] == '$' && mathVars?.[0] !== label.trim()) {// If there was mathy stuff not captured, let's do math!
mathVars?.forEach((variable)=>{
const foundVar = lookupVar(variable, globalPageNumber, hoist);
if(foundVar && foundVar.resolved && foundVar.content && !isNaN(foundVar.content)) // Only subsitute math values if fully resolved, not empty strings, and numbers
replacedLabel = replacedLabel.replaceAll(new RegExp(`(?<!\\w)(${variable})(?!\\w)`, 'g'), foundVar.content);
});
try {
return mathParser.evaluate(replacedLabel);
} catch {
return undefined; // Return undefined if invalid math result
}
}
//^=====--------------------< HANDLE MATH >-------------------=====^//
const foundVar = lookupVar(label, globalPageNumber, hoist);
if(!foundVar || (!foundVar.resolved && !allowUnresolved))
return undefined; // Return undefined if not found, or parially-resolved vars are not allowed
// url or <url> "title" or 'title' or (title)
const linkRegex = /^([^<\s][^\s]*|<.*?>)(?: ("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\((?:\\\(|\\\)|[^()])*\)))?$/m;
const linkMatch = linkRegex.exec(foundVar.content);
const href = linkMatch ? linkMatch[1] : null; //TODO: TRIM OFF < > IF PRESENT
const title = linkMatch ? linkMatch[2]?.slice(1, -1) : null;
if(!prefix[0] && href) // Link
return `[${label}](${href}${title ? ` "${title}"` : ''})`;
if(prefix[0] == '!' && href) // Image
return `![${label}](${href} ${title ? ` "${title}"` : ''})`;
if(prefix[0] == '$') // Variable
return foundVar.content;
};
const lookupVar = function(label, index, hoist=false) {
while (index >= 0) {
if(globalVarsList[index]?.[label] !== undefined)
return globalVarsList[index][label];
index--;
}
if(hoist) { //If normal lookup failed, attempt hoisting
index = Object.keys(globalVarsList).length; // Move index to start from last page
while (index >= 0) {
if(globalVarsList[index]?.[label] !== undefined)
return globalVarsList[index][label];
index--;
}
}
return undefined;
};
const processVariableQueue = function() {
let resolvedOne = true;
let finalLoop = false;
while (resolvedOne || finalLoop) { // Loop through queue until no more variable calls can be resolved
resolvedOne = false;
for (const item of varsQueue) {
if(item.type == 'text')
continue;
if(item.type == 'varDefBlock') {
const regex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
let match;
let resolved = true;
let tempContent = item.content;
while (match = regex.exec(item.content)) { // regex to find variable calls
const value = replaceVar(match[0], true);
if(value == undefined)
resolved = false;
else
tempContent = tempContent.replaceAll(match[0], value);
}
if(resolved == true || item.content != tempContent) {
resolvedOne = true;
item.content = tempContent;
}
globalVarsList[globalPageNumber][item.varName] = {
content : item.content,
resolved : resolved
};
if(resolved)
item.type = 'resolved';
}
if(item.type == 'varCallBlock' || item.type == 'varCallInline') {
const value = replaceVar(item.content, true, finalLoop); // final loop will just use the best value so far
if(value == undefined)
continue;
resolvedOne = true;
item.content = value;
item.type = 'text';
}
}
varsQueue = varsQueue.filter((item)=>item.type !== 'resolved'); // Remove any fully-resolved variable definitions
if(finalLoop)
break;
if(!resolvedOne)
finalLoop = true;
}
varsQueue = varsQueue.filter((item)=>item.type !== 'varDefBlock');
};
function MarkedVariables() {
return {
hooks : {
preprocess(src) {
const codeBlockSkip = /^(?: {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+|^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})(?:[^\n]*)(?:\n|$)(?:|(?:[\s\S]*?)(?:\n|$))(?: {0,3}\2[~`]* *(?=\n|$))|`[^`]*?`/;
const blockDefRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/; //Matches 3, [4]:5
const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 6, [7]
const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+)\)/; //Matches 8, 9[10](11)
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 12, [13]
// Combine regexes and wrap in parens like so: (regex1)|(regex2)|(regex3)|(regex4)
const combinedRegex = new RegExp([codeBlockSkip, blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map((s)=>`(${s.source})`).join('|'), 'gm');
let lastIndex = 0;
let match;
while ((match = combinedRegex.exec(src)) !== null) {
// Format any matches into tokens and store
if(match.index > lastIndex) { // Any non-variable stuff
varsQueue.push(
{ type : 'text',
varName : null,
content : src.slice(lastIndex, match.index)
});
}
if(match[1]) {
varsQueue.push(
{ type : 'text',
varName : null,
content : match[0]
});
}
if(match[3]) { // Block Definition
const label = match[4] ? normalizeVarNames(match[4]) : null;
const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Normalize text content (except newlines for block-level content)
varsQueue.push(
{ type : 'varDefBlock',
varName : label,
content : content
});
}
if(match[6]) { // Block Call
const label = match[7] ? normalizeVarNames(match[7]) : null;
varsQueue.push(
{ type : 'varCallBlock',
varName : label,
content : match[0]
});
}
if(match[8]) { // Inline Definition
const label = match[10] ? normalizeVarNames(match[10]) : null;
let content = match[11] || null;
// In case of nested (), find the correct matching end )
let level = 0;
let i;
for (i = 0; i < content.length; i++) {
if(content[i] === '\\') {
i++;
} else if(content[i] === '(') {
level++;
} else if(content[i] === ')') {
level--;
if(level < 0)
break;
}
}
combinedRegex.lastIndex = combinedRegex.lastIndex - (content.length - i);
content = content.slice(0, i).trim().replace(/\s+/g, ' ');
varsQueue.push(
{ type : 'varDefBlock',
varName : label,
content : content
});
varsQueue.push(
{ type : 'varCallInline',
varName : label,
content : match[9]
});
}
if(match[12]) { // Inline Call
const label = match[13] ? normalizeVarNames(match[13]) : null;
varsQueue.push(
{ type : 'varCallInline',
varName : label,
content : match[0]
});
}
lastIndex = combinedRegex.lastIndex;
}
if(lastIndex < src.length) {
varsQueue.push(
{ type : 'text',
varName : null,
content : src.slice(lastIndex)
});
}
processVariableQueue();
const output = varsQueue.map((item)=>item.content).join('');
varsQueue = []; // Must clear varsQueue because custom HTML renderer uses Marked.parse which will preprocess again without clearing the array
return output;
}
}
};
};
//^=====--------------------< Variable Handling >-------------------=====^//
// Emoji options
// To add more icon fonts, need to do these things
// 1) Add the font file as .woff2 to themes/fonts/iconFonts folder
@@ -678,7 +353,7 @@ const tableTerminators = [
` *{{[^{\n]*\n.*?\n}}` // mustacheDiv
];
Marked.use(MarkedVariables());
Marked.use(markedVariables());
Marked.use(MarkedDefinitionLists());
Marked.use({ extensions: [forcedParagraphBreaks, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
@@ -805,25 +480,17 @@ const mergeHTMLTags = (originalTags, newTags)=>{
};
};
const globalVarsList = {};
let varsQueue = [];
let globalPageNumber = 0;
const Markdown = {
marked : Marked,
render : (rawBrewText, pageNumber=0)=>{
const lastPageNumber = pageNumber > 0 ? globalVarsList[pageNumber - 1].HB_pageNumber.content : 0;
globalVarsList[pageNumber] = { //Reset global links for current page, to ensure values are parsed in order
'HB_pageNumber' : { //Add document variables for this page
content : !isNaN(Number(lastPageNumber)) ? Number(lastPageNumber) + 1 : lastPageNumber,
resolved : true
}
};
varsQueue = []; //Could move into MarkedVariables()
globalPageNumber = pageNumber;
if(pageNumber==0) {
MarkedGFMResetHeadingIDs();
}
setMarkedVariablePage(pageNumber);
const lastPageNumber = pageNumber > 0 ? getMarkedVariable('HB_pageNumber', pageNumber - 1) : 0;
setMarkedVariable('HB_pageNumber', //Add document variables for this page
!isNaN(Number(lastPageNumber)) ? Number(lastPageNumber) + 1 : lastPageNumber,
pageNumber);
if(pageNumber==0) MarkedGFMResetHeadingIDs();
rawBrewText = rawBrewText.replace(/^\\column(?:break)?$/gm, `\n<div class='columnSplit'></div>\n`);

View File

@@ -1,3 +0,0 @@
module.exports = function(props){
return <svg version='1.1' x='0px' y='0px' viewBox='0 0 80 100' enableBackground='new 0 0 80 80'><g><g><polygon fill='#000000' points='12.9,71.4 7.6,66.1 19.3,54.4 20.7,55.8 10.4,66.1 12.9,68.6 23.2,58.3 24.6,59.7 '/></g><g><path fill='#000000' d='M29,61.6c-1.7,0-3.4-0.7-4.6-1.9l-5.1-5.1c-2.5-2.5-2.5-6.6,0-9.2l0.7-0.7L34.3,59l-0.7,0.7 C32.4,60.9,30.8,61.6,29,61.6z M20.1,47.6c-1.1,1.7-0.9,4.1,0.6,5.6l5.1,5.1c0.8,0.8,2,1.3,3.2,1.3c0.9,0,1.7-0.2,2.4-0.7 L20.1,47.6z'/></g><g><path fill='#000000' d='M12.3,74.8c-0.8,0-1.5-0.3-2-0.8l-5.2-5.2c-0.5-0.5-0.8-1.2-0.8-2c0-0.8,0.3-1.5,0.8-2 c1.1-1.1,2.9-1.1,4,0l5.2,5.2c1.1,1.1,1.1,2.9,0,4C13.8,74.5,13.1,74.8,12.3,74.8z M7.1,65.9c-0.2,0-0.4,0.1-0.6,0.2 c-0.2,0.2-0.2,0.4-0.2,0.6s0.1,0.4,0.2,0.6l5.2,5.2c0.3,0.3,0.9,0.3,1.2,0c0.3-0.3,0.3-0.8,0-1.2l-5.2-5.2 C7.5,66,7.3,65.9,7.1,65.9z'/></g><g><polygon fill='#000000' points='31.7,58.7 30.3,57.3 70,17.6 70,9 62.4,9 23.3,49.4 21.9,48 61.6,7 72,7 72,18.4 '/></g><g><rect x='46' y='6.7' transform='matrix(0.7168 0.6973 -0.6973 0.7168 35.9716 -23.568)' fill='#000000' width='2' height='51.6'/></g><g><rect x='13' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='17' y='57' fill='#000000' width='2' height='7'/></g></g><g><g><polygon fill='#000000' points='68.4,71.4 56.7,59.7 58.1,58.3 68.4,68.6 70.8,66.1 60.5,55.8 61.9,54.4 73.6,66.1 '/></g><g><path fill='#000000' d='M52.2,61.6c-1.7,0-3.4-0.7-4.6-1.9L46.9,59l14.3-14.3l0.7,0.7c2.5,2.5,2.5,6.6,0,9.2l-5.1,5.1 C55.6,60.9,53.9,61.6,52.2,61.6z M49.8,58.9c0.7,0.4,1.5,0.7,2.4,0.7c1.2,0,2.3-0.5,3.2-1.3l5.1-5.1c1.5-1.5,1.7-3.8,0.6-5.6 L49.8,58.9z'/></g><g><path fill='#000000' d='M68.9,74.8c-0.8,0-1.5-0.3-2-0.8c-1.1-1.1-1.1-2.9,0-4l5.2-5.2c1.1-1.1,2.9-1.1,4,0c0.5,0.5,0.8,1.2,0.8,2 c0,0.8-0.3,1.5-0.8,2L70.9,74C70.4,74.5,69.7,74.8,68.9,74.8z M74.2,65.9c-0.2,0-0.4,0.1-0.6,0.2l-5.2,5.2c-0.3,0.3-0.3,0.8,0,1.2 c0.3,0.3,0.9,0.3,1.2,0l5.2-5.2c0.2-0.2,0.2-0.4,0.2-0.6s-0.1-0.4-0.2-0.6C74.6,66,74.4,65.9,74.2,65.9z'/></g><g><rect x='38.6' y='52.3' transform='matrix(0.7082 0.706 -0.706 0.7082 50.8397 -16.4875)' fill='#000000' width='13.4' height='2'/></g><g><polygon fill='#000000' points='30.6,39.9 9,18.4 9,7 19.7,7 41.1,29.1 39.7,30.5 18.8,9 11,9 11,17.6 32,38.5 '/></g><g><rect x='47.8' y='43.1' transform='matrix(0.6959 0.7181 -0.7181 0.6959 48.1381 -25.5246)' fill='#000000' width='12.8' height='2'/></g><g><rect x='12' y='23.1' transform='matrix(0.6974 0.7167 -0.7167 0.6974 25.1384 -11.3825)' fill='#000000' width='28.1' height='2'/></g><g><rect x='43.8' y='46.4' transform='matrix(0.6974 0.7167 -0.7167 0.6974 48.7492 -20.5985)' fill='#000000' width='10' height='2'/></g><g><rect x='66' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='62' y='57' fill='#000000' width='2' height='7'/></g></g></svg>;
};

View File

@@ -1,6 +1,6 @@
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
const source = '<div>*Bold text*</div>';

View File

@@ -1,6 +1,6 @@
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
describe('Inline Definition Lists', ()=>{
test('No Term 1 Definition', function() {

View File

@@ -1,4 +1,4 @@
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
const dedent = require('dedent-tabs').default;
// Marked.js adds line returns after closing tags on some default tokens.

View File

@@ -1,6 +1,6 @@
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
describe('Hard Breaks', ()=>{
test('Single Break', function() {

View File

@@ -1,7 +1,7 @@
/* eslint-disable max-lines */
const dedent = require('dedent-tabs').default;
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.

View File

@@ -1,6 +1,6 @@
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
describe('Non-Breaking Spaces Interactions', ()=>{
test('I am actually a single-line definition list!', function() {

View File

@@ -1,6 +1,6 @@
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
describe('Justification', ()=>{
test('Left Justify', function() {

View File

@@ -1,7 +1,7 @@
/* eslint-disable max-lines */
const dedent = require('dedent-tabs').default;
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'markdown.js';
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.

View File

@@ -1,4 +1,4 @@
import Markdown from '../../../../shared/naturalcrit/markdown.js';
import Markdown from '../../../../shared/markdown.js';
module.exports = {
createFooterFunc : function(headerSize=1){

View File

@@ -492,7 +492,7 @@ body { counter-reset : page-numbers 0; }
.pageNumber { left : 30px; }
}
.resetCounting { counter-set : page-numbers 1; }
&:has(.resetCounting) { counter-set : page-numbers 1; }
&:not(:has(.skipCounting)) { counter-increment : page-numbers; }

View File

@@ -1,6 +1,6 @@
{
"name" : "UnearthedArcana",
"renderer" : "V3",
"baseTheme" : false,
"baseTheme" : "Blank",
"baseSnippets" : false
}

View File

@@ -39,7 +39,7 @@
"UnearthedArcana": {
"name": "UnearthedArcana",
"renderer": "V3",
"baseTheme": false,
"baseTheme": "Blank",
"baseSnippets": false,
"path": "UnearthedArcana"
}