mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-03 06:12:51 +00:00
Merge branch 'master' into migration-guide
# Conflicts: # shared/naturalcrit/markdown.js
This commit is contained in:
33
changelog.md
33
changelog.md
@@ -3,11 +3,12 @@ h5 {
|
|||||||
font-size: .35cm !important;
|
font-size: .35cm !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.taskList li {
|
.page ul ul {
|
||||||
list-style-type : none;
|
margin-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.taskList li input {
|
.taskList li input {
|
||||||
|
list-style-type : none;
|
||||||
margin-left : -0.52cm;
|
margin-left : -0.52cm;
|
||||||
transform: translateY(.05cm);
|
transform: translateY(.05cm);
|
||||||
filter: brightness(1.1) drop-shadow(1px 2px 1px #222);
|
filter: brightness(1.1) drop-shadow(1px 2px 1px #222);
|
||||||
@@ -33,6 +34,34 @@ 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).
|
||||||
|
|
||||||
|
### Tuesday 07/12/2021 - v3.0.5
|
||||||
|
{{taskList
|
||||||
|
* [x] Fixed paragraph spacing for **note** and **descriptive** boxes in V3.
|
||||||
|
|
||||||
|
Fixes issues: [#1836](https://github.com/naturalcrit/homebrewery/issues/1836)
|
||||||
|
|
||||||
|
* [x] Added a whole bunch of hotkeys:
|
||||||
|
|
||||||
|
* Page Break `CTRL + ENTER`
|
||||||
|
* Column Break `CTRL + SHIFT + ENTER`
|
||||||
|
* Bulleted Lists `CTRL + L`
|
||||||
|
* Numbered Lists `CTRL + SHIFT + L`
|
||||||
|
* Headers `CTRL + SHIFT + (1-6)`
|
||||||
|
* Underline `CTRL + U`
|
||||||
|
* Link `CTRL + K`
|
||||||
|
* Non-breaking space (\ ) `CTRL + .`
|
||||||
|
* Add Horizontal Space `CTRL + SHIFT + .`
|
||||||
|
* Remove Horizontal Space `CTRL + SHIFT + ,`
|
||||||
|
* Curly Span `CTRL + M`
|
||||||
|
* Curly Div `CTRL + SHIFT + M`
|
||||||
|
|
||||||
|
* [x] Fixed page numbers in the editor panel getting scrambled when scrolling up and down.
|
||||||
|
|
||||||
|
* [x] Faster swapping between tabs on long brews.
|
||||||
|
|
||||||
|
* [x] Better error messages for common issue with Google Drive credentials expiring.
|
||||||
|
}}
|
||||||
|
|
||||||
### Wednesday 17/11/2021 - v3.0.4
|
### Wednesday 17/11/2021 - v3.0.4
|
||||||
{{taskList
|
{{taskList
|
||||||
* [x] Fixed incorrect sorting of Google brews by page count and views on the user page.
|
* [x] Fixed incorrect sorting of Google brews by page count and views on the user page.
|
||||||
|
|||||||
@@ -107,67 +107,69 @@ const Editor = createClass({
|
|||||||
if(this.state.view === 'text') {
|
if(this.state.view === 'text') {
|
||||||
const codeMirror = this.refs.codeEditor.codeMirror;
|
const codeMirror = this.refs.codeEditor.codeMirror;
|
||||||
|
|
||||||
//reset custom text styles
|
codeMirror.operation(()=>{ // Batch CodeMirror styling
|
||||||
const customHighlights = codeMirror.getAllMarks().filter((mark)=>!mark.__isFold); //Don't undo code folding
|
//reset custom text styles
|
||||||
for (let i=0;i<customHighlights.length;i++) customHighlights[i].clear();
|
const customHighlights = codeMirror.getAllMarks().filter((mark)=>!mark.__isFold); //Don't undo code folding
|
||||||
|
for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear();
|
||||||
|
|
||||||
const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{
|
let editorPageCount = 2; // start page count from page 2
|
||||||
|
|
||||||
//reset custom line styles
|
_.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{
|
||||||
codeMirror.removeLineClass(lineNumber, 'background');
|
|
||||||
codeMirror.removeLineClass(lineNumber, 'text');
|
|
||||||
|
|
||||||
// Legacy Codemirror styling
|
//reset custom line styles
|
||||||
if(this.props.renderer == 'legacy') {
|
codeMirror.removeLineClass(lineNumber, 'background', 'pageLine');
|
||||||
if(line.includes('\\page')){
|
codeMirror.removeLineClass(lineNumber, 'text');
|
||||||
|
|
||||||
|
// Styling for \page breaks
|
||||||
|
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
|
||||||
|
(this.props.renderer == 'V3' && line.match(/^\\page$/))) {
|
||||||
|
|
||||||
|
// add back the original class 'background' but also add the new class '.pageline'
|
||||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
r.push(lineNumber);
|
const pageCountElement = Object.assign(document.createElement('span'), {
|
||||||
}
|
className : 'editor-page-count',
|
||||||
}
|
textContent : editorPageCount
|
||||||
|
});
|
||||||
|
codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement);
|
||||||
|
|
||||||
// New Codemirror styling for V3 renderer
|
editorPageCount += 1;
|
||||||
if(this.props.renderer == 'V3') {
|
};
|
||||||
if(line.match(/^\\page$/)){
|
|
||||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
|
||||||
r.push(lineNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(line.match(/^\\column$/)){
|
// New Codemirror styling for V3 renderer
|
||||||
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
if(this.props.renderer == 'V3') {
|
||||||
r.push(lineNumber);
|
if(line.match(/^\\column$/)){
|
||||||
}
|
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||||
|
|
||||||
// Highlight inline spans {{content}}
|
|
||||||
if(line.includes('{{') && line.includes('}}')){
|
|
||||||
const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
|
|
||||||
let match;
|
|
||||||
let blockCount = 0;
|
|
||||||
while ((match = regex.exec(line)) != null) {
|
|
||||||
if(match[0].startsWith('{')) {
|
|
||||||
blockCount += 1;
|
|
||||||
} else {
|
|
||||||
blockCount -= 1;
|
|
||||||
}
|
|
||||||
if(blockCount < 0) {
|
|
||||||
blockCount = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
|
|
||||||
}
|
}
|
||||||
} else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){
|
|
||||||
// Highlight block divs {{\n Content \n}}
|
|
||||||
let endCh = line.length+1;
|
|
||||||
|
|
||||||
const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/);
|
// Highlight inline spans {{content}}
|
||||||
if(match)
|
if(line.includes('{{') && line.includes('}}')){
|
||||||
endCh = match.index+match[0].length;
|
const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
|
||||||
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
let match;
|
||||||
|
let blockCount = 0;
|
||||||
|
while ((match = regex.exec(line)) != null) {
|
||||||
|
if(match[0].startsWith('{')) {
|
||||||
|
blockCount += 1;
|
||||||
|
} else {
|
||||||
|
blockCount -= 1;
|
||||||
|
}
|
||||||
|
if(blockCount < 0) {
|
||||||
|
blockCount = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
|
||||||
|
}
|
||||||
|
} else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){
|
||||||
|
// Highlight block divs {{\n Content \n}}
|
||||||
|
let endCh = line.length+1;
|
||||||
|
|
||||||
|
const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/);
|
||||||
|
if(match)
|
||||||
|
endCh = match.index+match[0].length;
|
||||||
|
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
});
|
||||||
return r;
|
|
||||||
}, []);
|
|
||||||
return lineNumbers;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,13 @@
|
|||||||
|
|
||||||
.codeEditor{
|
.codeEditor{
|
||||||
height : 100%;
|
height : 100%;
|
||||||
counter-reset : page;
|
|
||||||
counter-increment : page;
|
|
||||||
.pageLine{
|
.pageLine{
|
||||||
background : #33333328;
|
background : #33333328;
|
||||||
border-top : #339 solid 1px;
|
border-top : #339 solid 1px;
|
||||||
&:after{
|
}
|
||||||
content : counter(page);
|
.editor-page-count{
|
||||||
counter-increment : page;
|
color : grey;
|
||||||
float : right;
|
float : right;
|
||||||
color : gray;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.columnSplit{
|
.columnSplit{
|
||||||
font-style : italic;
|
font-style : italic;
|
||||||
|
|||||||
@@ -100,25 +100,25 @@ const subtitles = [
|
|||||||
|
|
||||||
module.exports = ()=>{
|
module.exports = ()=>{
|
||||||
return `<style>
|
return `<style>
|
||||||
.phb#p1{ text-align:center; }
|
.page#p1{ text-align:center; counter-increment: none; }
|
||||||
.phb#p1:after{ display:none; }
|
.page#p1:after{ display:none; }
|
||||||
.phb#p2 { counter-reset:phb-page-numbers; }
|
.page:nth-child(2n) .pageNumber { left: inherit !important; right: 2px !important; }
|
||||||
.phb:nth-child(2n) .pageNumber { left: inherit !important; right: 2px !important; }
|
.page:nth-child(2n+1) .pageNumber { right: inherit !important; left: 2px !important; }
|
||||||
.phb:nth-child(2n+1) .pageNumber { right: inherit !important; left: 2px !important; }
|
.page:nth-child(2n)::after { transform: scaleX(1); }
|
||||||
.phb:nth-child(2n)::after { transform: scaleX(1); }
|
.page:nth-child(2n+1)::after { transform: scaleX(-1); }
|
||||||
.phb:nth-child(2n+1)::after { transform: scaleX(-1); }
|
.page:nth-child(2n) .footnote { left: inherit; text-align: right; }
|
||||||
.phb:nth-child(2n) .footnote { left: inherit; text-align: right; }
|
.page:nth-child(2n+1) .footnote { left: 80px; text-align: left; }
|
||||||
.phb:nth-child(2n+1) .footnote { left: 80px; text-align: left; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div style='margin-top:450px;'></div>
|
{{margin-top:225px}}
|
||||||
|
|
||||||
# ${_.sample(titles)}
|
# ${_.sample(titles)}
|
||||||
|
|
||||||
<div style='margin-top:25px'></div>
|
{{margin-top:25px}}
|
||||||
<div class='wide'>
|
|
||||||
|
{{wide
|
||||||
##### ${_.sample(subtitles)}
|
##### ${_.sample(subtitles)}
|
||||||
</div>
|
}}
|
||||||
|
|
||||||
\\page`;
|
\\page`;
|
||||||
};
|
};
|
||||||
@@ -350,14 +350,14 @@ const EditPage = createClass({
|
|||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){
|
if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
Oops!
|
||||||
<div className='errorContainer' onClick={this.clearErrors}>
|
<div className='errorContainer' onClick={this.clearErrors}>
|
||||||
Looks like your Google credentials have
|
Looks like your Google credentials have
|
||||||
expired! Visit the log in page to sign out
|
expired! Visit our log in page to sign out
|
||||||
and sign back in with Google
|
and sign back in with Google,
|
||||||
to save this to Google Drive!
|
then try saving again!
|
||||||
<a target='_blank' rel='noopener noreferrer'
|
<a target='_blank' rel='noopener noreferrer'
|
||||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||||
<div className='confirm'>
|
<div className='confirm'>
|
||||||
|
|||||||
@@ -227,14 +227,14 @@ const NewPage = createClass({
|
|||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){
|
if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
Oops!
|
||||||
<div className='errorContainer' onClick={this.clearErrors}>
|
<div className='errorContainer' onClick={this.clearErrors}>
|
||||||
Looks like your Google credentials have
|
Looks like your Google credentials have
|
||||||
expired! Visit the log in page to sign out
|
expired! Visit our log in page to sign out
|
||||||
and sign back in with Google
|
and sign back in with Google,
|
||||||
to save this to Google Drive!
|
then try saving again!
|
||||||
<a target='_blank' rel='noopener noreferrer'
|
<a target='_blank' rel='noopener noreferrer'
|
||||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||||
<div className='confirm'>
|
<div className='confirm'>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const SharePage = createClass({
|
|||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
const P_KEY = 80;
|
const P_KEY = 80;
|
||||||
if(e.keyCode == P_KEY){
|
if(e.keyCode == P_KEY){
|
||||||
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const BrewItem = createClass({
|
|||||||
renderDeleteBrewLink : function(){
|
renderDeleteBrewLink : function(){
|
||||||
if(!this.props.brew.editId) return;
|
if(!this.props.brew.editId) return;
|
||||||
|
|
||||||
return <a onClick={this.deleteBrew}>
|
return <a className='deleteLink' onClick={this.deleteBrew}>
|
||||||
<i className='fas fa-trash-alt' title='Delete' />
|
<i className='fas fa-trash-alt' title='Delete' />
|
||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
@@ -61,7 +61,7 @@ const BrewItem = createClass({
|
|||||||
editLink = this.props.brew.googleId + editLink;
|
editLink = this.props.brew.googleId + editLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <a href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
|
return <a className='editLink' href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
|
||||||
<i className='fas fa-pencil-alt' title='Edit' />
|
<i className='fas fa-pencil-alt' title='Edit' />
|
||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
@@ -74,7 +74,7 @@ const BrewItem = createClass({
|
|||||||
shareLink = this.props.brew.googleId + shareLink;
|
shareLink = this.props.brew.googleId + shareLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <a href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
|
return <a className='shareLink' href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
|
||||||
<i className='fas fa-share-alt' title='Share' />
|
<i className='fas fa-share-alt' title='Share' />
|
||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
@@ -87,7 +87,7 @@ const BrewItem = createClass({
|
|||||||
shareLink = this.props.brew.googleId + shareLink;
|
shareLink = this.props.brew.googleId + shareLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <a href={`/download/${shareLink}`}>
|
return <a className='downloadLink' href={`/download/${shareLink}`}>
|
||||||
<i className='fas fa-download' title='Download' />
|
<i className='fas fa-download' title='Download' />
|
||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const UserPage = createClass({
|
|||||||
<button
|
<button
|
||||||
value={`${sortValue}`}
|
value={`${sortValue}`}
|
||||||
onClick={this.handleSortOptionChange}
|
onClick={this.handleSortOptionChange}
|
||||||
className={`${(this.state.sortType == sortValue ? 'active' : '')}`}
|
className={`sortOption ${(this.state.sortType == sortValue ? 'active' : '')}`}
|
||||||
>
|
>
|
||||||
{`${sortTitle}`}
|
{`${sortTitle}`}
|
||||||
</button>
|
</button>
|
||||||
@@ -102,7 +102,7 @@ const UserPage = createClass({
|
|||||||
|
|
||||||
renderFilterOption : function(){
|
renderFilterOption : function(){
|
||||||
return <td>
|
return <td>
|
||||||
<label>
|
<label className='filterOption'>
|
||||||
<i className='fas fa-search'></i>
|
<i className='fas fa-search'></i>
|
||||||
<input
|
<input
|
||||||
type='search'
|
type='search'
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
"host" : "homebrewery.local.naturalcrit.com:8000",
|
"host" : "homebrewery.local.naturalcrit.com:8000",
|
||||||
"naturalcrit_url" : "local.naturalcrit.com:8010",
|
"naturalcrit_url" : "local.naturalcrit.com:8010",
|
||||||
"secret" : "secret",
|
"secret" : "secret",
|
||||||
"web_port" : 8000
|
"web_port" : 8000,
|
||||||
|
"enable_v3" : true
|
||||||
}
|
}
|
||||||
|
|||||||
2037
package-lock.json
generated
2037
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
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.0.4",
|
"version": "3.0.5",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.11.x"
|
"node": "16.11.x"
|
||||||
},
|
},
|
||||||
@@ -40,28 +40,30 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.16.0",
|
"@babel/core": "^7.16.5",
|
||||||
"@babel/plugin-transform-runtime": "^7.16.4",
|
"@babel/plugin-transform-runtime": "^7.16.5",
|
||||||
"@babel/preset-env": "^7.16.4",
|
"@babel/preset-env": "^7.16.5",
|
||||||
"@babel/preset-react": "^7.16.0",
|
"@babel/preset-react": "^7.16.5",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.1",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"codemirror": "^5.64.0",
|
"codemirror": "^5.64.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.10.1",
|
"dedent-tabs": "^0.10.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-async-handler": "^1.1.4",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.1",
|
"express-static-gzip": "2.1.1",
|
||||||
"fs-extra": "10.0.0",
|
"fs-extra": "10.0.0",
|
||||||
"googleapis": "92.0.0",
|
"googleapis": "92.0.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
"less": "^3.13.1",
|
"less": "^3.13.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "3.0.8",
|
"marked": "4.0.7",
|
||||||
|
"marked-extended-tables": "^1.0.3",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"mongoose": "^6.0.14",
|
"mongoose": "^6.1.2",
|
||||||
"nanoid": "3.1.30",
|
"nanoid": "3.1.30",
|
||||||
"nconf": "^0.11.3",
|
"nconf": "^0.11.3",
|
||||||
"prop-types": "15.7.2",
|
"prop-types": "15.7.2",
|
||||||
@@ -75,7 +77,7 @@
|
|||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.3.0",
|
"eslint": "^8.4.1",
|
||||||
"eslint-plugin-react": "^7.27.1",
|
"eslint-plugin-react": "^7.27.1",
|
||||||
"pico-check": "^2.1.3"
|
"pico-check": "^2.1.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,16 @@
|
|||||||
"codemirror/mode/javascript/javascript.js",
|
"codemirror/mode/javascript/javascript.js",
|
||||||
"codemirror/addon/fold/foldcode.js",
|
"codemirror/addon/fold/foldcode.js",
|
||||||
"codemirror/addon/fold/foldgutter.js",
|
"codemirror/addon/fold/foldgutter.js",
|
||||||
|
"codemirror/addon/fold/xml-fold.js",
|
||||||
|
"codemirror/addon/search/search.js",
|
||||||
|
"codemirror/addon/search/searchcursor.js",
|
||||||
|
"codemirror/addon/search/jump-to-line.js",
|
||||||
|
"codemirror/addon/search/match-highlighter.js",
|
||||||
|
"codemirror/addon/search/matchesonscrollbar.js",
|
||||||
|
"codemirror/addon/dialog/dialog.js",
|
||||||
|
"codemirror/addon/edit/closetag.js",
|
||||||
|
"codemirror/addon/edit/trailingspace.js",
|
||||||
|
"codemirror/addon/selection/active-line.js",
|
||||||
"moment",
|
"moment",
|
||||||
"superagent",
|
"superagent",
|
||||||
"marked"
|
"marked"
|
||||||
|
|||||||
18
server.js
18
server.js
@@ -2,6 +2,7 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const jwt = require('jwt-simple');
|
const jwt = require('jwt-simple');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const homebrewApi = require('./server/homebrew.api.js');
|
const homebrewApi = require('./server/homebrew.api.js');
|
||||||
@@ -32,7 +33,7 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{
|
|||||||
if(accessType == 'raw') {
|
if(accessType == 'raw') {
|
||||||
return brew;
|
return brew;
|
||||||
}
|
}
|
||||||
splitTextAndStyle(brew);
|
splitTextStyleAndMetadata(brew);
|
||||||
return brew;
|
return brew;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,8 +46,15 @@ const sanitizeBrew = (brew, full=false)=>{
|
|||||||
return brew;
|
return brew;
|
||||||
};
|
};
|
||||||
|
|
||||||
const splitTextAndStyle = (brew)=>{
|
const splitTextStyleAndMetadata = (brew)=>{
|
||||||
brew.text = brew.text.replaceAll('\r\n', '\n');
|
brew.text = brew.text.replaceAll('\r\n', '\n');
|
||||||
|
if(brew.text.startsWith('```metadata')) {
|
||||||
|
const index = brew.text.indexOf('```\n\n');
|
||||||
|
const metadataSection = brew.text.slice(12, index - 1);
|
||||||
|
const metadata = yaml.load(metadataSection);
|
||||||
|
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer']));
|
||||||
|
brew.text = brew.text.slice(index + 5);
|
||||||
|
}
|
||||||
if(brew.text.startsWith('```css')) {
|
if(brew.text.startsWith('```css')) {
|
||||||
const index = brew.text.indexOf('```\n\n');
|
const index = brew.text.indexOf('```\n\n');
|
||||||
brew.style = brew.text.slice(7, index - 1);
|
brew.style = brew.text.slice(7, index - 1);
|
||||||
@@ -129,7 +137,7 @@ app.get('/v3_preview', async (req, res, next)=>{
|
|||||||
text : welcomeTextV3,
|
text : welcomeTextV3,
|
||||||
renderer : 'V3'
|
renderer : 'V3'
|
||||||
};
|
};
|
||||||
splitTextAndStyle(brew);
|
splitTextStyleAndMetadata(brew);
|
||||||
req.brew = brew;
|
req.brew = brew;
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
@@ -152,7 +160,7 @@ app.get('/changelog', async (req, res, next)=>{
|
|||||||
text : changelogText,
|
text : changelogText,
|
||||||
renderer : 'V3'
|
renderer : 'V3'
|
||||||
};
|
};
|
||||||
splitTextAndStyle(brew);
|
splitTextStyleAndMetadata(brew);
|
||||||
req.brew = brew;
|
req.brew = brew;
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
@@ -164,7 +172,7 @@ app.get('/faq', async (req, res, next)=>{
|
|||||||
text : faqText,
|
text : faqText,
|
||||||
renderer : 'V3'
|
renderer : 'V3'
|
||||||
};
|
};
|
||||||
splitTextAndStyle(brew);
|
splitTextStyleAndMetadata(brew);
|
||||||
req.brew = brew;
|
req.brew = brew;
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const router = require('express').Router();
|
|||||||
const zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
const GoogleActions = require('./googleActions.js');
|
const GoogleActions = require('./googleActions.js');
|
||||||
const Markdown = require('../shared/naturalcrit/markdown.js');
|
const Markdown = require('../shared/naturalcrit/markdown.js');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
// const getTopBrews = (cb) => {
|
// const getTopBrews = (cb) => {
|
||||||
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
|
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
|
||||||
@@ -11,6 +12,22 @@ const Markdown = require('../shared/naturalcrit/markdown.js');
|
|||||||
// });
|
// });
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
const mergeBrewText = (brew)=>{
|
||||||
|
let text = brew.text;
|
||||||
|
if(brew.style !== undefined) {
|
||||||
|
text = `\`\`\`css\n` +
|
||||||
|
`${brew.style || ''}\n` +
|
||||||
|
`\`\`\`\n\n` +
|
||||||
|
`${text}`;
|
||||||
|
}
|
||||||
|
const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer']);
|
||||||
|
text = `\`\`\`metadata\n` +
|
||||||
|
`${yaml.dump(metadata)}\n` +
|
||||||
|
`\`\`\`\n\n` +
|
||||||
|
`${text}`;
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
const MAX_TITLE_LENGTH = 100;
|
const MAX_TITLE_LENGTH = 100;
|
||||||
|
|
||||||
const getGoodBrewTitle = (text)=>{
|
const getGoodBrewTitle = (text)=>{
|
||||||
@@ -28,16 +45,6 @@ const excludePropsFromUpdate = (brew)=>{
|
|||||||
return brew;
|
return brew;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergeBrewText = (text, style)=>{
|
|
||||||
if(typeof style !== 'undefined') {
|
|
||||||
text = `\`\`\`css\n` +
|
|
||||||
`${style}\n` +
|
|
||||||
`\`\`\`\n\n` +
|
|
||||||
`${text}`;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
const newBrew = (req, res)=>{
|
const newBrew = (req, res)=>{
|
||||||
const brew = req.body;
|
const brew = req.body;
|
||||||
|
|
||||||
@@ -46,7 +53,7 @@ const newBrew = (req, res)=>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
brew.authors = (req.account) ? [req.account.username] : [];
|
brew.authors = (req.account) ? [req.account.username] : [];
|
||||||
brew.text = mergeBrewText(brew.text, brew.style);
|
brew.text = mergeBrewText(brew);
|
||||||
|
|
||||||
delete brew.editId;
|
delete brew.editId;
|
||||||
delete brew.shareId;
|
delete brew.shareId;
|
||||||
@@ -75,7 +82,7 @@ const updateBrew = (req, res)=>{
|
|||||||
.then((brew)=>{
|
.then((brew)=>{
|
||||||
const updateBrew = excludePropsFromUpdate(req.body);
|
const updateBrew = excludePropsFromUpdate(req.body);
|
||||||
brew = _.merge(brew, updateBrew);
|
brew = _.merge(brew, updateBrew);
|
||||||
brew.text = mergeBrewText(brew.text, brew.style);
|
brew.text = mergeBrewText(brew);
|
||||||
|
|
||||||
// Compress brew text to binary before saving
|
// Compress brew text to binary before saving
|
||||||
brew.textBin = zlib.deflateRawSync(brew.text);
|
brew.textBin = zlib.deflateRawSync(brew.text);
|
||||||
@@ -143,7 +150,7 @@ const newGoogleBrew = async (req, res, next)=>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
brew.authors = (req.account) ? [req.account.username] : [];
|
brew.authors = (req.account) ? [req.account.username] : [];
|
||||||
brew.text = mergeBrewText(brew.text, brew.style);
|
brew.text = mergeBrewText(brew);
|
||||||
|
|
||||||
delete brew.editId;
|
delete brew.editId;
|
||||||
delete brew.shareId;
|
delete brew.shareId;
|
||||||
@@ -165,13 +172,13 @@ const updateGoogleBrew = async (req, res, next)=>{
|
|||||||
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
||||||
|
|
||||||
const brew = excludePropsFromUpdate(req.body);
|
const brew = excludePropsFromUpdate(req.body);
|
||||||
brew.text = mergeBrewText(brew.text, brew.style);
|
brew.text = mergeBrewText(brew);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew);
|
const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew);
|
||||||
return res.status(200).send(updatedBrew);
|
return res.status(200).send(updatedBrew);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return res.status(err.response.status).send(err);
|
return res.status(err.response?.status || 500).send(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
48
shared/naturalcrit/codeEditor/close-tag.js
Normal file
48
shared/naturalcrit/codeEditor/close-tag.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const autoCloseCurlyBraces = function(CodeMirror, cm, typingClosingBrace) {
|
||||||
|
const ranges = cm.listSelections(), replacements = [];
|
||||||
|
for (let i = 0; i < ranges.length; i++) {
|
||||||
|
if(!ranges[i].empty()) return CodeMirror.Pass;
|
||||||
|
const pos = ranges[i].head, line = cm.getLine(pos.line), tok = cm.getTokenAt(pos);
|
||||||
|
if(!typingClosingBrace && (tok.type == 'string' || tok.string.charAt(0) != '{' || tok.start != pos.ch - 1))
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
else if(typingClosingBrace) {
|
||||||
|
let hasUnclosedBraces = false, index = -1;
|
||||||
|
do {
|
||||||
|
index = line.indexOf('{{', index + 1);
|
||||||
|
if(index !== -1 && line.indexOf('}}', index + 1) === -1) {
|
||||||
|
hasUnclosedBraces = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (index !== -1);
|
||||||
|
if(!hasUnclosedBraces) return CodeMirror.Pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
replacements[i] = typingClosingBrace ? {
|
||||||
|
text : '}}',
|
||||||
|
newPos : CodeMirror.Pos(pos.line, pos.ch + 2)
|
||||||
|
} : {
|
||||||
|
text : '{}}',
|
||||||
|
newPos : CodeMirror.Pos(pos.line, pos.ch + 1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = ranges.length - 1; i >= 0; i--) {
|
||||||
|
const info = replacements[i];
|
||||||
|
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, '+insert');
|
||||||
|
const sel = cm.listSelections().slice(0);
|
||||||
|
sel[i] = {
|
||||||
|
head : info.newPos,
|
||||||
|
anchor : info.newPos
|
||||||
|
};
|
||||||
|
cm.setSelections(sel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
autoCloseCurlyBraces : function(CodeMirror, codeMirror) {
|
||||||
|
const map = { name: 'autoCloseCurlyBraces' };
|
||||||
|
map[`'{'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm); };
|
||||||
|
map[`'}'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm, true); };
|
||||||
|
codeMirror.addKeyMap(map);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4,7 +4,7 @@ const React = require('react');
|
|||||||
const createClass = require('create-react-class');
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
const closeTag = require('./close-tag');
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
if(typeof navigator !== 'undefined'){
|
if(typeof navigator !== 'undefined'){
|
||||||
@@ -16,8 +16,24 @@ if(typeof navigator !== 'undefined'){
|
|||||||
require('codemirror/mode/javascript/javascript.js');
|
require('codemirror/mode/javascript/javascript.js');
|
||||||
|
|
||||||
//Addons
|
//Addons
|
||||||
|
//Code folding
|
||||||
require('codemirror/addon/fold/foldcode.js');
|
require('codemirror/addon/fold/foldcode.js');
|
||||||
require('codemirror/addon/fold/foldgutter.js');
|
require('codemirror/addon/fold/foldgutter.js');
|
||||||
|
//Search and replace
|
||||||
|
require('codemirror/addon/search/search.js');
|
||||||
|
require('codemirror/addon/search/searchcursor.js');
|
||||||
|
require('codemirror/addon/search/jump-to-line.js');
|
||||||
|
require('codemirror/addon/search/match-highlighter.js');
|
||||||
|
require('codemirror/addon/search/matchesonscrollbar.js');
|
||||||
|
require('codemirror/addon/dialog/dialog.js');
|
||||||
|
//Trailing space highlighting
|
||||||
|
require('codemirror/addon/edit/trailingspace.js');
|
||||||
|
//Active line highlighting
|
||||||
|
require('codemirror/addon/selection/active-line.js');
|
||||||
|
//Auto-closing
|
||||||
|
//XML code folding is a requirement of the auto-closing tag feature and is not enabled
|
||||||
|
require('codemirror/addon/fold/xml-fold.js');
|
||||||
|
require('codemirror/addon/edit/closetag.js');
|
||||||
|
|
||||||
const foldCode = require('./fold-code');
|
const foldCode = require('./fold-code');
|
||||||
foldCode.registerHomebreweryHelper(CodeMirror);
|
foldCode.registerHomebreweryHelper(CodeMirror);
|
||||||
@@ -115,6 +131,9 @@ const CodeEditor = createClass({
|
|||||||
'Shift-Cmd-Enter' : this.newColumn,
|
'Shift-Cmd-Enter' : this.newColumn,
|
||||||
'Ctrl-Enter' : this.newPage,
|
'Ctrl-Enter' : this.newPage,
|
||||||
'Cmd-Enter' : this.newPage,
|
'Cmd-Enter' : this.newPage,
|
||||||
|
'Ctrl-F' : 'findPersistent',
|
||||||
|
'Cmd-F' : 'findPersistent',
|
||||||
|
'Shift-Enter' : 'findPersistentPrevious',
|
||||||
'Ctrl-[' : this.foldAllCode,
|
'Ctrl-[' : this.foldAllCode,
|
||||||
'Cmd-[' : this.foldAllCode,
|
'Cmd-[' : this.foldAllCode,
|
||||||
'Ctrl-]' : this.unfoldAllCode,
|
'Ctrl-]' : this.unfoldAllCode,
|
||||||
@@ -142,8 +161,19 @@ const CodeEditor = createClass({
|
|||||||
return `\u21A4 ${text} \u21A6`;
|
return `\u21A4 ${text} \u21A6`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
|
gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
|
autoCloseTags : true,
|
||||||
|
styleActiveLine : true,
|
||||||
|
showTrailingSpace : true,
|
||||||
|
specialChars : / /,
|
||||||
|
specialCharPlaceholder : function(char) {
|
||||||
|
const el = document.createElement('span');
|
||||||
|
el.className = 'cm-space';
|
||||||
|
el.innerHTML = ' ';
|
||||||
|
return el;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
closeTag.autoCloseCurlyBraces(CodeMirror, this.codeMirror);
|
||||||
|
|
||||||
// Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works.
|
// Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works.
|
||||||
this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());});
|
this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
@import (less) 'codemirror/lib/codemirror.css';
|
@import (less) 'codemirror/lib/codemirror.css';
|
||||||
@import (less) 'codemirror/addon/fold/foldgutter.css';
|
@import (less) 'codemirror/addon/fold/foldgutter.css';
|
||||||
|
@import (less) 'codemirror/addon/search/matchesonscrollbar.css';
|
||||||
|
@import (less) 'codemirror/addon/dialog/dialog.css';
|
||||||
|
|
||||||
.codeEditor{
|
.codeEditor{
|
||||||
.CodeMirror-foldmarker {
|
.CodeMirror-foldmarker {
|
||||||
@@ -7,4 +9,14 @@
|
|||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-tab {
|
||||||
|
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-trailingspace {
|
||||||
|
.cm-space {
|
||||||
|
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const Markdown = require('marked');
|
const Marked = require('marked');
|
||||||
const renderer = new Markdown.Renderer();
|
const MarkedExtendedTables = require('marked-extended-tables');
|
||||||
|
const renderer = new Marked.Renderer();
|
||||||
|
|
||||||
//Processes the markdown within an HTML block if it's just a class-wrapper
|
//Processes the markdown within an HTML block if it's just a class-wrapper
|
||||||
renderer.html = function (html) {
|
renderer.html = function (html) {
|
||||||
@@ -9,7 +10,7 @@ renderer.html = function (html) {
|
|||||||
const openTag = html.substring(0, html.indexOf('>')+1);
|
const openTag = html.substring(0, html.indexOf('>')+1);
|
||||||
html = html.substring(html.indexOf('>')+1);
|
html = html.substring(html.indexOf('>')+1);
|
||||||
html = html.substring(0, html.lastIndexOf('</div>'));
|
html = html.substring(0, html.lastIndexOf('</div>'));
|
||||||
return `${openTag} ${Markdown(html)} </div>`;
|
return `${openTag} ${Marked.parse(html)} </div>`;
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
@@ -235,200 +236,10 @@ const definitionLists = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const spanTable = {
|
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] });
|
||||||
name : 'spanTable',
|
Marked.use(MarkedExtendedTables());
|
||||||
level : 'block', // Is this a block-level or inline-level tokenizer?
|
Marked.use(mustacheInjectBlock);
|
||||||
start(src) { return src.match(/^\n *([^\n ].*\|.*)\n/)?.index; }, // Hint to Marked.js to stop and check for a match
|
Marked.use({ smartypants: true });
|
||||||
tokenizer(src, tokens) {
|
|
||||||
//const regex = this.tokenizer.rules.block.table;
|
|
||||||
const regex = new RegExp('^ *([^\\n ].*\\|.*\\n(?: *[^\\s].*\\n)*?)' // Header
|
|
||||||
+ ' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)\\|?' // Align
|
|
||||||
+ '(?:\\n *((?:(?!\\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})' // Cells
|
|
||||||
+ '(?:\\n+|$)| {0,3}#{1,6} | {0,3}>| {4}[^\\n]| {0,3}(?:`{3,}'
|
|
||||||
+ '(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n| {0,3}(?:[*+-]|1[.)]) |'
|
|
||||||
+ '<\\/?(?:address|article|aside|base|basefont|blockquote|body|'
|
|
||||||
+ 'caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\\n|\\/?>)|<(?:script|pre|style|textarea|!--)).*(?:\\n|$))*)\\n*|$)'); // Cells
|
|
||||||
const cap = regex.exec(src);
|
|
||||||
|
|
||||||
if(cap) {
|
|
||||||
const item = {
|
|
||||||
type : 'spanTable',
|
|
||||||
header : cap[1].replace(/\n$/, '').split('\n'),
|
|
||||||
align : cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
|
|
||||||
rows : cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get first header row to determine how many columns
|
|
||||||
item.header[0] = splitCells(item.header[0]);
|
|
||||||
|
|
||||||
const colCount = item.header[0].reduce((length, header)=>{
|
|
||||||
return length + header.colspan;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if(colCount === item.align.length) {
|
|
||||||
item.raw = cap[0];
|
|
||||||
|
|
||||||
let i, j, k, row;
|
|
||||||
|
|
||||||
// Get alignment row (:---:)
|
|
||||||
let l = item.align.length;
|
|
||||||
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
if(/^ *-+: *$/.test(item.align[i])) {
|
|
||||||
item.align[i] = 'right';
|
|
||||||
} else if(/^ *:-+: *$/.test(item.align[i])) {
|
|
||||||
item.align[i] = 'center';
|
|
||||||
} else if(/^ *:-+ *$/.test(item.align[i])) {
|
|
||||||
item.align[i] = 'left';
|
|
||||||
} else {
|
|
||||||
item.align[i] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get any remaining header rows
|
|
||||||
l = item.header.length;
|
|
||||||
for (i = 1; i < l; i++) {
|
|
||||||
item.header[i] = splitCells(item.header[i], colCount, item.header[i-1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get main table cells
|
|
||||||
l = item.rows.length;
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
item.rows[i] = splitCells(item.rows[i], colCount, item.rows[i-1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// header child tokens
|
|
||||||
l = item.header.length;
|
|
||||||
for (j = 0; j < l; j++) {
|
|
||||||
row = item.header[j];
|
|
||||||
for (k = 0; k < row.length; k++) {
|
|
||||||
row[k].tokens = [];
|
|
||||||
this.lexer.inlineTokens(row[k].text, row[k].tokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cell child tokens
|
|
||||||
l = item.rows.length;
|
|
||||||
for (j = 0; j < l; j++) {
|
|
||||||
row = item.rows[j];
|
|
||||||
for (k = 0; k < row.length; k++) {
|
|
||||||
row[k].tokens = [];
|
|
||||||
this.lexer.inlineTokens(row[k].text, row[k].tokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
let i, j, row, cell, col, text;
|
|
||||||
let output = `<table>`;
|
|
||||||
output += `<thead>`;
|
|
||||||
for (i = 0; i < token.header.length; i++) {
|
|
||||||
row = token.header[i];
|
|
||||||
let col = 0;
|
|
||||||
output += `<tr>`;
|
|
||||||
for (j = 0; j < row.length; j++) {
|
|
||||||
cell = row[j];
|
|
||||||
text = this.parser.parseInline(cell.tokens);
|
|
||||||
output += getTableCell(text, cell, 'th', token.align[col]);
|
|
||||||
col += cell.colspan;
|
|
||||||
}
|
|
||||||
output += `</tr>`;
|
|
||||||
}
|
|
||||||
output += `</thead>`;
|
|
||||||
if(token.rows.length) {
|
|
||||||
output += `<tbody>`;
|
|
||||||
for (i = 0; i < token.rows.length; i++) {
|
|
||||||
row = token.rows[i];
|
|
||||||
col = 0;
|
|
||||||
output += `<tr>`;
|
|
||||||
for (j = 0; j < row.length; j++) {
|
|
||||||
cell = row[j];
|
|
||||||
text = this.parser.parseInline(cell.tokens);
|
|
||||||
output += getTableCell(text, cell, 'td', token.align[col]);
|
|
||||||
col += cell.colspan;
|
|
||||||
}
|
|
||||||
output += `</tr>`;
|
|
||||||
}
|
|
||||||
output += `</tbody>`;
|
|
||||||
}
|
|
||||||
output += `</table>`;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTableCell = (text, cell, type, align)=>{
|
|
||||||
if(!cell.rowspan) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const tag = `<${type}`
|
|
||||||
+ `${cell.colspan > 1 ? ` colspan=${cell.colspan}` : ''}`
|
|
||||||
+ `${cell.rowspan > 1 ? ` rowspan=${cell.rowspan}` : ''}`
|
|
||||||
+ `${align ? ` align=${align}` : ''}>`;
|
|
||||||
return `${tag + text}</${type}>\n`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const splitCells = (tableRow, count, prevRow = [])=>{
|
|
||||||
const cells = [...tableRow.matchAll(/(?:[^|\\]|\\.?)+(?:\|+|$)/g)].map((x)=>x[0]);
|
|
||||||
|
|
||||||
// Remove first/last cell in a row if whitespace only and no leading/trailing pipe
|
|
||||||
if(!cells[0]?.trim()) { cells.shift(); }
|
|
||||||
if(!cells[cells.length - 1]?.trim()) { cells.pop(); }
|
|
||||||
|
|
||||||
let numCols = 0;
|
|
||||||
let i, j, trimmedCell, prevCell, prevCols;
|
|
||||||
|
|
||||||
for (i = 0; i < cells.length; i++) {
|
|
||||||
trimmedCell = cells[i].split(/\|+$/)[0];
|
|
||||||
cells[i] = {
|
|
||||||
rowspan : 1,
|
|
||||||
colspan : Math.max(cells[i].length - trimmedCell.length, 1),
|
|
||||||
text : trimmedCell.trim().replace(/\\\|/g, '|')
|
|
||||||
// display escaped pipes as normal character
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle Rowspan
|
|
||||||
if(trimmedCell.slice(-1) == '^' && prevRow.length) {
|
|
||||||
// Find matching cell in previous row
|
|
||||||
prevCols = 0;
|
|
||||||
for (j = 0; j < prevRow.length; j++) {
|
|
||||||
prevCell = prevRow[j];
|
|
||||||
if((prevCols == numCols) && (prevCell.colspan == cells[i].colspan)) {
|
|
||||||
// merge into matching cell in previous row (the "target")
|
|
||||||
cells[i].rowSpanTarget = prevCell.rowSpanTarget ?? prevCell;
|
|
||||||
cells[i].rowSpanTarget.text += ` ${cells[i].text.slice(0, -1)}`;
|
|
||||||
cells[i].rowSpanTarget.rowspan += 1;
|
|
||||||
cells[i].rowspan = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
prevCols += prevCell.colspan;
|
|
||||||
if(prevCols > numCols)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
numCols += cells[i].colspan;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force main cell rows to match header column count
|
|
||||||
if(numCols > count) {
|
|
||||||
cells.splice(count);
|
|
||||||
} else {
|
|
||||||
while (numCols < count) {
|
|
||||||
cells.push({
|
|
||||||
colspan : 1,
|
|
||||||
text : ''
|
|
||||||
});
|
|
||||||
numCols += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cells;
|
|
||||||
};
|
|
||||||
|
|
||||||
Markdown.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, spanTable] });
|
|
||||||
Markdown.use(mustacheInjectBlock);
|
|
||||||
Markdown.use({ smartypants: true });
|
|
||||||
|
|
||||||
//Fix local links in the Preview iFrame to link inside the frame
|
//Fix local links in the Preview iFrame to link inside the frame
|
||||||
renderer.link = function (href, title, text) {
|
renderer.link = function (href, title, text) {
|
||||||
@@ -532,11 +343,11 @@ const processStyleTags = (string)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
marked : Markdown,
|
marked : Marked,
|
||||||
render : (rawBrewText)=>{
|
render : (rawBrewText)=>{
|
||||||
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`)
|
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`)
|
||||||
.replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`);
|
.replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`);
|
||||||
return Markdown(
|
return Marked.parse(
|
||||||
sanatizeScriptTags(rawBrewText),
|
sanatizeScriptTags(rawBrewText),
|
||||||
{ renderer: renderer }
|
{ renderer: renderer }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -511,7 +511,8 @@ body {
|
|||||||
color : #58180d;
|
color : #58180d;
|
||||||
background-color : #faf7ea;
|
background-color : #faf7ea;
|
||||||
border-radius : 4px;
|
border-radius : 4px;
|
||||||
white-space : pre-wrap
|
white-space : pre-wrap;
|
||||||
|
overflow-wrap : break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code{
|
pre code{
|
||||||
@@ -652,7 +653,7 @@ body {
|
|||||||
break-inside : avoid;
|
break-inside : avoid;
|
||||||
h1 {
|
h1 {
|
||||||
text-align : center;
|
text-align : center;
|
||||||
margin-bottom : 0cm;
|
margin-bottom : 0.3cm;
|
||||||
}
|
}
|
||||||
a{
|
a{
|
||||||
display : table;
|
display : table;
|
||||||
@@ -663,14 +664,12 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
h4 {
|
h4 {
|
||||||
margin-top : 0.14cm;
|
margin-top : 0.2cm;
|
||||||
|
line-height : 0.4cm;
|
||||||
& + ul li {
|
& + ul li {
|
||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& > ul {
|
|
||||||
margin-top: 0.52cm;
|
|
||||||
}
|
|
||||||
ul{
|
ul{
|
||||||
padding-left : 0;
|
padding-left : 0;
|
||||||
list-style-type : none;
|
list-style-type : none;
|
||||||
|
|||||||
Reference in New Issue
Block a user