mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-31 08:42:40 +00:00
Merge branch 'master' into experimentalNotificationDB
This commit is contained in:
47
changelog.md
47
changelog.md
@@ -80,6 +80,53 @@ pre {
|
|||||||
## changelog
|
## changelog
|
||||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||||
|
|
||||||
|
### Thursday 17/08/2023 - v3.9.2
|
||||||
|
{{taskList
|
||||||
|
|
||||||
|
##### Calculuschild
|
||||||
|
|
||||||
|
* [x] Fix links to certain old Google Drive files
|
||||||
|
|
||||||
|
Fixes issue [#2917](https://github.com/naturalcrit/homebrewery/issues/2917)
|
||||||
|
|
||||||
|
##### G-Ambatte
|
||||||
|
|
||||||
|
* [x] Menus now open on click, and internally consistent
|
||||||
|
|
||||||
|
Fixes issue [#2702](https://github.com/naturalcrit/homebrewery/issues/2702), [#2782](https://github.com/naturalcrit/homebrewery/issues/2782)
|
||||||
|
|
||||||
|
* [x] Add smarter footer snippet
|
||||||
|
|
||||||
|
Fixes issue [#2289](https://github.com/naturalcrit/homebrewery/issues/2289)
|
||||||
|
|
||||||
|
* [x] Add sanitization in Style editor
|
||||||
|
|
||||||
|
Fixes issue [#1437](https://github.com/naturalcrit/homebrewery/issues/1437)
|
||||||
|
|
||||||
|
* [x] Rework class table snippets to remove unnecessary randomness
|
||||||
|
|
||||||
|
Fixes issue [#2964](https://github.com/naturalcrit/homebrewery/issues/2964)
|
||||||
|
|
||||||
|
* [x] Add User Page link to Google Drive file for file owners, add icons for additional storage locations
|
||||||
|
|
||||||
|
Fixes issue [#2954](https://github.com/naturalcrit/homebrewery/issues/2954)
|
||||||
|
|
||||||
|
* [x] Add default save location selection to Account Page
|
||||||
|
|
||||||
|
Fixes issue [#2943](https://github.com/naturalcrit/homebrewery/issues/2943)
|
||||||
|
|
||||||
|
##### 5e-Cleric
|
||||||
|
|
||||||
|
* [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)
|
||||||
|
|
||||||
|
##### Gazook89
|
||||||
|
|
||||||
|
* [x] Adjustments to improve mobile viewing
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
### Wednesday 28/06/2023 - v3.9.1
|
### Wednesday 28/06/2023 - v3.9.1
|
||||||
{{taskList
|
{{taskList
|
||||||
|
|
||||||
|
|||||||
@@ -147,11 +147,11 @@ const BrewRenderer = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderPage : function(pageText, index){
|
renderPage : function(pageText, index){
|
||||||
const cleanPageText = this.sanitizeScriptTags(pageText);
|
let cleanPageText = this.sanitizeScriptTags(pageText);
|
||||||
if(this.props.renderer == 'legacy')
|
if(this.props.renderer == 'legacy')
|
||||||
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(cleanPageText) }} key={index} />;
|
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(cleanPageText) }} key={index} />;
|
||||||
else {
|
else {
|
||||||
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
cleanPageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||||
return (
|
return (
|
||||||
<div className='page' id={`p${index + 1}`} key={index} >
|
<div className='page' id={`p${index + 1}`} key={index} >
|
||||||
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(cleanPageText) }} />
|
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(cleanPageText) }} />
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
|||||||
|
|
||||||
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||||
|
|
||||||
|
let SAVEKEY = '';
|
||||||
|
|
||||||
const AccountPage = createClass({
|
const AccountPage = createClass({
|
||||||
displayName : 'AccountPage',
|
displayName : 'AccountPage',
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
@@ -29,6 +31,27 @@ const AccountPage = createClass({
|
|||||||
uiItems : this.props.uiItems
|
uiItems : this.props.uiItems
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
componentDidMount : function(){
|
||||||
|
if(!this.state.saveLocation && this.props.uiItems.username) {
|
||||||
|
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${this.props.uiItems.username}`;
|
||||||
|
let saveLocation = window.localStorage.getItem(SAVEKEY);
|
||||||
|
saveLocation = saveLocation ?? (this.state.uiItems.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY');
|
||||||
|
this.makeActive(saveLocation);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
makeActive : function(newSelection){
|
||||||
|
if(this.state.saveLocation == newSelection) return;
|
||||||
|
window.localStorage.setItem(SAVEKEY, newSelection);
|
||||||
|
this.setState({
|
||||||
|
saveLocation : newSelection
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderButton : function(name, key, shouldRender=true){
|
||||||
|
if(!shouldRender) return;
|
||||||
|
return <button className={this.state.saveLocation==key ? 'active' : ''} onClick={()=>{this.makeActive(key);}}>{name}</button>;
|
||||||
|
},
|
||||||
|
|
||||||
renderNavItems : function() {
|
renderNavItems : function() {
|
||||||
return <Navbar>
|
return <Navbar>
|
||||||
@@ -61,6 +84,11 @@ const AccountPage = createClass({
|
|||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div className='dataGroup'>
|
||||||
|
<h4>Default Save Location</h4>
|
||||||
|
{this.renderButton('Homebrewery', 'HOMEBREWERY')}
|
||||||
|
{this.renderButton('Google Drive', 'GOOGLE-DRIVE', this.state.uiItems.googleId)}
|
||||||
|
</div>
|
||||||
</>;
|
</>;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const moment = require('moment');
|
|||||||
const request = require('../../../../utils/request-middleware.js');
|
const request = require('../../../../utils/request-middleware.js');
|
||||||
|
|
||||||
const googleDriveIcon = require('../../../../googleDrive.svg');
|
const googleDriveIcon = require('../../../../googleDrive.svg');
|
||||||
|
const homebreweryIcon = require('../../../../thumbnail.png');
|
||||||
const dedent = require('dedent-tabs').default;
|
const dedent = require('dedent-tabs').default;
|
||||||
|
|
||||||
const BrewItem = createClass({
|
const BrewItem = createClass({
|
||||||
@@ -90,11 +91,17 @@ const BrewItem = createClass({
|
|||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderGoogleDriveIcon : function(){
|
renderStorageIcon : function(){
|
||||||
if(!this.props.brew.googleId) return;
|
if(this.props.brew.googleId) {
|
||||||
|
return <span title={this.props.brew.webViewLink ? 'Your Google Drive Storage': 'Another User\'s Google Drive Storage'}>
|
||||||
|
<a href={this.props.brew.webViewLink} target='_blank'>
|
||||||
|
<img className='googleDriveIcon' src={googleDriveIcon} alt='googleDriveIcon' />
|
||||||
|
</a>
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
return <span>
|
return <span title='Homebrewery Storage'>
|
||||||
<img className='googleDriveIcon' src={googleDriveIcon} alt='googleDriveIcon' />
|
<img className='homebreweryIcon' src={homebreweryIcon} alt='homebreweryIcon' />
|
||||||
</span>;
|
</span>;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -144,7 +151,7 @@ const BrewItem = createClass({
|
|||||||
Last updated: ${moment(brew.updatedAt).local().format(dateFormatString)}`}>
|
Last updated: ${moment(brew.updatedAt).local().format(dateFormatString)}`}>
|
||||||
<i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()}
|
<i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()}
|
||||||
</span>
|
</span>
|
||||||
{this.renderGoogleDriveIcon()}
|
{this.renderStorageIcon()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='links'>
|
<div className='links'>
|
||||||
|
|||||||
@@ -98,4 +98,11 @@
|
|||||||
padding : 0px;
|
padding : 0px;
|
||||||
margin : -5px;
|
margin : -5px;
|
||||||
}
|
}
|
||||||
|
.homebreweryIcon {
|
||||||
|
mix-blend-mode : darken;
|
||||||
|
height : 24px;
|
||||||
|
position : relative;
|
||||||
|
top : 5px;
|
||||||
|
left : -5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,23 @@
|
|||||||
margin : 5px 0px;
|
margin : 5px 0px;
|
||||||
border : 2px solid black;
|
border : 2px solid black;
|
||||||
border-radius : 5px;
|
border-radius : 5px;
|
||||||
|
button {
|
||||||
|
background-color : transparent;
|
||||||
|
border : 1px solid black;
|
||||||
|
border-radius : 5px;
|
||||||
|
width : 125px;
|
||||||
|
color : black;
|
||||||
|
margin-right : 5px;
|
||||||
|
&.active {
|
||||||
|
background-color: #0007;
|
||||||
|
color: white;
|
||||||
|
&:before {
|
||||||
|
content: '\f00c';
|
||||||
|
font-family: 'FONT AWESOME 5 FREE';
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
h1, h2, h3, h4 {
|
h1, h2, h3, h4 {
|
||||||
width : 100%;
|
width : 100%;
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|||||||
|
|
||||||
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
|
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
|
||||||
|
|
||||||
const BREWKEY = 'homebrewery-new';
|
const BREWKEY = 'homebrewery-new';
|
||||||
const STYLEKEY = 'homebrewery-new-style';
|
const STYLEKEY = 'homebrewery-new-style';
|
||||||
const METAKEY = 'homebrewery-new-meta';
|
const METAKEY = 'homebrewery-new-meta';
|
||||||
|
let SAVEKEY;
|
||||||
|
|
||||||
|
|
||||||
const NewPage = createClass({
|
const NewPage = createClass({
|
||||||
@@ -62,12 +63,16 @@ const NewPage = createClass({
|
|||||||
brew.renderer = metaStorage?.renderer ?? brew.renderer;
|
brew.renderer = metaStorage?.renderer ?? brew.renderer;
|
||||||
brew.theme = metaStorage?.theme ?? brew.theme;
|
brew.theme = metaStorage?.theme ?? brew.theme;
|
||||||
brew.lang = metaStorage?.lang ?? brew.lang;
|
brew.lang = metaStorage?.lang ?? brew.lang;
|
||||||
|
|
||||||
this.setState({
|
|
||||||
brew : brew
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`;
|
||||||
|
const saveStorage = localStorage.getItem(SAVEKEY) || 'HOMEBREWERY';
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
brew : brew,
|
||||||
|
saveGoogle : (saveStorage == 'GOOGLE-DRIVE' && this.state.saveGoogle)
|
||||||
|
});
|
||||||
|
|
||||||
localStorage.setItem(BREWKEY, brew.text);
|
localStorage.setItem(BREWKEY, brew.text);
|
||||||
if(brew.style)
|
if(brew.style)
|
||||||
localStorage.setItem(STYLEKEY, brew.style);
|
localStorage.setItem(STYLEKEY, brew.style);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const template = async function(name, title='', props = {}){
|
|||||||
return `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" />
|
||||||
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
|
<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="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||||
<link href=${`/${name}/bundle.css`} rel='stylesheet' />
|
<link href=${`/${name}/bundle.css`} rel='stylesheet' />
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ npm install
|
|||||||
npm audit fix
|
npm audit fix
|
||||||
npm run postinstall
|
npm run postinstall
|
||||||
|
|
||||||
cp freebsd/rc.d/homebrewery /usr/local/etc/rc.d/
|
cp install/freebsd/rc.d/homebrewery /usr/local/etc/rc.d/
|
||||||
chmod +x /usr/local/etc/rc.d/homebrewery
|
chmod +x /usr/local/etc/rc.d/homebrewery
|
||||||
|
|
||||||
sysrc homebrewery_enable=YES
|
sysrc homebrewery_enable=YES
|
||||||
|
|||||||
1509
package-lock.json
generated
1509
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "3.9.1",
|
"version": "3.9.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.16.x"
|
"node": ">=18.16.x"
|
||||||
},
|
},
|
||||||
@@ -78,9 +78,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.8",
|
"@babel/core": "^7.22.10",
|
||||||
"@babel/plugin-transform-runtime": "^7.22.7",
|
"@babel/plugin-transform-runtime": "^7.22.10",
|
||||||
"@babel/preset-env": "^7.22.7",
|
"@babel/preset-env": "^7.22.10",
|
||||||
"@babel/preset-react": "^7.22.5",
|
"@babel/preset-react": "^7.22.5",
|
||||||
"@googleapis/drive": "^5.1.0",
|
"@googleapis/drive": "^5.1.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
@@ -99,30 +99,30 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "5.1.1",
|
"marked": "5.1.1",
|
||||||
"marked-extended-tables": "^1.0.6",
|
"marked-extended-tables": "^1.0.6",
|
||||||
"marked-gfm-heading-id": "^3.0.4",
|
"marked-gfm-heading-id": "^3.0.6",
|
||||||
"marked-smartypants-lite": "^1.0.0",
|
"marked-smartypants-lite": "^1.0.0",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"mongoose": "^7.3.2",
|
"mongoose": "^7.4.3",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.0",
|
"nconf": "^0.12.0",
|
||||||
"npm": "^9.8.0",
|
"npm": "^9.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-frame-component": "^4.1.3",
|
"react-frame-component": "^4.1.3",
|
||||||
"react-router-dom": "6.14.1",
|
"react-router-dom": "6.15.0",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"superagent": "^6.1.0",
|
"superagent": "^6.1.0",
|
||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.44.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-jest": "^27.2.2",
|
"eslint-plugin-jest": "^27.2.3",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"jest": "^29.6.1",
|
"jest": "^29.6.2",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"stylelint": "^15.10.1",
|
"stylelint": "^15.10.3",
|
||||||
"stylelint-config-recess-order": "^4.3.0",
|
"stylelint-config-recess-order": "^4.3.0",
|
||||||
"stylelint-config-recommended": "^13.0.0",
|
"stylelint-config-recommended": "^13.0.0",
|
||||||
"stylelint-stylistic": "^0.4.3",
|
"stylelint-stylistic": "^0.4.3",
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ app.get('/user/:username', async (req, res, next)=>{
|
|||||||
brew.pageCount = googleBrews[match].pageCount;
|
brew.pageCount = googleBrews[match].pageCount;
|
||||||
brew.renderer = googleBrews[match].renderer;
|
brew.renderer = googleBrews[match].renderer;
|
||||||
brew.version = googleBrews[match].version;
|
brew.version = googleBrews[match].version;
|
||||||
|
brew.webViewLink = googleBrews[match].webViewLink;
|
||||||
googleBrews.splice(match, 1);
|
googleBrews.splice(match, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const GoogleActions = {
|
|||||||
const obj = await drive.files.list({
|
const obj = await drive.files.list({
|
||||||
pageSize : 1000,
|
pageSize : 1000,
|
||||||
pageToken : NextPageToken || '',
|
pageToken : NextPageToken || '',
|
||||||
fields : 'nextPageToken, files(id, name, description, createdTime, modifiedTime, properties)',
|
fields : 'nextPageToken, files(id, name, description, createdTime, modifiedTime, properties, webViewLink)',
|
||||||
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
|
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
|
||||||
})
|
})
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
@@ -139,7 +139,8 @@ const GoogleActions = {
|
|||||||
published : file.properties.published ? file.properties.published == 'true' : false,
|
published : file.properties.published ? file.properties.published == 'true' : false,
|
||||||
systems : [],
|
systems : [],
|
||||||
lang : file.properties.lang,
|
lang : file.properties.lang,
|
||||||
thumbnail : file.properties.thumbnail
|
thumbnail : file.properties.thumbnail,
|
||||||
|
webViewLink : file.webViewLink
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return brews;
|
return brews;
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.codeEditor{
|
.codeEditor{
|
||||||
|
@media screen and (pointer : coarse) {
|
||||||
|
font-size : 16px;
|
||||||
|
}
|
||||||
.CodeMirror-foldmarker {
|
.CodeMirror-foldmarker {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ const SplitPane = createClass({
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleUp : function(){
|
handleUp : function(e){
|
||||||
|
e.preventDefault();
|
||||||
if(this.state.isDragging){
|
if(this.state.isDragging){
|
||||||
this.props.onDragFinish(this.state.currentDividerPos);
|
this.props.onDragFinish(this.state.currentDividerPos);
|
||||||
window.localStorage.setItem(this.props.storageKey, this.state.currentDividerPos);
|
window.localStorage.setItem(this.props.storageKey, this.state.currentDividerPos);
|
||||||
@@ -78,6 +79,7 @@ const SplitPane = createClass({
|
|||||||
handleMove : function(e){
|
handleMove : function(e){
|
||||||
if(!this.state.isDragging) return;
|
if(!this.state.isDragging) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
const newSize = this.limitPosition(e.pageX);
|
const newSize = this.limitPosition(e.pageX);
|
||||||
this.setState({
|
this.setState({
|
||||||
currentDividerPos : newSize,
|
currentDividerPos : newSize,
|
||||||
@@ -122,7 +124,7 @@ const SplitPane = createClass({
|
|||||||
renderDivider : function(){
|
renderDivider : function(){
|
||||||
return <>
|
return <>
|
||||||
{this.renderMoveArrows()}
|
{this.renderMoveArrows()}
|
||||||
<div className='divider' onMouseDown={this.handleDown} >
|
<div className='divider' onPointerDown={this.handleDown} >
|
||||||
<div className='dots'>
|
<div className='dots'>
|
||||||
<i className='fas fa-circle' />
|
<i className='fas fa-circle' />
|
||||||
<i className='fas fa-circle' />
|
<i className='fas fa-circle' />
|
||||||
@@ -133,7 +135,7 @@ const SplitPane = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
return <div className='splitPane' onPointerMove={this.handleMove} onPointerUp={this.handleUp}>
|
||||||
<Pane
|
<Pane
|
||||||
ref='pane1'
|
ref='pane1'
|
||||||
width={this.state.currentDividerPos}
|
width={this.state.currentDividerPos}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
flex : 1;
|
flex : 1;
|
||||||
}
|
}
|
||||||
.divider{
|
.divider{
|
||||||
|
touch-action : none;
|
||||||
display : table;
|
display : table;
|
||||||
height : 100%;
|
height : 100%;
|
||||||
width : 15px;
|
width : 15px;
|
||||||
|
|||||||
@@ -220,34 +220,51 @@ module.exports = [
|
|||||||
view : 'text',
|
view : 'text',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : 'Class Table',
|
name : 'Class Tables',
|
||||||
icon : 'fas fa-table',
|
icon : 'fas fa-table',
|
||||||
gen : ClassTableGen.full('classTable,frame,decoration,wide'),
|
gen : ClassTableGen.full('classTable,frame,decoration,wide'),
|
||||||
},
|
subsnippets : [
|
||||||
{
|
{
|
||||||
name : 'Class Table (unframed)',
|
name : 'Martial Class Table',
|
||||||
icon : 'fas fa-border-none',
|
icon : 'fas fa-table',
|
||||||
gen : ClassTableGen.full('classTable,wide'),
|
gen : ClassTableGen.non('classTable,frame,decoration'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : '1/2 Class Table',
|
name : 'Martial Class Table (unframed)',
|
||||||
icon : 'fas fa-list-alt',
|
icon : 'fas fa-border-none',
|
||||||
gen : ClassTableGen.half('classTable,decoration,frame'),
|
gen : ClassTableGen.non('classTable'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : '1/2 Class Table (unframed)',
|
name : 'Full Caster Class Table',
|
||||||
icon : 'fas fa-border-none',
|
icon : 'fas fa-table',
|
||||||
gen : ClassTableGen.half('classTable'),
|
gen : ClassTableGen.full('classTable,frame,decoration,wide'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : '1/3 Class Table',
|
name : 'Full Caster Class Table (unframed)',
|
||||||
icon : 'fas fa-border-all',
|
icon : 'fas fa-border-none',
|
||||||
gen : ClassTableGen.third('classTable,frame'),
|
gen : ClassTableGen.full('classTable,wide'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : '1/3 Class Table (unframed)',
|
name : 'Half Caster Class Table',
|
||||||
icon : 'fas fa-border-none',
|
icon : 'fas fa-list-alt',
|
||||||
gen : ClassTableGen.third('classTable'),
|
gen : ClassTableGen.half('classTable,frame,decoration,wide'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Half Caster Class Table (unframed)',
|
||||||
|
icon : 'fas fa-border-none',
|
||||||
|
gen : ClassTableGen.half('classTable,wide'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Third Caster Spell Table',
|
||||||
|
icon : 'fas fa-border-all',
|
||||||
|
gen : ClassTableGen.third('classTable,frame,decoration'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Third Caster Spell Table (unframed)',
|
||||||
|
icon : 'fas fa-border-none',
|
||||||
|
gen : ClassTableGen.third('classTable'),
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Rune Table',
|
name : 'Rune Table',
|
||||||
|
|||||||
@@ -1,132 +1,138 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const dedent = require('dedent-tabs').default;
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
'Astrological Botany',
|
'Astrological Botany', 'Biochemical Sorcery', 'Civil Divination',
|
||||||
'Biochemical Sorcery',
|
'Consecrated Augury', 'Demonic Anthropology', 'Divinatory Mineralogy',
|
||||||
'Civil Divination',
|
'Exo Interfacer', 'Genetic Banishing', 'Gunpowder Torturer',
|
||||||
'Consecrated Augury',
|
'Gunslinger Corruptor', 'Hermetic Geography', 'Immunological Cultist',
|
||||||
'Demonic Anthropology',
|
'Malefic Chemist', 'Mathematical Pharmacy', 'Nuclear Biochemistry',
|
||||||
'Divinatory Mineralogy',
|
'Orbital Gravedigger', 'Pharmaceutical Outlaw', 'Phased Linguist',
|
||||||
'Exo Interfacer',
|
'Plasma Gunslinger', 'Police Necromancer', 'Ritual Astronomy',
|
||||||
'Genetic Banishing',
|
'Sixgun Poisoner', 'Seismological Alchemy', 'Spiritual Illusionism',
|
||||||
'Gunpowder Torturer',
|
'Statistical Occultism', 'Spell Analyst', 'Torque Interfacer'
|
||||||
'Gunslinger Corruptor',
|
].map((f)=>_.padEnd(f, 21)); // Pad to equal length of 21 chars long
|
||||||
'Hermetic Geography',
|
|
||||||
'Immunological Cultist',
|
const classnames = [
|
||||||
'Malefic Chemist',
|
'Ackerman', 'Berserker-Typist', 'Concierge', 'Fishmonger',
|
||||||
'Mathematical Pharmacy',
|
'Haberdasher', 'Manicurist', 'Netrunner', 'Weirkeeper'
|
||||||
'Nuclear Biochemistry',
|
|
||||||
'Orbital Gravedigger',
|
|
||||||
'Pharmaceutical Outlaw',
|
|
||||||
'Phased Linguist',
|
|
||||||
'Plasma Gunslinger',
|
|
||||||
'Police Necromancer',
|
|
||||||
'Ritual Astronomy',
|
|
||||||
'Sixgun Poisoner',
|
|
||||||
'Seismological Alchemy',
|
|
||||||
'Spiritual Illusionism',
|
|
||||||
'Statistical Occultism',
|
|
||||||
'Spell Analyst',
|
|
||||||
'Torque Interfacer'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const classnames = ['Ackerman', 'Berserker-Typist', 'Concierge', 'Fishmonger',
|
|
||||||
'Haberdasher', 'Manicurist', 'Netrunner', 'Weirkeeper'];
|
|
||||||
|
|
||||||
const levels = ['1st', '2nd', '3rd', '4th', '5th',
|
|
||||||
'6th', '7th', '8th', '9th', '10th',
|
|
||||||
'11th', '12th', '13th', '14th', '15th',
|
|
||||||
'16th', '17th', '18th', '19th', '20th'];
|
|
||||||
|
|
||||||
const profBonus = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6];
|
|
||||||
|
|
||||||
const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
|
|
||||||
|
|
||||||
const drawSlots = function(Slots, rows, padding){
|
|
||||||
let slots = Number(Slots);
|
|
||||||
return _.times(rows, function(i){
|
|
||||||
const max = maxes[i];
|
|
||||||
if(slots < 1) return _.pad('—', padding);
|
|
||||||
const res = _.min([max, slots]);
|
|
||||||
slots -= res;
|
|
||||||
return _.pad(res.toString(), padding);
|
|
||||||
}).join(' | ');
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
full : function(classes){
|
non : function(snippetClasses){
|
||||||
const classname = _.sample(classnames);
|
return dedent`
|
||||||
|
{{${snippetClasses}
|
||||||
|
##### The ${_.sample(classnames)}
|
||||||
let cantrips = 3;
|
| Level | Proficiency Bonus | Features | ${_.sample(features)} |
|
||||||
let spells = 1;
|
|:-----:|:-----------------:|:---------|:---------------------:|
|
||||||
let slots = 2;
|
| 1st | +2 | ${_.sample(features)} | 2 |
|
||||||
return `{{${classes}\n##### The ${classname}\n` +
|
| 2nd | +2 | ${_.sample(features)} | 2 |
|
||||||
`| Level | Proficiency | Features | Cantrips | Spells | --- Spell Slots Per Spell Level ---|||||||||\n`+
|
| 3rd | +2 | ${_.sample(features)} | 3 |
|
||||||
`| ^| Bonus ^| ^| Known ^| Known ^|1st |2nd |3rd |4th |5th |6th |7th |8th |9th |\n`+
|
| 4th | +2 | ${_.sample(features)} | 3 |
|
||||||
`|:-----:|:-----------:|:-------------|:--------:|:------:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|\n${
|
| 5th | +3 | ${_.sample(features)} | 3 |
|
||||||
_.map(levels, function(levelName, level){
|
| 6th | +3 | ${_.sample(features)} | 4 |
|
||||||
const res = [
|
| 7th | +3 | ${_.sample(features)} | 4 |
|
||||||
_.pad(levelName, 5),
|
| 8th | +3 | ${_.sample(features)} | 4 |
|
||||||
_.pad(`+${profBonus[level]}`, 2),
|
| 9th | +4 | ${_.sample(features)} | 4 |
|
||||||
_.padEnd(_.sample(features), 21),
|
| 10th | +4 | ${_.sample(features)} | 4 |
|
||||||
_.pad(cantrips.toString(), 8),
|
| 11th | +4 | ${_.sample(features)} | 4 |
|
||||||
_.pad(spells.toString(), 6),
|
| 12th | +4 | ${_.sample(features)} | 5 |
|
||||||
drawSlots(slots, 9, 2),
|
| 13th | +5 | ${_.sample(features)} | 5 |
|
||||||
].join(' | ');
|
| 14th | +5 | ${_.sample(features)} | 5 |
|
||||||
|
| 15th | +5 | ${_.sample(features)} | 5 |
|
||||||
cantrips += _.random(0, 1);
|
| 16th | +5 | ${_.sample(features)} | 5 |
|
||||||
spells += _.random(0, 1);
|
| 17th | +6 | ${_.sample(features)} | 6 |
|
||||||
slots += _.random(0, 2);
|
| 18th | +6 | ${_.sample(features)} | 6 |
|
||||||
|
| 19th | +6 | ${_.sample(features)} | 6 |
|
||||||
return `| ${res} |`;
|
| 20th | +6 | ${_.sample(features)} | unlimited |
|
||||||
}).join('\n')}\n}}\n\n`;
|
}}\n\n`;
|
||||||
},
|
},
|
||||||
|
|
||||||
half : function(classes){
|
full : function(snippetClasses){
|
||||||
const classname = _.sample(classnames);
|
return dedent`
|
||||||
|
{{${snippetClasses}
|
||||||
let featureScore = 1;
|
##### The ${_.sample(classnames)}
|
||||||
return `{{${classes}\n##### The ${classname}\n` +
|
| Level | Proficiency | Features | Cantrips | --- Spell Slots Per Spell Level ---|||||||||
|
||||||
`| Level | Proficiency Bonus | Features | ${_.pad(_.sample(features), 21)} |\n` +
|
| ^| Bonus ^| ^| Known ^|1st |2nd |3rd |4th |5th |6th |7th |8th |9th |
|
||||||
`|:-----:|:-----------------:|:---------|:---------------------:|\n${
|
|:-----:|:-----------:|:-------------|:--------:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|
||||||
_.map(levels, function(levelName, level){
|
| 1st | +2 | ${_.sample(features)} | 2 | 2 | — | — | — | — | — | — | — | — |
|
||||||
const res = [
|
| 2nd | +2 | ${_.sample(features)} | 2 | 3 | — | — | — | — | — | — | — | — |
|
||||||
_.pad(levelName, 5),
|
| 3rd | +2 | ${_.sample(features)} | 2 | 4 | 2 | — | — | — | — | — | — | — |
|
||||||
_.pad(`+${profBonus[level]}`, 2),
|
| 4th | +2 | ${_.sample(features)} | 3 | 4 | 3 | — | — | — | — | — | — | — |
|
||||||
_.padEnd(_.sample(features), 23),
|
| 5th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 2 | — | — | — | — | — | — |
|
||||||
_.pad(`+${featureScore}`, 21),
|
| 6th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 3 | — | — | — | — | — | — |
|
||||||
].join(' | ');
|
| 7th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 1 | — | — | — | — | — |
|
||||||
|
| 8th | +3 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 2 | — | — | — | — | — |
|
||||||
featureScore += _.random(0, 1);
|
| 9th | +4 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 2 | 1 | — | — | — | — |
|
||||||
|
| 10th | +4 | ${_.sample(features)} | 3 | 4 | 3 | 3 | 2 | 1 | — | — | — | — |
|
||||||
return `| ${res} |`;
|
| 11th | +4 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | — | — | — |
|
||||||
}).join('\n')}\n}}\n\n`;
|
| 12th | +4 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | — | — | — |
|
||||||
|
| 13th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | — | — |
|
||||||
|
| 14th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | — | — |
|
||||||
|
| 15th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | 1 | — |
|
||||||
|
| 16th | +5 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | 1 | — |
|
||||||
|
| 17th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 2 | 1 | 1 | 1 | 1 | 1 |
|
||||||
|
| 18th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 3 | 1 | 1 | 1 | 1 | 1 |
|
||||||
|
| 19th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 3 | 2 | 2 | 1 | 1 | 1 |
|
||||||
|
| 20th | +6 | ${_.sample(features)} | 4 | 4 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | 1 |
|
||||||
|
}}\n\n`;
|
||||||
},
|
},
|
||||||
|
|
||||||
third : function(classes){
|
half : function(snippetClasses){
|
||||||
const classname = _.sample(classnames);
|
return dedent`
|
||||||
|
{{${snippetClasses}
|
||||||
|
##### The ${_.sample(classnames)}
|
||||||
|
| Level | Proficiency | Features | Spells |--- Spell Slots Per Spell Level ---|||||
|
||||||
|
| ^| Bonus ^| ^| Known ^| 1st | 2nd | 3rd | 4th | 5th |
|
||||||
|
|:-----:|:-----------:|:-------------|:------:|:-----:|:-----:|:-----:|:-----:|:-----:|
|
||||||
|
| 1st | +2 | ${_.sample(features)} | — | — | — | — | — | — |
|
||||||
|
| 2nd | +2 | ${_.sample(features)} | 2 | 2 | — | — | — | — |
|
||||||
|
| 3rd | +2 | ${_.sample(features)} | 3 | 3 | — | — | — | — |
|
||||||
|
| 4th | +2 | ${_.sample(features)} | 3 | 3 | — | — | — | — |
|
||||||
|
| 5th | +3 | ${_.sample(features)} | 4 | 4 | 2 | — | — | — |
|
||||||
|
| 6th | +3 | ${_.sample(features)} | 4 | 4 | 2 | — | — | — |
|
||||||
|
| 7th | +3 | ${_.sample(features)} | 5 | 4 | 3 | — | — | — |
|
||||||
|
| 8th | +3 | ${_.sample(features)} | 5 | 4 | 3 | — | — | — |
|
||||||
|
| 9th | +4 | ${_.sample(features)} | 6 | 4 | 3 | 2 | — | — |
|
||||||
|
| 10th | +4 | ${_.sample(features)} | 6 | 4 | 3 | 2 | — | — |
|
||||||
|
| 11th | +4 | ${_.sample(features)} | 7 | 4 | 3 | 3 | — | — |
|
||||||
|
| 12th | +4 | ${_.sample(features)} | 7 | 4 | 3 | 3 | — | — |
|
||||||
|
| 13th | +5 | ${_.sample(features)} | 8 | 4 | 3 | 3 | 1 | — |
|
||||||
|
| 14th | +5 | ${_.sample(features)} | 8 | 4 | 3 | 3 | 1 | — |
|
||||||
|
| 15th | +5 | ${_.sample(features)} | 9 | 4 | 3 | 3 | 2 | — |
|
||||||
|
| 16th | +5 | ${_.sample(features)} | 9 | 4 | 3 | 3 | 2 | — |
|
||||||
|
| 17th | +6 | ${_.sample(features)} | 10 | 4 | 3 | 3 | 3 | 1 |
|
||||||
|
| 18th | +6 | ${_.sample(features)} | 10 | 4 | 3 | 3 | 3 | 1 |
|
||||||
|
| 19th | +6 | ${_.sample(features)} | 11 | 4 | 3 | 3 | 3 | 2 |
|
||||||
|
| 20th | +6 | ${_.sample(features)} | 11 | 4 | 3 | 3 | 3 | 2 |
|
||||||
|
}}\n\n`;
|
||||||
|
},
|
||||||
|
|
||||||
let cantrips = 3;
|
third : function(snippetClasses){
|
||||||
let spells = 1;
|
return dedent`
|
||||||
let slots = 2;
|
{{${snippetClasses}
|
||||||
return `{{${classes}\n##### ${classname} Spellcasting\n` +
|
##### ${_.sample(classnames)} Spellcasting
|
||||||
`| Class | Cantrips | Spells |--- Spells Slots per Spell Level ---||||\n` +
|
| Level | Cantrips | Spells |--- Spells Slots per Spell Level ---||||
|
||||||
`| Level ^| Known ^| Known ^| 1st | 2nd | 3rd | 4th |\n` +
|
| ^| Known ^| Known ^| 1st | 2nd | 3rd | 4th |
|
||||||
`|:------:|:--------:|:-------:|:-------:|:-------:|:-------:|:-------:|\n${
|
|:-----:|:--------:|:------:|:-------:|:-------:|:-------:|:-------:|
|
||||||
_.map(levels, function(levelName, level){
|
| 3rd | 2 | 3 | 2 | — | — | — |
|
||||||
const res = [
|
| 4th | 2 | 4 | 3 | — | — | — |
|
||||||
_.pad(levelName, 6),
|
| 5th | 2 | 4 | 3 | — | — | — |
|
||||||
_.pad(cantrips.toString(), 8),
|
| 6th | 2 | 4 | 3 | — | — | — |
|
||||||
_.pad(spells.toString(), 7),
|
| 7th | 2 | 5 | 4 | 2 | — | — |
|
||||||
drawSlots(slots, 4, 7),
|
| 8th | 2 | 6 | 4 | 2 | — | — |
|
||||||
].join(' | ');
|
| 9th | 2 | 6 | 4 | 2 | — | — |
|
||||||
|
| 10th | 3 | 7 | 4 | 3 | — | — |
|
||||||
cantrips += _.random(0, 1);
|
| 11th | 3 | 8 | 4 | 3 | — | — |
|
||||||
spells += _.random(0, 1);
|
| 12th | 3 | 8 | 4 | 3 | — | — |
|
||||||
slots += _.random(0, 1);
|
| 13th | 3 | 9 | 4 | 3 | 2 | — |
|
||||||
|
| 14th | 3 | 10 | 4 | 3 | 2 | — |
|
||||||
return `| ${res} |`;
|
| 15th | 3 | 10 | 4 | 3 | 2 | — |
|
||||||
}).join('\n')}\n}}\n\n`;
|
| 16th | 3 | 11 | 4 | 3 | 3 | — |
|
||||||
|
| 17th | 3 | 11 | 4 | 3 | 3 | — |
|
||||||
|
| 18th | 3 | 11 | 4 | 3 | 3 | — |
|
||||||
|
| 19th | 3 | 12 | 4 | 3 | 3 | 1 |
|
||||||
|
| 20th | 3 | 13 | 4 | 3 | 3 | 1 |
|
||||||
|
}}\n\n`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,21 +29,23 @@ const getTOC = (pages)=>{
|
|||||||
|
|
||||||
const res = [];
|
const res = [];
|
||||||
_.each(pages, (page, pageNum)=>{
|
_.each(pages, (page, pageNum)=>{
|
||||||
const lines = page.split('\n');
|
if(!page.includes("{{frontCover}}") && !page.includes("{{insideCover}}") && !page.includes("{{partCover}}") && !page.includes("{{backCover}}")) {
|
||||||
_.each(lines, (line)=>{
|
const lines = page.split('\n');
|
||||||
if(_.startsWith(line, '# ')){
|
_.each(lines, (line)=>{
|
||||||
const title = line.replace('# ', '');
|
if(_.startsWith(line, '# ')){
|
||||||
add1(title, pageNum);
|
const title = line.replace('# ', '');
|
||||||
}
|
add1(title, pageNum);
|
||||||
if(_.startsWith(line, '## ')){
|
}
|
||||||
const title = line.replace('## ', '');
|
if(_.startsWith(line, '## ')){
|
||||||
add2(title, pageNum);
|
const title = line.replace('## ', '');
|
||||||
}
|
add2(title, pageNum);
|
||||||
if(_.startsWith(line, '### ')){
|
}
|
||||||
const title = line.replace('### ', '');
|
if(_.startsWith(line, '### ')){
|
||||||
add3(title, pageNum);
|
const title = line.replace('### ', '');
|
||||||
}
|
add3(title, pageNum);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user