0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-24 16:23:01 +00:00

Compare commits

...

60 Commits

Author SHA1 Message Date
Trevor Buckner
7e38271ac6 Up to v3.11.0 2024-02-21 17:30:15 -05:00
Trevor Buckner
4866eacd5d Merge pull request #3306 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.22.1
Bump react-router-dom from 6.22.0 to 6.22.1
2024-02-20 23:51:32 -05:00
Trevor Buckner
1386020bbb Merge pull request #3273 from 5e-Cleric/brew-author-comma-fix
brew fix commas
2024-02-20 23:25:23 -05:00
Trevor Buckner
6b7af58e6c Change span to Fragment; linting 2024-02-20 23:24:43 -05:00
dependabot[bot]
0f1d07d90f Bump react-router-dom from 6.22.0 to 6.22.1
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.0 to 6.22.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.22.1/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 04:08:37 +00:00
Trevor Buckner
d5230757b1 Merge pull request #3305 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.1.3
Bump mongoose from 8.1.1 to 8.1.3
2024-02-20 23:07:27 -05:00
Trevor Buckner
06825468b4 Merge pull request #3299 from 5e-Cleric/update-popup
popup update 3.11
2024-02-20 23:07:05 -05:00
Trevor Buckner
faab60f271 Merge branch 'master' into update-popup 2024-02-20 23:06:33 -05:00
dependabot[bot]
4bed2349a9 Bump mongoose from 8.1.1 to 8.1.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.1.1 to 8.1.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.1.1...8.1.3)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 04:05:02 +00:00
Trevor Buckner
53f1e53fcb Merge pull request #3199 from naturalcrit/GlobalReflinks
Yet another Brew Variables PR
2024-02-20 23:03:53 -05:00
Trevor Buckner
df447d3d4d typo 2024-02-20 23:01:49 -05:00
Trevor Buckner
e3bf913a80 lint 2024-02-20 23:00:57 -05:00
Trevor Buckner
7bb1f16946 Always render currently-edited page first, so variables update across pages properly 2024-02-20 22:56:37 -05:00
Trevor Buckner
6870fd6d76 Add tests for cross-page behavior and fix related bugs 2024-02-20 22:31:33 -05:00
Trevor Buckner
9ad1d1f196 remove unused 'parseVarse' variable 2024-02-20 17:05:26 -05:00
Trevor Buckner
969cff61bf Add one more test that was failing 2024-02-20 17:03:17 -05:00
Trevor Buckner
736f729457 Rename variables 2024-02-20 16:46:49 -05:00
Trevor Buckner
0050e1e294 Added another test for a broken case and fixed; Cleaned up more 2024-02-20 15:37:54 -05:00
Trevor Buckner
f2d1b61a7a Remove unused variables 2024-02-20 00:04:29 -05:00
Trevor Buckner
1d1fa99b4b Pass all tests 2024-02-19 23:57:19 -05:00
Víctor Losada Hernández
f42cab6e40 suggested changes 2024-02-17 11:51:42 +01:00
Trevor Buckner
bc21abd509 Fix matches inside of code blocks 2024-02-14 11:38:47 -05:00
Trevor Buckner
72744718cc Adding some tests 2024-02-13 22:44:57 -05:00
Trevor Buckner
e85a62a05c Disable default def behavior. 2024-02-13 09:35:55 -05:00
Víctor Losada Hernández
5a68acc0f5 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into update-popup 2024-02-13 14:31:45 +01:00
Víctor Losada Hernández
f51fca74e6 initial commit 2024-02-13 14:27:32 +01:00
Víctor Losada Hernández
fe5a76c0df fix link href 2024-02-13 10:52:53 +01:00
Trevor Buckner
3bda834ad3 remove console.log 2024-02-13 00:53:34 -05:00
Trevor Buckner
29f0a8e635 Fix code blocks and nested parens. 2024-02-13 00:23:58 -05:00
Trevor Buckner
c035404555 Add exponents via '^' 2024-02-12 11:58:56 -05:00
Trevor Buckner
74ddc71962 Add 'floor()' and 'ceil()' math functions. 2024-02-12 11:57:37 -05:00
Trevor Buckner
1491a1b4ff Fix Math assignments 2024-02-12 11:54:28 -05:00
Trevor Buckner
ab54188ba4 Fix small typo 2024-02-09 02:15:53 -05:00
Trevor Buckner
e0b6b95295 Merge pull request #3290 from naturalcrit/PreprocessVars
Simplify and clean Vars - Done in preprocessing step now
2024-02-09 02:04:02 -05:00
Trevor Buckner
c7cfade86f Support () and round in math 2024-02-09 01:59:59 -05:00
Trevor Buckner
e4fa59aae8 Merge branch 'master' into PreprocessVars 2024-02-08 23:13:19 -05:00
Trevor Buckner
35227268cf Move to preprocessor step 2024-02-08 23:12:06 -05:00
Trevor Buckner
c27f5d9efa Merge pull request #3287 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recess-order-4.6.0
Bump stylelint-config-recess-order from 4.5.0 to 4.6.0
2024-02-08 10:53:40 -05:00
dependabot[bot]
87c9e587a1 Bump stylelint-config-recess-order from 4.5.0 to 4.6.0
Bumps [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recess-order
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-08 03:54:50 +00:00
Trevor Buckner
ed2d539995 Merge pull request #3282 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-8.7.0
Bump @googleapis/drive from 8.6.0 to 8.7.0
2024-02-07 07:45:44 -05:00
dependabot[bot]
65cc8567a1 Bump @googleapis/drive from 8.6.0 to 8.7.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 8.6.0 to 8.7.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/main/release-please-config.json)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/drive-v8.6.0...drive-v8.7.0)

---
updated-dependencies:
- dependency-name: "@googleapis/drive"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-07 04:12:33 +00:00
Trevor Buckner
dd82f54549 Merge pull request #3283 from naturalcrit/dependabot/npm_and_yarn/marked-gfm-heading-id-3.1.3
Bump marked-gfm-heading-id from 3.1.2 to 3.1.3
2024-02-06 23:11:44 -05:00
dependabot[bot]
a014056440 Bump marked-gfm-heading-id from 3.1.2 to 3.1.3
Bumps [marked-gfm-heading-id](https://github.com/markedjs/marked-gfm-heading-id) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/markedjs/marked-gfm-heading-id/releases)
- [Changelog](https://github.com/markedjs/marked-gfm-heading-id/blob/main/release.config.cjs)
- [Commits](https://github.com/markedjs/marked-gfm-heading-id/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: marked-gfm-heading-id
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-07 03:58:59 +00:00
Trevor Buckner
c176c38f30 Merge pull request #3280 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.22.0
Bump react-router-dom from 6.21.3 to 6.22.0
2024-02-06 22:58:10 -05:00
dependabot[bot]
4c87aed628 Bump react-router-dom from 6.21.3 to 6.22.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.21.3 to 6.22.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-07 03:51:27 +00:00
Trevor Buckner
09f2f96dff Merge pull request #3285 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recess-order-4.5.0
Bump stylelint-config-recess-order from 4.4.0 to 4.5.0
2024-02-06 22:50:26 -05:00
dependabot[bot]
f075b19a68 Bump stylelint-config-recess-order from 4.4.0 to 4.5.0
Bumps [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v4.4.0...v4.5.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recess-order
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-07 03:42:21 +00:00
Trevor Buckner
7177548c0e Merge pull request #3276 from naturalcrit/dependabot/npm_and_yarn/marked-11.2.0
Bump marked from 11.1.1 to 11.2.0
2024-01-30 14:29:00 -05:00
dependabot[bot]
3bc2df0ac5 Bump marked from 11.1.1 to 11.2.0
Bumps [marked](https://github.com/markedjs/marked) from 11.1.1 to 11.2.0.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v11.1.1...v11.2.0)

---
updated-dependencies:
- dependency-name: marked
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 03:26:17 +00:00
Víctor Losada Hernández
77abab8395 initial commit 2024-01-27 16:58:28 +01:00
Trevor Buckner
84de560083 Initial commit 2024-01-22 16:00:21 -05:00
Trevor Buckner
9e4344de83 Merge branch 'master' into GlobalReflinks 2024-01-19 00:13:02 -05:00
Trevor Buckner
760269a6e1 Fix math and inline defs getting lowercased 2024-01-19 00:11:52 -05:00
Víctor Losada Hernández
79e8dfec18 Merge branch 'master' into GlobalReflinks 2024-01-18 16:55:21 +01:00
Trevor Buckner
0ac88bd84a Fix $[var]:() - Must have at least one char inside ( ) 2024-01-09 22:29:30 -05:00
Trevor Buckner
90977521df Merge branch 'master' into GlobalReflinks 2024-01-09 13:53:09 -05:00
Trevor Buckner
09a52bc7cb Merge pull request #3232 from naturalcrit/CleanupVarsCode
Added hoisting, math, fixed several bugs
2024-01-09 13:51:03 -05:00
Trevor Buckner
6ef80eed7f All features working...? 2024-01-09 13:28:35 -05:00
Trevor Buckner
4dd58aaad3 Ugly ugly initial commit 2023-12-21 17:13:13 -05:00
Trevor Buckner
3a4de13551 split renderer into steps 2023-12-19 15:57:32 -05:00
11 changed files with 955 additions and 75 deletions

View File

@@ -75,11 +75,188 @@ pre {
.page {
padding-bottom: 1.5cm;
}
.varSyntaxTable th:first-of-type {
width:6cm;
}
```
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Wednesday 21/2/2024 - v3.11.0
{{taskList
##### Gazook89
* [x] Brew view count no longer increases when viewed by owner
Fixes issue [#3037](https://github.com/naturalcrit/homebrewery/issues/3037)
* [x] Small tweak to PHB H3 sizing
Fixes issue [#2989](https://github.com/naturalcrit/homebrewery/issues/2989)
* [x] Add **Fold/Unfold All** {{fas,fa-compress-alt}} / {{fas,fa-expand-alt}} buttons to editor bar
Fixes issue [#2965](https://github.com/naturalcrit/homebrewery/issues/2965)
##### G-Ambatte
* [x] Share link added to Editor Access error page
Fixes issue [#3086](https://github.com/naturalcrit/homebrewery/issues/3086)
* [x] Add Darkbrewery theme to Editor theme selector {{fas,fa-palette}}
Fixes issue [#3034](https://github.com/naturalcrit/homebrewery/issues/3034)
* [x] Fix Firefox prints with alternating blank pages
Fixes issue [#3115](https://github.com/naturalcrit/homebrewery/issues/3115)
* [x] Admin page working again
Fixes issue [#2657](https://github.com/naturalcrit/homebrewery/issues/2657)
##### 5e-Cleric
* [x] Fix indenting issue with Monster Blocks and italics in Class Feature
Fixes issues [#527](https://github.com/naturalcrit/homebrewery/issues/527),
[#3247](https://github.com/naturalcrit/homebrewery/issues/3247)
* [x] Allow CSS vars in curly syntax to be formatted as strings using single quotes
`{{--customVar:"'a string'"}}`
Fixes issue [#3066](https://github.com/naturalcrit/homebrewery/issues/3066)
* [x] Add *Elderberry Inn* icons {{ei,action}} `{{ei,icon-name}}`
Fixes issue [#3171](https://github.com/naturalcrit/homebrewery/issues/3171)
* [x] New {{openSans **{{fas,fa-keyboard}} FONTS** }} snippets!
Fixes issue [#3171](https://github.com/naturalcrit/homebrewery/issues/3171)
* [x] New page now opens in a new tab
##### abquintic (new contributor!)
* [x] Add ^super^ `^abc^` and ^^sub^^ `^^abc^^` syntax.
Fixes issue [#2171](https://github.com/naturalcrit/homebrewery/issues/2171)
* [x] Add HTML tag assignment to curly syntax `{{tag=value}}`
Fixes issue [1488](https://github.com/naturalcrit/homebrewery/issues/1488)
* [x] {{openSans **Brew → Clone to New**}} now clones tags
Fixes issue [1488](https://github.com/naturalcrit/homebrewery/issues/1488)
##### calculuschild
* [x] Better error messages for "Out of Google Drive Storage" and "Not logged in to edit"
Fixes issues [2510](https://github.com/naturalcrit/homebrewery/issues/2510),
[2975](https://github.com/naturalcrit/homebrewery/issues/2975)
* [x] New Variables syntax. See below for details.
}}
{{wide
### Brew Variable Syntax
You may already be familiar with `[link](url)` and `![image](url)` syntax. We have expanded this to include a third `$[variable](text)` syntax. All three of these syntaxes now share a common set of features:
{{varSyntaxTable
| syntax | description |
|:-------|-------------|
| `[var]:content` | Assigns a variable (must start on a line by itself, and ends at the next blank line) |
| `[var](content)` | Assigns a variable and outputs it (can be inline) |
| `[var]` | Outputs the variable contents as a link, if formatted as a valid link |
| `![var]` | Outputs as an image, if formatted as a valid image |
| `$[var]` | Outputs as Markdown |
| `$[var1 + var2 - 2 * var3]` | Performs math operations and outputs result if all variables are valid numbers |
}}
}}
{{wide,margin-top:0,margin-bottom:0
### Examples
}}
{{wide,columns:2,margin-top:0,margin-bottom:0
```
[first]: Bob
[last]: Jones
My name is $[first] $[last].
```
\column
[first]: Bob
[last]: Jones
My name is $[first] $[last].
}}
{{wide,columns:2,margin-top:0,margin-bottom:0
```
[myTable]:
| h1 | h2 |
|----|----|
| c1 | c2 |
Here is my table:
$[myTable]
```
\column
[myTable]:
| h1 | h2 |
|----|----|
| c1 | c2 |
Here is my table:
$[myTable]
}}
{{wide,columns:2,margin-top:0,margin-bottom:0
```
There are $[TableNum] tables total.
#### Table $[TableNum](1): Horses
#### Table $[TableNum]($[TableNum + 1]): Cows
```
\column
There are $[TableNum] tables in this document. *(note: final value of `$[TableNum]` gets hoisted up if available)*
#### Table $[TableNum](1): Horses
#### Table $[TableNum]($[TableNum + 1]): Cows
}}
\page
### Friday 13/10/2023 - v3.10.0
{{taskList

View File

@@ -89,15 +89,16 @@ const BrewRenderer = (props)=>{
}));
};
const shouldRender = (index)=>{
if(!state.isMounted) return false;
const isInView = (index)=>{
if(!state.isMounted)
return false;
if(index == props.currentEditorPage) //Already rendered before this step
return false;
if(Math.abs(index - state.viewablePageNumber) <= 3)
return true;
if(index + 1 == props.currentEditorPage)
return true;
return false;
};
@@ -138,7 +139,7 @@ const BrewRenderer = (props)=>{
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)
const html = Markdown.render(cleanPageText);
const html = Markdown.render(cleanPageText, index);
return <BrewPage className='page' index={index} key={index} contents={html} />;
}
};
@@ -150,8 +151,11 @@ const BrewRenderer = (props)=>{
if(rawPages.length != renderedPages.length) // Re-render all pages when page count changes
renderedPages.length = 0;
// Render currently-edited page first so cross-page effects (variables, links) can propagate out first
renderedPages[props.currentEditorPage] = renderPage(rawPages[props.currentEditorPage], props.currentEditorPage);
_.forEach(rawPages, (page, index)=>{
if((shouldRender(index) || !renderedPages[index]) && typeof window !== 'undefined'){
if((isInView(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
}
});

View File

@@ -25,13 +25,10 @@ const NotificationPopup = createClass({
return (
<>
<li key='psa'>
<em>Broken default logo on <b>CoverPage</b> </em> <br />
If you have used the Cover Page snippet and notice the Naturalcrit
logo is showing as a broken image, this is due to some small tweaks
of this BETA feature. To fix the logo in your cover page, rename
the image link <b>"/assets/naturalCritLogoRed.svg"</b>. Remember
that any snippet marked "BETA" may have a similar change in the
future as we encounter any bugs or reworks.
<em>Don't store IMAGES in Google Drive</em><br />
Google Drive is not an image service, and will block images from being used
in brews if they get more views than expected. Google has confirmed they won't fix
this, so we recommend you look for another image hosting service such as imgur, ImgBB or Google Photos.
</li>
<li key='googleDriveFolder'>

View File

@@ -135,7 +135,11 @@ const BrewItem = createClass({
</> : <></>
}
<span title={`Authors:\n${brew.authors?.join('\n')}`}>
<i className='fas fa-user'/> {brew.authors.map((item) => <a href={`/user/${item}`}>{item}</a>)}
<i className='fas fa-user'/> {brew.authors?.map((author, index)=>(
<>
<a key={index} href={`/user/${author}`}>{author}</a>
{index < brew.authors.length - 1 && ', '}
</>))}
</span>
<br />
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>

View File

@@ -113,7 +113,7 @@ const EditPage = createClass({
brew : { ...prevState.brew, text: text },
isPending : true,
htmlErrors : htmlErrors,
currentEditorPage : this.refs.editor.getCurrentPage()
currentEditorPage : this.refs.editor.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
}), ()=>{if(this.state.autoSave) this.trySave();});
},

View File

@@ -31,9 +31,10 @@ const HomePage = createClass({
},
getInitialState : function() {
return {
brew : this.props.brew,
welcomeText : this.props.brew.text,
error : undefined
brew : this.props.brew,
welcomeText : this.props.brew.text,
error : undefined,
currentEditorPage : 0
};
},
handleSave : function(){
@@ -53,7 +54,8 @@ const HomePage = createClass({
},
handleTextChange : function(text){
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text }
brew : { ...prevState.brew, text: text },
currentEditorPage : this.refs.editor.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
}));
},
renderNavbar : function(){
@@ -85,7 +87,12 @@ const HomePage = createClass({
renderer={this.state.brew.renderer}
showEditButtons={false}
/>
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer}/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
currentEditorPage={this.state.currentEditorPage}
/>
</SplitPane>
</div>

View File

@@ -38,11 +38,12 @@ const NewPage = createClass({
const brew = this.props.brew;
return {
brew : brew,
isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false),
error : null,
htmlErrors : Markdown.validate(brew.text)
brew : brew,
isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false),
error : null,
htmlErrors : Markdown.validate(brew.text),
currentEditorPage : 0
};
},
@@ -104,8 +105,9 @@ const NewPage = createClass({
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors
brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors,
currentEditorPage : this.refs.editor.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
}));
localStorage.setItem(BREWKEY, text);
},
@@ -220,7 +222,15 @@ const NewPage = createClass({
onMetaChange={this.handleMetaChange}
renderer={this.state.brew.renderer}
/>
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} lang={this.state.brew.lang} errors={this.state.htmlErrors}/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
currentEditorPage={this.state.currentEditorPage}
/>
</SplitPane>
</div>
</div>;

78
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "homebrewery",
"version": "3.10.0",
"version": "3.11.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "homebrewery",
"version": "3.10.0",
"version": "3.11.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -14,13 +14,14 @@
"@babel/plugin-transform-runtime": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.6.0",
"@googleapis/drive": "^8.7.0",
"body-parser": "^1.20.2",
"classnames": "^2.3.2",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.6",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"expr-eval": "^2.0.2",
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7",
@@ -29,19 +30,19 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "11.1.1",
"marked": "11.2.0",
"marked-extended-tables": "^1.0.8",
"marked-gfm-heading-id": "^3.1.2",
"marked-gfm-heading-id": "^3.1.3",
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.1.1",
"mongoose": "^8.1.3",
"nanoid": "3.3.4",
"nconf": "^0.12.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-frame-component": "^4.1.3",
"react-router-dom": "6.21.3",
"react-router-dom": "6.22.1",
"sanitize-filename": "1.6.3",
"superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -54,7 +55,7 @@
"jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0",
"stylelint": "^15.11.0",
"stylelint-config-recess-order": "^4.4.0",
"stylelint-config-recess-order": "^4.6.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.4"
@@ -1966,9 +1967,9 @@
}
},
"node_modules/@googleapis/drive": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.6.0.tgz",
"integrity": "sha512-Af3/5i6h7gbjHnwFuO9zMTpYOy2yhhfZlNciUEjb14L3ZdT1WNIDM038viIAb9ovFzkrIDqLSfUbFCgh1pywkw==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.7.0.tgz",
"integrity": "sha512-XAi6kfySIU4H3ivX2DpzTDce5UhNke5NxEWCL6tySEdcVqx+cmXJmkMqwfOAHJalEB5s9PPfdLBU29Xd5XlLSQ==",
"dependencies": {
"googleapis-common": "^7.0.0"
},
@@ -2837,9 +2838,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz",
"integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==",
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz",
"integrity": "sha512-zcU0gM3z+3iqj8UX45AmWY810l3oUmXM7uH4dt5xtzvMhRtYVhKGOmgOd1877dOPPepfCjUv57w+syamWIYe7w==",
"engines": {
"node": ">=14.0.0"
}
@@ -6036,6 +6037,11 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/expr-eval": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz",
"integrity": "sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg=="
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -10041,9 +10047,9 @@
}
},
"node_modules/marked": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz",
"integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==",
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz",
"integrity": "sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==",
"bin": {
"marked": "bin/marked.js"
},
@@ -10060,14 +10066,14 @@
}
},
"node_modules/marked-gfm-heading-id": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/marked-gfm-heading-id/-/marked-gfm-heading-id-3.1.2.tgz",
"integrity": "sha512-SdIZvhNxDgndFkDa2WRcFP4ahYm6k6hoHdTCN+fD7HRiI/R3Eimcw/Yl7ikQ+0KUuDpi75NnYQiThZnZsNr9Dg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/marked-gfm-heading-id/-/marked-gfm-heading-id-3.1.3.tgz",
"integrity": "sha512-A0cRU4PCueX/5m8VE4mT8uTQ36l3xMYRojz3Eqnk4BmUFZ0T+9Xhn2KvHcANP4qbhfOeuMrWJCTQbASIBR5xeg==",
"dependencies": {
"github-slugger": "^2.0.0"
},
"peerDependencies": {
"marked": ">=4 <12"
"marked": ">=4 <13"
}
},
"node_modules/marked-smartypants-lite": {
@@ -10453,9 +10459,9 @@
}
},
"node_modules/mongoose": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.1.tgz",
"integrity": "sha512-DbLb0NsiEXmaqLOpEz+AtAsgwhRw6f25gwa1dF5R7jj6lS1D8X6uTdhBSC8GDVtOwe5Tfw2EL7nTn6hiJT3Bgg==",
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.3.tgz",
"integrity": "sha512-a5MajZSDJiQgy0iQcR+MIpFe7zehGJI4doJ6Dh1MvnGh8/HNNhr5pn07RPA86KCTjP2vuKdffpFmvXxcHiUOjw==",
"dependencies": {
"bson": "^6.2.0",
"kareem": "2.5.1",
@@ -11873,11 +11879,11 @@
"dev": true
},
"node_modules/react-router": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz",
"integrity": "sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==",
"version": "6.22.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.1.tgz",
"integrity": "sha512-0pdoRGwLtemnJqn1K0XHUbnKiX0S4X8CgvVVmHGOWmofESj31msHo/1YiqcJWK7Wxfq2a4uvvtS01KAQyWK/CQ==",
"dependencies": {
"@remix-run/router": "1.14.2"
"@remix-run/router": "1.15.1"
},
"engines": {
"node": ">=14.0.0"
@@ -11887,12 +11893,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.3.tgz",
"integrity": "sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==",
"version": "6.22.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.1.tgz",
"integrity": "sha512-iwMyyyrbL7zkKY7MRjOVRy+TMnS/OPusaFVxM2P11x9dzSzGmLsebkCvYirGq0DWB9K9hOspHYYtDz33gE5Duw==",
"dependencies": {
"@remix-run/router": "1.14.2",
"react-router": "6.21.3"
"@remix-run/router": "1.15.1",
"react-router": "6.22.1"
},
"engines": {
"node": ">=14.0.0"
@@ -13327,9 +13333,9 @@
}
},
"node_modules/stylelint-config-recess-order": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-4.4.0.tgz",
"integrity": "sha512-Q99kvZyIM/aoPEV4dRDkzD3fZLzH0LXi+pawCf1r700uUeF/PHQ5PZXjwFUuGrWhOzd1N+cuVm+OUGsY2fRN5A==",
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-4.6.0.tgz",
"integrity": "sha512-V76fhv3YtcNXh/hyAuAdSzi5FmcrG54Mp2AThJ3D/PTMTSYzUPd7GIhP6z9mTqnRhmkk6YTfcu/JWB8h+Yrcaw==",
"dev": true,
"dependencies": {
"stylelint-order": "6.x"

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.10.0",
"version": "3.11.0",
"engines": {
"npm": "^10.2.x",
"node": "^20.8.x"
@@ -26,6 +26,7 @@
"test:coverage": "jest --coverage --silent --runInBand",
"test:dev": "jest --verbose --watch",
"test:basic": "jest tests/markdown/basic.test.js --verbose",
"test:variables": "jest tests/markdown/variables.test.js --verbose",
"test:mustache-syntax": "jest '.*(mustache-syntax).*' --verbose --noStackTrace",
"test:mustache-syntax:inline": "jest '.*(mustache-syntax).*' -t '^Inline:.*' --verbose --noStackTrace",
"test:mustache-syntax:block": "jest '.*(mustache-syntax).*' -t '^Block:.*' --verbose --noStackTrace",
@@ -83,13 +84,14 @@
"@babel/plugin-transform-runtime": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.6.0",
"@googleapis/drive": "^8.7.0",
"body-parser": "^1.20.2",
"classnames": "^2.3.2",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.6",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"expr-eval": "^2.0.2",
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7",
@@ -98,19 +100,19 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "11.1.1",
"marked": "11.2.0",
"marked-extended-tables": "^1.0.8",
"marked-gfm-heading-id": "^3.1.2",
"marked-gfm-heading-id": "^3.1.3",
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.1.1",
"mongoose": "^8.1.3",
"nanoid": "3.3.4",
"nconf": "^0.12.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-frame-component": "^4.1.3",
"react-router-dom": "6.21.3",
"react-router-dom": "6.22.1",
"sanitize-filename": "1.6.3",
"superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -123,7 +125,7 @@
"jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0",
"stylelint": "^15.11.0",
"stylelint-config-recess-order": "^4.4.0",
"stylelint-config-recess-order": "^4.6.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.4"

View File

@@ -4,7 +4,40 @@ const Marked = require('marked');
const MarkedExtendedTables = require('marked-extended-tables');
const { markedSmartypantsLite: MarkedSmartypantsLite } = require('marked-smartypants-lite');
const { gfmHeadingId: MarkedGFMHeadingId } = require('marked-gfm-heading-id');
const MathParser = require('expr-eval').Parser;
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,
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, abs : false, trunc : false, join : false, sum : 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, indexOf : false,
remainder : false, factorial : false,
comparison : false, concatenate : false,
logical : false, assignment : false,
array : false, fndef : false
}
});
//Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (html) {
@@ -50,6 +83,11 @@ renderer.link = function (href, title, text) {
return out;
};
// Disable default reflink behavior, as it steps on our variables extension
tokenizer.def = function () {
return undefined;
};
const mustacheSpans = {
name : 'mustacheSpans',
level : 'inline', // Is this a block-level or inline-level tokenizer?
@@ -288,9 +326,255 @@ const definitionLists = {
}
};
//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 = match[2];
//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(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(variable, foundVar.content);
});
try {
return mathParser.evaluate(replacedLabel);
} catch (error) {
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] ? match[4].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
varsQueue.push(
{ type : 'varDefBlock',
varName : label,
content : content
});
}
if(match[6]) { // Block Call
const label = match[7] ? match[7].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
varsQueue.push(
{ type : 'varCallBlock',
varName : label,
content : match[0]
});
}
if(match[8]) { // Inline Definition
const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
let content = match[11] ? match[11].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
// 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;
}
}
if(i > -1) {
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] ? match[13].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
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 >-------------------=====^//
Marked.use(MarkedVariables());
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, mangle: false });
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
const nonWordAndColonTest = /[^\w:]/g;
@@ -369,12 +653,28 @@ const processStyleTags = (string)=>{
`${attributes?.length ? ` ${attributes.join(' ')}` : ''}`;
};
const globalVarsList = {};
let varsQueue = [];
let globalPageNumber = 0;
module.exports = {
marked : Marked,
render : (rawBrewText)=>{
render : (rawBrewText, pageNumber=1)=>{
globalVarsList[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order
varsQueue = []; //Could move into MarkedVariables()
globalPageNumber = pageNumber;
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`)
.replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`);
return Marked.parse(rawBrewText);
const opts = Marked.defaults;
rawBrewText = opts.hooks.preprocess(rawBrewText);
const tokens = Marked.lexer(rawBrewText, opts);
Marked.walkTokens(tokens, opts.walkTokens);
const html = Marked.parser(tokens, opts);
return opts.hooks.postprocess(html);
},
validate : (rawBrewText)=>{

View File

@@ -0,0 +1,373 @@
/* eslint-disable max-lines */
const dedent = require('dedent-tabs').default;
const Markdown = require('naturalcrit/markdown.js');
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.
String.prototype.trimReturns = function(){
return this.replace(/\r?\n|\r/g, '').trim();
};
renderAllPages = function(pages){
const outputs = [];
pages.forEach((page, index)=>{
const output = Markdown.render(page, index);
outputs.push(output);
});
return outputs;
};
// Adding `.failing()` method to `describe` or `it` will make failing tests "pass" as long as they continue to fail.
// Remove the `.failing()` method once you have fixed the issue.
describe('Block-level variables', ()=>{
it('Handles variable assignment and recall with simple text', function() {
const source = dedent`
[var]: string
$[var]
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
});
it('Handles variable assignment and recall with multiline string', function() {
const source = dedent`
[var]: string
across multiple
lines
$[var]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string across multiple lines</p>');
});
it('Handles variable assignment and recall with tables', function() {
const source = dedent`
[var]:
##### Title
| H1 | H2 |
|:---|:--:|
| A | B |
| C | D |
$[var]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<h5 id="title">Title</h5>
<table><thead><tr><th align=left>H1</th>
<th align=center>H2</th>
</tr></thead><tbody><tr><td align=left>A</td>
<td align=center>B</td>
</tr><tr><td align=left>C</td>
<td align=center>D</td>
</tr></tbody></table>`.trimReturns());
});
it('Hoists undefined variables', function() {
const source = dedent`
$[var]
[var]: string`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
});
it('Hoists last instance of variable', function() {
const source = dedent`
$[var]
[var]: string
[var]: new string`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>new string</p>');
});
it('Handles complex hoisting', function() {
const source = dedent`
$[titleAndName]: $[title] $[fullName]
$[title]: Mr.
$[fullName]: $[firstName] $[lastName]
[firstName]: Bob
Welcome, $[titleAndName]!
[lastName]: Jacob
[lastName]: $[lastName]son
`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Welcome, Mr. Bob Jacobson!</p>');
});
it('Handles variable reassignment', function() {
const source = dedent`
[var]: one
$[var]
[var]: two
$[var]
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>one</p><p>two</p>'.trimReturns());
});
it('Handles variable reassignment with hoisting', function() {
const source = dedent`
$[var]
[var]: one
$[var]
[var]: two
$[var]
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>two</p><p>one</p><p>two</p>'.trimReturns());
});
it('Ignores undefined variables that can\'t be hoisted', function() {
const source = dedent`
$[var](My name is $[first] $[last])
$[last]: Jones
`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is $[first] Jones</p>`.trimReturns());
});
});
describe('Inline-level variables', ()=>{
it('Handles variable assignment and recall with simple text', function() {
const source = dedent`
$[var](string)
$[var]
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p><p>string</p>');
});
it('Hoists undefined variables when possible', function() {
const source = dedent`
$[var](My name is $[name] Jones)
[name]: Bob`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>My name is Bob Jones</p>');
});
it('Hoists last instance of variable', function() {
const source = dedent`
$[var](My name is $[name] Jones)
$[name](Bob)
[name]: Bill`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is Bill Jones</p> <p>Bob</p>`.trimReturns());
});
it('Only captures nested parens if balanced', function() {
const source = dedent`
$[var1](A variable (with nested parens) inside)
$[var1]
$[var2](A variable ) with unbalanced parens)
$[var2]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>A variable (with nested parens) inside</p>
<p>A variable (with nested parens) inside</p>
<p>A variable with unbalanced parens)</p>
<p>A variable</p>
`.trimReturns());
});
});
describe('Math', ()=>{
it('Handles simple math using numbers only', function() {
const source = dedent`
$[1 + 3 * 5 - (1 / 4)]
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>15.75</p>');
});
it('Handles round function', function() {
const source = dedent`
$[round(1/4)]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
});
it('Handles floor function', function() {
const source = dedent`
$[floor(0.6)]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
});
it('Handles ceil function', function() {
const source = dedent`
$[ceil(0.2)]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
});
it('Handles nested functions', function() {
const source = dedent`
$[ceil(floor(round(0.6)))]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
});
it('Handles simple math with variables', function() {
const source = dedent`
$[num1]: 5
$[num2]: 4
Answer is $[answer]($[1 + 3 * num1 - (1 / num2)]).
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Answer is 15.75.</p>');
});
it('Handles variable incrementing', function() {
const source = dedent`
$[num1]: 5
Increment num1 to get $[num1]($[num1 + 1]) and again to $[num1]($[num1 + 1]).
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Increment num1 to get 6 and again to 7.</p>');
});
});
describe('Code blocks', ()=>{
it('Ignores all variables in fenced code blocks', function() {
const source = dedent`
\`\`\`
[var]: string
$[var]
$[var](new string)
\`\`\`
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<pre><code>
[var]: string
$[var]
$[var](new string)
</code></pre>`.trimReturns());
});
it('Ignores all variables in indented code blocks', function() {
const source = dedent`
test
[var]: string
$[var]
$[var](new string)
`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>test</p>
<pre><code>
[var]: string
$[var]
$[var](new string)
</code></pre>`.trimReturns());
});
it('Ignores all variables in inline code blocks', function() {
const source = '[var](Hello) `[link](url)`. This `[var] does not work`';
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p><a href="Hello">var</a> <code>[link](url)</code>. This <code>[var] does not work</code></p>`.trimReturns());
});
});
describe('Normal Links and Images', ()=>{
it('Renders normal images', function() {
const source = `![alt text](url)`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p><img src="url" alt="alt text"></p>`.trimReturns());
});
it('Renders normal images with a title', function() {
const source = 'An image ![alt text](url "and title")!';
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>An image <img src="url" alt="alt text" title="and title">!</p>`.trimReturns());
});
it('Applies curly injectors to images', function() {
const source = `![alt text](url){width:100px}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p><img class="" style="width:100px;" src="url" alt="alt text"></p>`.trimReturns());
});
it('Renders normal links', function() {
const source = 'A Link to my [website](url)!';
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>A Link to my <a href="url">website</a>!</p>`.trimReturns());
});
it('Renders normal links with a title', function() {
const source = 'A Link to my [website](url "and title")!';
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>A Link to my <a href="url" title="and title">website</a>!</p>`.trimReturns());
});
});
describe('Cross-page variables', ()=>{
it('Handles variable assignment and recall across pages', function() {
const source0 = `[var]: string`;
const source1 = `$[var]`;
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('\\page<p>string</p>');
});
it('Handles hoisting across pages', function() {
const source0 = `$[var]`;
const source1 = `[var]: string`;
renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('<p>string</p>\\page');
});
it('Handles reassignment and hoisting across pages', function() {
const source0 = `$[var]\n\n[var]: one\n\n$[var]`;
const source1 = `[var]: two\n\n$[var]`;
renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('<p>two</p><p>one</p>\\page<p>two</p>');
});
});