diff --git a/client/admin/admin.jsx b/client/admin/admin.jsx index a481c9c14c..92e0b2aee3 100644 --- a/client/admin/admin.jsx +++ b/client/admin/admin.jsx @@ -18,7 +18,7 @@ const Admin = createClass({
- + homebrewery admin
diff --git a/client/admin/brewCleanup/brewCleanup.jsx b/client/admin/brewCleanup/brewCleanup.jsx index 55efbb0e1e..b55a70bef7 100644 --- a/client/admin/brewCleanup/brewCleanup.jsx +++ b/client/admin/brewCleanup/brewCleanup.jsx @@ -45,8 +45,8 @@ const BrewCleanup = createClass({ return
Found {this.state.count} Brews that could be removed. @@ -59,7 +59,7 @@ const BrewCleanup = createClass({ diff --git a/client/admin/brewCompress/brewCompress.jsx b/client/admin/brewCompress/brewCompress.jsx index ad4e0cd8e6..c12f430a22 100644 --- a/client/admin/brewCompress/brewCompress.jsx +++ b/client/admin/brewCompress/brewCompress.jsx @@ -59,8 +59,8 @@ const BrewCompress = createClass({ return
{this.state.pending @@ -76,7 +76,7 @@ const BrewCompress = createClass({ diff --git a/client/admin/brewLookup/brewLookup.jsx b/client/admin/brewLookup/brewLookup.jsx index c6103bef4c..c9212d9900 100644 --- a/client/admin/brewLookup/brewLookup.jsx +++ b/client/admin/brewLookup/brewLookup.jsx @@ -61,7 +61,7 @@ const BrewLookup = createClass({

Brew Lookup

; } diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index d34025a0ad..5b337301a3 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -4,6 +4,7 @@ const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); +const MarkdownLegacy = require('naturalcrit/markdownLegacy.js'); const Markdown = require('naturalcrit/markdown.js'); const ErrorBar = require('./errorBar/errorBar.jsx'); @@ -18,12 +19,18 @@ const PPR_THRESHOLD = 50; const BrewRenderer = createClass({ getDefaultProps : function() { return { - text : '', - errors : [] + text : '', + renderer : 'legacy', + errors : [] }; }, getInitialState : function() { - const pages = this.props.text.split('\\page'); + let pages; + if(this.props.renderer == 'legacy') { + pages = this.props.text.split('\\page'); + } else { + pages = this.props.text.split(/^\\page/gm); + } return { viewablePageNumber : 0, @@ -34,7 +41,7 @@ const BrewRenderer = createClass({ usePPR : pages.length >= PPR_THRESHOLD, visibility : 'hidden', initialContent : ` - + @@ -48,12 +55,19 @@ const BrewRenderer = createClass({ window.removeEventListener('resize', this.updateSize); }, - componentWillReceiveProps : function(nextProps) { - const pages = nextProps.text.split('\\page'); - this.setState({ - pages : pages, - usePPR : pages.length >= PPR_THRESHOLD - }); + componentDidUpdate : function(prevProps) { + if(prevProps.text !== this.props.text) { + let pages; + if(this.props.renderer == 'legacy') { + pages = this.props.text.split('\\page'); + } else { + pages = this.props.text.split(/^\\page/gm); + } + this.setState({ + pages : pages, + usePPR : pages.length >= PPR_THRESHOLD + }); + } }, updateSize : function() { @@ -103,12 +117,15 @@ const BrewRenderer = createClass({ renderDummyPage : function(index){ return
- +
; }, renderPage : function(pageText, index){ - return
; + if(this.props.renderer == 'legacy') + return
; + else + return
; }, renderPages : function(){ @@ -159,7 +176,7 @@ const BrewRenderer = createClass({ : null} -
diff --git a/client/homebrew/brewRenderer/brewRenderer.less b/client/homebrew/brewRenderer/brewRenderer.less index 6dee6433f6..231e3301e4 100644 --- a/client/homebrew/brewRenderer/brewRenderer.less +++ b/client/homebrew/brewRenderer/brewRenderer.less @@ -1,8 +1,7 @@ +@import (multiple, less) 'shared/naturalcrit/styles/reset.less'; +& {@import (multiple, less) './client/homebrew/phbStyle/phb.styleLegacy.less';} //&{} keeps internal variables locally-scoped +& {@import (multiple, less) './client/homebrew/phbStyle/phb.style.less';} -@import (less) './client/homebrew/phbStyle/phb.style.less'; -.pane{ - position : relative; -} .brewRenderer{ will-change : transform; overflow-y : scroll; @@ -14,8 +13,17 @@ margin-left : auto; box-shadow : 1px 4px 14px #000; } + &>.phb3{ + margin-right : auto; + margin-bottom : 30px; + margin-left : auto; + box-shadow : 1px 4px 14px #000; + } } } +.pane{ + position : relative; +} .pageInfo{ position : absolute; right : 17px; @@ -37,4 +45,4 @@ font-size : 10px; font-weight : 800; color : white; -} \ No newline at end of file +} diff --git a/client/homebrew/brewRenderer/errorBar/errorBar.jsx b/client/homebrew/brewRenderer/errorBar/errorBar.jsx index c4de215f33..971903211f 100644 --- a/client/homebrew/brewRenderer/errorBar/errorBar.jsx +++ b/client/homebrew/brewRenderer/errorBar/errorBar.jsx @@ -62,7 +62,7 @@ const ErrorBar = createClass({ if(!this.props.errors.length) return null; return
- +

There are HTML errors in your markup

If these aren't fixed your brew will not render properly when you print it to PDF or share it {this.renderErrors()} diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx index 973a010353..ea202deaec 100644 --- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx +++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx @@ -60,8 +60,8 @@ const NotificationPopup = createClass({ if(_.isEmpty(this.state.notifications)) return null; return
- - + +

Notice

This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:
    {_.values(this.state.notifications)}
diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index d85d36d34e..dd93b8aaa5 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -23,7 +23,8 @@ const Editor = createClass({ metadata : {}, onMetadataChange : ()=>{}, - showMetaButton : true + showMetaButton : true, + renderer : 'legacy' }; }, getInitialState : function() { @@ -38,7 +39,7 @@ const Editor = createClass({ componentDidMount : function() { this.updateEditorSize(); - this.highlightPageLines(); + this.highlightCustomMarkdown(); window.addEventListener('resize', this.updateEditorSize); }, componentWillUnmount : function() { @@ -78,15 +79,67 @@ const Editor = createClass({ }, 1); }, - highlightPageLines : function(){ + highlightCustomMarkdown : function(){ if(!this.refs.codeEditor) return; const codeMirror = this.refs.codeEditor.codeMirror; + //reset custom text styles + const customHighlights = codeMirror.getAllMarks(); + for (let i=0;i{ - if(line.indexOf('\\page') !== -1){ - codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); - r.push(lineNumber); + + //reset custom line styles + codeMirror.removeLineClass(lineNumber, 'background'); + codeMirror.removeLineClass(lineNumber, 'text'); + + // Legacy Codemirror styling + if(this.props.renderer == 'legacy') { + if(line.includes('\\page')){ + codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); + r.push(lineNumber); + } } + + // New Codemirror styling for V3 renderer + if(this.props.renderer == 'V3') { + if(line.startsWith('\\page')){ + codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); + r.push(lineNumber); + } + + if(line.startsWith('\\column')){ + codeMirror.addLineClass(lineNumber, 'text', 'columnSplit'); + r.push(lineNumber); + } + + if(line.startsWith('{{') || line.startsWith('}}')){ + 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' }); + } + + if(line.includes('{{') && line.includes('}}')){ + const regex = /{{(?:[\w,#-]|="[\w, ]*")*\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' }); + } + } + } + return r; }, []); return lineNumbers; @@ -112,7 +165,7 @@ const Editor = createClass({ }, render : function(){ - this.highlightPageLines(); + this.highlightCustomMarkdown(); return (
+ showMetaButton={this.props.showMetaButton} + renderer={this.props.renderer} /> {this.renderMetadataEditor()} - +
*/}
diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index e13b7b8ef2..cd190ea886 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -9,6 +9,22 @@ background-color : fade(#333, 15%); border-bottom : #333 solid 1px; } + .columnSplit{ + font-style : italic; + color : grey; + background-color : fade(#299, 15%); + border-bottom : #299 solid 1px; + } + .block{ + color : purple; + font-weight : bold; + //font-style: italic; + } + .inline-block{ + color : red; + font-weight : bold; + //font-style: italic; + } } .brewJump{ @@ -26,4 +42,4 @@ .tooltipLeft("Jump to brew page"); } -} \ No newline at end of file +} diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 3bcb76c967..89f6f4bb4b 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -17,7 +17,8 @@ const MetadataEditor = createClass({ tags : '', published : false, authors : [], - systems : [] + systems : [], + renderer : 'legacy' }, onChange : ()=>{} }; @@ -36,6 +37,12 @@ const MetadataEditor = createClass({ } this.props.onChange(this.props.metadata); }, + handleRenderer : function(renderer, e){ + if(e.target.checked){ + this.props.metadata.renderer = renderer; + } + this.props.onChange(this.props.metadata); + }, handlePublish : function(val){ this.props.onChange(_.merge({}, this.props.metadata, { published : val @@ -83,11 +90,11 @@ const MetadataEditor = createClass({ renderPublish : function(){ if(this.props.metadata.published){ return ; } else { return ; } }, @@ -99,7 +106,7 @@ const MetadataEditor = createClass({
; @@ -126,13 +133,42 @@ const MetadataEditor = createClass({
; }, + renderRenderOptions : function(){ + if(!global.enable_v3) return; + + return
+ +
+ + + +
+
; + }, + render : function(){ return
@@ -154,6 +190,8 @@ const MetadataEditor = createClass({
*/} + {this.renderAuthors()} +
@@ -161,7 +199,7 @@ const MetadataEditor = createClass({
- {this.renderAuthors()} + {this.renderRenderOptions()}
diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index c347e397f8..65c08c22f9 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -5,7 +5,8 @@ const _ = require('lodash'); const cx = require('classnames'); -const Snippets = require('./snippets/snippets.js'); +const SnippetsLegacy = require('./snippetsLegacy/snippets.js'); +const SnippetsV3 = require('./snippets/snippets.js'); const execute = function(val, brew){ if(_.isFunction(val)) return val(brew); @@ -19,7 +20,14 @@ const Snippetbar = createClass({ onInject : ()=>{}, onToggle : ()=>{}, showmeta : false, - showMetaButton : true + showMetaButton : true, + renderer : '' + }; + }, + + getInitialState : function() { + return { + renderer : this.props.renderer }; }, @@ -28,6 +36,11 @@ const Snippetbar = createClass({ }, renderSnippetGroups : function(){ + if(this.props.renderer == 'V3') + Snippets = SnippetsV3; + else + Snippets = SnippetsLegacy; + return _.map(Snippets, (snippetGroup)=>{ return - + Properties
; }, @@ -69,7 +82,7 @@ const SnippetGroup = createClass({ return { brew : '', groupName : '', - icon : 'fa-rocket', + icon : 'fas fa-rocket', snippets : [], onSnippetClick : function(){}, }; @@ -80,7 +93,7 @@ const SnippetGroup = createClass({ renderSnippets : function(){ return _.map(this.props.snippets, (snippet)=>{ return
this.handleSnippetClick(snippet)}> - + {snippet.name}
; }); @@ -89,7 +102,7 @@ const SnippetGroup = createClass({ render : function(){ return
- + {this.props.groupName}
diff --git a/client/homebrew/editor/snippetbar/snippets/snippets.js b/client/homebrew/editor/snippetbar/snippets/snippets.js index b7d661feac..8cf198857d 100644 --- a/client/homebrew/editor/snippetbar/snippets/snippets.js +++ b/client/homebrew/editor/snippetbar/snippets/snippets.js @@ -12,73 +12,58 @@ module.exports = [ { groupName : 'Editor', - icon : 'fa-pencil', + icon : 'fas fa-pencil-alt', snippets : [ { name : 'Column Break', - icon : 'fa-columns', - gen : '```\n```\n\n' + icon : 'fas fa-columns', + gen : '\n\\column\n' }, { name : 'New Page', - icon : 'fa-file-text', - gen : '\\page\n\n' + icon : 'fas fa-file-alt', + gen : '\n\\page\n' }, { name : 'Vertical Spacing', - icon : 'fa-arrows-v', - gen : '
\n\n' + icon : 'fas fa-times-circle', + gen : '' }, { name : 'Wide Block', - icon : 'fa-arrows-h', - gen : '
\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n
\n' + icon : 'fas fa-times-circle', + gen : '' }, { name : 'Image', - icon : 'fa-image', - gen : [ - '', - 'Credit: Kyounghwan Kim' - ].join('\n') + icon : 'fas fa-times-circle', + gen : '' }, { name : 'Background Image', - icon : 'fa-tree', - gen : [ - '' - ].join('\n') + icon : 'fas fa-times-circle', + gen : '' }, - { name : 'Page Number', - icon : 'fa-bookmark', - gen : '
1
\n
PART 1 | FANCINESS
\n\n' + icon : 'fas fa-bookmark', + gen : '{{pageNumber\n1\n}}\n{{footnote\nPART 1 | FANCINESS\n}}\n\n' }, - { name : 'Auto-incrementing Page Number', - icon : 'fa-sort-numeric-asc', - gen : '
\n' + icon : 'fas fa-sort-numeric-down', + gen : '{{\npageNumber,auto\n}}\n\n' }, - { name : 'Link to page', - icon : 'fa-link', + icon : 'fas fa-link', gen : '[Click here](#p3) to go to page 3\n' }, - { name : 'Table of Contents', - icon : 'fa-book', + icon : 'fas fa-book', gen : TableOfContentsGen }, - - ] }, @@ -87,26 +72,26 @@ module.exports = [ { groupName : 'PHB', - icon : 'fa-book', + icon : 'fas fa-book', snippets : [ { name : 'Spell', - icon : 'fa-magic', + icon : 'fas fa-magic', gen : MagicGen.spell, }, { name : 'Spell List', - icon : 'fa-list', + icon : 'fas fa-scroll', gen : MagicGen.spellList, }, { name : 'Class Feature', - icon : 'fa-trophy', + icon : 'fas fa-mask', gen : ClassFeatureGen, }, { name : 'Note', - icon : 'fa-sticky-note', + icon : 'fas fa-sticky-note', gen : function(){ return [ '> ##### Time to Drop Knowledge', @@ -118,7 +103,7 @@ module.exports = [ }, { name : 'Descriptive Text Box', - icon : 'fa-sticky-note-o', + icon : 'fas fa-comment-alt', gen : function(){ return [ '
', @@ -132,17 +117,17 @@ module.exports = [ }, { name : 'Monster Stat Block', - icon : 'fa-bug', + icon : 'fas fa-spider', gen : MonsterBlockGen.half, }, { name : 'Wide Monster Stat Block', - icon : 'fa-paw', + icon : 'fas fa-dragon', gen : MonsterBlockGen.full, }, { name : 'Cover Page', - icon : 'fa-file-word-o', + icon : 'fas fa-file-word', gen : CoverPageGen, }, ] @@ -154,21 +139,21 @@ module.exports = [ { groupName : 'Tables', - icon : 'fa-table', + icon : 'fas fa-table', snippets : [ { name : 'Class Table', - icon : 'fa-table', + icon : 'fas fa-table', gen : ClassTableGen.full, }, { name : 'Half Class Table', - icon : 'fa-list-alt', + icon : 'fas fa-list-alt', gen : ClassTableGen.half, }, { name : 'Table', - icon : 'fa-th-list', + icon : 'fas fa-th-list', gen : function(){ return [ '##### Cookie Tastiness', @@ -184,7 +169,7 @@ module.exports = [ }, { name : 'Wide Table', - icon : 'fa-list', + icon : 'fas fa-list', gen : function(){ return [ '
', @@ -202,7 +187,7 @@ module.exports = [ }, { name : 'Split Table', - icon : 'fa-th-large', + icon : 'fas fa-th-large', gen : function(){ return [ '
', @@ -238,11 +223,11 @@ module.exports = [ { groupName : 'Print', - icon : 'fa-print', + icon : 'fas fa-print', snippets : [ { name : 'A4 PageSize', - icon : 'fa-file-o', + icon : 'far fa-file', gen : [' + +
+ +# ${_.sample(titles)} + +
+
+##### ${_.sample(subtitles)} +
+ +\\page`; +}; \ No newline at end of file diff --git a/client/homebrew/editor/snippetbar/snippetsLegacy/fullclass.gen.js b/client/homebrew/editor/snippetbar/snippetsLegacy/fullclass.gen.js new file mode 100644 index 0000000000..5ede9e501f --- /dev/null +++ b/client/homebrew/editor/snippetbar/snippetsLegacy/fullclass.gen.js @@ -0,0 +1,43 @@ +const _ = require('lodash'); + +const ClassFeatureGen = require('./classfeature.gen.js'); + +const ClassTableGen = require('./classtable.gen.js'); + +module.exports = function(){ + + const classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher', + 'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge']); + + + const image = _.sample(_.map([ + 'http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png', + 'http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png', + 'http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png', + 'http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png', + 'http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png', + 'http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png', + 'http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png', + 'http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png', + ], function(url){ + return ``; + })); + + + return `${[ + image, + '', + '```', + '```', + '
\n\n', + `## ${classname}`, + 'Cool intro stuff will go here', + + '\\page', + ClassTableGen(classname), + ClassFeatureGen(classname), + + + + ].join('\n')}\n\n\n`; +}; \ No newline at end of file diff --git a/client/homebrew/editor/snippetbar/snippetsLegacy/magic.gen.js b/client/homebrew/editor/snippetbar/snippetsLegacy/magic.gen.js new file mode 100644 index 0000000000..ed17f86922 --- /dev/null +++ b/client/homebrew/editor/snippetbar/snippetsLegacy/magic.gen.js @@ -0,0 +1,91 @@ +const _ = require('lodash'); + +const spellNames = [ + 'Astral Rite of Acne', + 'Create Acne', + 'Cursed Ramen Erruption', + 'Dark Chant of the Dentists', + 'Erruption of Immaturity', + 'Flaming Disc of Inconvenience', + 'Heal Bad Hygene', + 'Heavenly Transfiguration of the Cream Devil', + 'Hellish Cage of Mucus', + 'Irritate Peanut Butter Fairy', + 'Luminous Erruption of Tea', + 'Mystic Spell of the Poser', + 'Sorcerous Enchantment of the Chimneysweep', + 'Steak Sauce Ray', + 'Talk to Groupie', + 'Astonishing Chant of Chocolate', + 'Astounding Pasta Puddle', + 'Ball of Annoyance', + 'Cage of Yarn', + 'Control Noodles Elemental', + 'Create Nervousness', + 'Cure Baldness', + 'Cursed Ritual of Bad Hair', + 'Dispell Piles in Dentist', + 'Eliminate Florists', + 'Illusionary Transfiguration of the Babysitter', + 'Necromantic Armor of Salad Dressing', + 'Occult Transfiguration of Foot Fetish', + 'Protection from Mucus Giant', + 'Tinsel Blast', + 'Alchemical Evocation of the Goths', + 'Call Fangirl', + 'Divine Spell of Crossdressing', + 'Dominate Ramen Giant', + 'Eliminate Vindictiveness in Gym Teacher', + 'Extra-Planar Spell of Irritation', + 'Induce Whining in Babysitter', + 'Invoke Complaining', + 'Magical Enchantment of Arrogance', + 'Occult Globe of Salad Dressing', + 'Overwhelming Enchantment of the Chocolate Fairy', + 'Sorcerous Dandruff Globe', + 'Spiritual Invocation of the Costumers', + 'Ultimate Rite of the Confetti Angel', + 'Ultimate Ritual of Mouthwash', +]; + +module.exports = { + + spellList : function(){ + const levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level']; + + const content = _.map(levels, (level)=>{ + const spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{ + return `- ${spell}`; + }).join('\n'); + return `##### ${level} \n${spells} \n`; + }).join('\n'); + + return `
\n${content}\n
`; + }, + + spell : function(){ + const level = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th']; + const spellSchools = ['abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation']; + + + let components = _.sampleSize(['V', 'S', 'M'], _.random(1, 3)).join(', '); + if(components.indexOf('M') !== -1){ + components += ` (${_.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1, 3)).join(', ')})`; + } + + return [ + `#### ${_.sample(spellNames)}`, + `*${_.sample(level)}-level ${_.sample(spellSchools)}*`, + '___', + '- **Casting Time:** 1 action', + `- **Range:** ${_.sample(['Self', 'Touch', '30 feet', '60 feet'])}`, + `- **Components:** ${components}`, + `- **Duration:** ${_.sample(['Until dispelled', '1 round', 'Instantaneous', 'Concentration, up to 10 minutes', '1 hour'])}`, + '', + 'A flame, equivalent in brightness to a torch, springs from from an object that you touch. ', + 'The effect look like a regular flame, but it creates no heat and doesn\'t use oxygen. ', + 'A *continual flame* can be covered or hidden but not smothered or quenched.', + '\n\n\n' + ].join('\n'); + } +}; \ No newline at end of file diff --git a/client/homebrew/editor/snippetbar/snippetsLegacy/monsterblock.gen.js b/client/homebrew/editor/snippetbar/snippetsLegacy/monsterblock.gen.js new file mode 100644 index 0000000000..1e8a0eebd4 --- /dev/null +++ b/client/homebrew/editor/snippetbar/snippetsLegacy/monsterblock.gen.js @@ -0,0 +1,200 @@ +const _ = require('lodash'); + +const genList = function(list, max){ + return _.sampleSize(list, _.random(0, max)).join(', ') || 'None'; +}; + +const getMonsterName = function(){ + return _.sample([ + 'All-devouring Baseball Imp', + 'All-devouring Gumdrop Wraith', + 'Chocolate Hydra', + 'Devouring Peacock', + 'Economy-sized Colossus of the Lemonade Stand', + 'Ghost Pigeon', + 'Gibbering Duck', + 'Sparklemuffin Peacock Spider', + 'Gum Elemental', + 'Illiterate Construct of the Candy Store', + 'Ineffable Chihuahua', + 'Irritating Death Hamster', + 'Irritating Gold Mouse', + 'Juggernaut Snail', + 'Juggernaut of the Sock Drawer', + 'Koala of the Cosmos', + 'Mad Koala of the West', + 'Milk Djinni of the Lemonade Stand', + 'Mind Ferret', + 'Mystic Salt Spider', + 'Necrotic Halitosis Angel', + 'Pinstriped Famine Sheep', + 'Ritalin Leech', + 'Shocker Kangaroo', + 'Stellar Tennis Juggernaut', + 'Wailing Quail of the Sun', + 'Angel Pigeon', + 'Anime Sphinx', + 'Bored Avalanche Sheep of the Wasteland', + 'Devouring Nougat Sphinx of the Sock Drawer', + 'Djinni of the Footlocker', + 'Ectoplasmic Jazz Devil', + 'Flatuent Angel', + 'Gelatinous Duck of the Dream-Lands', + 'Gelatinous Mouse', + 'Golem of the Footlocker', + 'Lich Wombat', + 'Mechanical Sloth of the Past', + 'Milkshake Succubus', + 'Puffy Bone Peacock of the East', + 'Rainbow Manatee', + 'Rune Parrot', + 'Sand Cow', + 'Sinister Vanilla Dragon', + 'Snail of the North', + 'Spider of the Sewer', + 'Stellar Sawdust Leech', + 'Storm Anteater of Hell', + 'Stupid Spirit of the Brewery', + 'Time Kangaroo', + 'Tomb Poodle', + ]); +}; + +const getType = function(){ + return `${_.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast'])} ${_.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])}`; +}; + +const getAlignment = function(){ + return _.sample([ + 'annoying evil', + 'chaotic gossipy', + 'chaotic sloppy', + 'depressed neutral', + 'lawful bogus', + 'lawful coy', + 'manic-depressive evil', + 'narrow-minded neutral', + 'neutral annoying', + 'neutral ignorant', + 'oedpipal neutral', + 'silly neutral', + 'unoriginal neutral', + 'weird neutral', + 'wordy evil', + 'unaligned' + ]); +}; + +const getStats = function(){ + return `>|${_.times(6, function(){ + const num = _.random(1, 20); + const mod = Math.ceil(num/2 - 5); + return `${num} (${mod >= 0 ? `+${mod}` : mod})`; + }).join('|')}|`; +}; + +const genAbilities = function(){ + return _.sample([ + '> ***Pack Tactics.*** These guys work together. Like super well, you don\'t even know.', + '> ***Fowl Appearance.*** While the creature remains motionless, it is indistinguishable from a normal chicken.', + '> ***Onion Stench.*** Any creatures within 5 feet of this thing develops an irrational craving for onion rings.', + '> ***Enormous Nose.*** This creature gains advantage on any check involving putting things in its nose.', + '> ***Sassiness.*** When questioned, this creature will talk back instead of answering.', + '> ***Big Jerk.*** Thinks he is just *waaaay* better than you.', + ]); +}; + +const genAction = function(){ + const name = _.sample([ + 'Abdominal Drop', + 'Airplane Hammer', + 'Atomic Death Throw', + 'Bulldog Rake', + 'Corkscrew Strike', + 'Crossed Splash', + 'Crossface Suplex', + 'DDT Powerbomb', + 'Dual Cobra Wristlock', + 'Dual Throw', + 'Elbow Hold', + 'Gory Body Sweep', + 'Heel Jawbreaker', + 'Jumping Driver', + 'Open Chin Choke', + 'Scorpion Flurry', + 'Somersault Stump Fists', + 'Suffering Wringer', + 'Super Hip Submission', + 'Super Spin', + 'Team Elbow', + 'Team Foot', + 'Tilt-a-whirl Chin Sleeper', + 'Tilt-a-whirl Eye Takedown', + 'Turnbuckle Roll' + ]); + + return `> ***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `; +}; + + +module.exports = { + + full : function(){ + return `${[ + '___', + '___', + `> ## ${getMonsterName()}`, + `>*${getType()}, ${getAlignment()}*`, + '> ___', + `> - **Armor Class** ${_.random(10, 20)}`, + `> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`, + `> - **Speed** ${_.random(0, 50)}ft.`, + '>___', + '>|STR|DEX|CON|INT|WIS|CHA|', + '>|:---:|:---:|:---:|:---:|:---:|:---:|', + getStats(), + '>___', + `> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`, + `> - **Senses** passive Perception ${_.random(3, 20)}`, + `> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`, + `> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`, + '> ___', + _.times(_.random(3, 6), function(){ + return genAbilities(); + }).join('\n>\n'), + '> ### Actions', + _.times(_.random(4, 6), function(){ + return genAction(); + }).join('\n>\n'), + ].join('\n')}\n\n\n`; + }, + + half : function(){ + return `${[ + '___', + `> ## ${getMonsterName()}`, + `>*${getType()}, ${getAlignment()}*`, + '> ___', + `> - **Armor Class** ${_.random(10, 20)}`, + `> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`, + `> - **Speed** ${_.random(0, 50)}ft.`, + '>___', + '>|STR|DEX|CON|INT|WIS|CHA|', + '>|:---:|:---:|:---:|:---:|:---:|:---:|', + getStats(), + '>___', + `> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`, + `> - **Senses** passive Perception ${_.random(3, 20)}`, + `> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`, + `> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`, + '> ___', + _.times(_.random(2, 3), function(){ + return genAbilities(); + }).join('\n>\n'), + '> ### Actions', + _.times(_.random(1, 2), function(){ + return genAction(); + }).join('\n>\n'), + ].join('\n')}\n\n\n`; + } +}; diff --git a/client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js b/client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js new file mode 100644 index 0000000000..b8410bd9f6 --- /dev/null +++ b/client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js @@ -0,0 +1,268 @@ +/* eslint-disable max-lines */ + +const MagicGen = require('./magic.gen.js'); +const ClassTableGen = require('./classtable.gen.js'); +const MonsterBlockGen = require('./monsterblock.gen.js'); +const ClassFeatureGen = require('./classfeature.gen.js'); +const CoverPageGen = require('./coverpage.gen.js'); +const TableOfContentsGen = require('./tableOfContents.gen.js'); + + +module.exports = [ + + { + groupName : 'Editor', + icon : 'fas fa-pencil-alt', + snippets : [ + { + name : 'Column Break', + icon : 'fas fa-columns', + gen : '```\n```\n\n' + }, + { + name : 'New Page', + icon : 'fas fa-file-alt', + gen : '\\page\n\n' + }, + { + name : 'Vertical Spacing', + icon : 'fas fa-arrows-alt-v', + gen : '
\n\n' + }, + { + name : 'Wide Block', + icon : 'fas fa-arrows-alt-h', + gen : '
\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n
\n' + }, + { + name : 'Image', + icon : 'fas fa-image', + gen : [ + '', + 'Credit: Kyounghwan Kim' + ].join('\n') + }, + { + name : 'Background Image', + icon : 'fas fa-tree', + gen : [ + '' + ].join('\n') + }, + + { + name : 'Page Number', + icon : 'fas fa-bookmark', + gen : '
1
\n
PART 1 | FANCINESS
\n\n' + }, + + { + name : 'Auto-incrementing Page Number', + icon : 'fas fa-sort-numeric-down', + gen : '
\n' + }, + + { + name : 'Link to page', + icon : 'fas fa-link', + gen : '[Click here](#p3) to go to page 3\n' + }, + + { + name : 'Table of Contents', + icon : 'fas fa-book', + gen : TableOfContentsGen + }, + + + ] + }, + + + /************************* PHB ********************/ + + { + groupName : 'PHB', + icon : 'fas fa-book', + snippets : [ + { + name : 'Spell', + icon : 'fas fa-magic', + gen : MagicGen.spell, + }, + { + name : 'Spell List', + icon : 'fas fa-list', + gen : MagicGen.spellList, + }, + { + name : 'Class Feature', + icon : 'fas fa-trophy', + gen : ClassFeatureGen, + }, + { + name : 'Note', + icon : 'fas fa-sticky-note', + gen : function(){ + return [ + '> ##### Time to Drop Knowledge', + '> Use notes to point out some interesting information. ', + '> ', + '> **Tables and lists** both work within a note.' + ].join('\n'); + }, + }, + { + name : 'Descriptive Text Box', + icon : 'far fa-sticky-note', + gen : function(){ + return [ + '
', + '##### Time to Drop Knowledge', + 'Use notes to point out some interesting information. ', + '', + '**Tables and lists** both work within a note.', + '
' + ].join('\n'); + }, + }, + { + name : 'Monster Stat Block', + icon : 'fas fa-bug', + gen : MonsterBlockGen.half, + }, + { + name : 'Wide Monster Stat Block', + icon : 'fas fa-paw', + gen : MonsterBlockGen.full, + }, + { + name : 'Cover Page', + icon : 'far fa-file-word', + gen : CoverPageGen, + }, + ] + }, + + + + /********************* TABLES *********************/ + + { + groupName : 'Tables', + icon : 'fas fa-table', + snippets : [ + { + name : 'Class Table', + icon : 'fas fa-table', + gen : ClassTableGen.full, + }, + { + name : 'Half Class Table', + icon : 'fas fa-list-alt', + gen : ClassTableGen.half, + }, + { + name : 'Table', + icon : 'fas fa-th-list', + gen : function(){ + return [ + '##### Cookie Tastiness', + '| Tastiness | Cookie Type |', + '|:----:|:-------------|', + '| -5 | Raisin |', + '| 8th | Chocolate Chip |', + '| 11th | 2 or lower |', + '| 14th | 3 or lower |', + '| 17th | 4 or lower |\n\n', + ].join('\n'); + }, + }, + { + name : 'Wide Table', + icon : 'fas fa-list', + gen : function(){ + return [ + '
', + '##### Cookie Tastiness', + '| Tastiness | Cookie Type |', + '|:----:|:-------------|', + '| -5 | Raisin |', + '| 8th | Chocolate Chip |', + '| 11th | 2 or lower |', + '| 14th | 3 or lower |', + '| 17th | 4 or lower |', + '
\n\n' + ].join('\n'); + }, + }, + { + name : 'Split Table', + icon : 'fas fa-th-large', + gen : function(){ + return [ + '
', + '| d10 | Damage Type |', + '|:---:|:------------|', + '| 1 | Acid |', + '| 2 | Cold |', + '| 3 | Fire |', + '| 4 | Force |', + '| 5 | Lightning |', + '', + '```', + '```', + '', + '| d10 | Damage Type |', + '|:---:|:------------|', + '| 6 | Necrotic |', + '| 7 | Poison |', + '| 8 | Psychic |', + '| 9 | Radiant |', + '| 10 | Thunder |', + '
\n\n', + ].join('\n'); + }, + } + ] + }, + + + + + /**************** PRINT *************/ + + { + groupName : 'Print', + icon : 'fas fa-print', + snippets : [ + { + name : 'A4 PageSize', + icon : 'far fa-file', + gen : ['' + ].join('\n') + }, + { + name : 'Ink Friendly', + icon : 'fas fa-tint', + gen : ['', + '' + ].join('\n') + }, + ] + }, + +]; diff --git a/client/homebrew/editor/snippetbar/snippetsLegacy/tableOfContents.gen.js b/client/homebrew/editor/snippetbar/snippetsLegacy/tableOfContents.gen.js new file mode 100644 index 0000000000..ca49526d4e --- /dev/null +++ b/client/homebrew/editor/snippetbar/snippetsLegacy/tableOfContents.gen.js @@ -0,0 +1,72 @@ +const _ = require('lodash'); + +const getTOC = (pages)=>{ + const add1 = (title, page)=>{ + res.push({ + title : title, + page : page + 1, + children : [] + }); + }; + const add2 = (title, page)=>{ + if(!_.last(res)) add1('', page); + _.last(res).children.push({ + title : title, + page : page + 1, + children : [] + }); + }; + const add3 = (title, page)=>{ + if(!_.last(res)) add1('', page); + if(!_.last(_.last(res).children)) add2('', page); + _.last(_.last(res).children).children.push({ + title : title, + page : page + 1, + children : [] + }); + }; + + const res = []; + _.each(pages, (page, pageNum)=>{ + const lines = page.split('\n'); + _.each(lines, (line)=>{ + if(_.startsWith(line, '# ')){ + const title = line.replace('# ', ''); + add1(title, pageNum); + } + if(_.startsWith(line, '## ')){ + const title = line.replace('## ', ''); + add2(title, pageNum); + } + if(_.startsWith(line, '### ')){ + const title = line.replace('### ', ''); + add3(title, pageNum); + } + }); + }); + return res; +}; + +module.exports = function(brew){ + const pages = brew.split('\\page'); + const TOC = getTOC(pages); + const markdown = _.reduce(TOC, (r, g1, idx1)=>{ + r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`); + if(g1.children.length){ + _.each(g1.children, (g2, idx2)=>{ + r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`); + if(g2.children.length){ + _.each(g2.children, (g3, idx3)=>{ + r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`); + }); + } + }); + } + return r; + }, []).join('\n'); + + return `
+##### Table Of Contents +${markdown} +
\n`; +}; \ No newline at end of file diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 93ee8f31fd..640a73f770 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -20,6 +20,7 @@ const Homebrew = createClass({ changelog : '', version : '0.0.0', account : null, + enable_v3 : false, brew : { title : '', text : '', @@ -33,6 +34,7 @@ const Homebrew = createClass({ componentWillMount : function() { global.account = this.props.account; global.version = this.props.version; + global.enable_v3 = this.props.enable_v3; }, render : function (){ diff --git a/client/homebrew/navbar/account.navitem.jsx b/client/homebrew/navbar/account.navitem.jsx index 3d36e5bc6e..f40fc92dee 100644 --- a/client/homebrew/navbar/account.navitem.jsx +++ b/client/homebrew/navbar/account.navitem.jsx @@ -20,12 +20,12 @@ const Account = createClass({ render : function(){ if(global.account){ - return + return {global.account.username} ; } - return + return login ; } diff --git a/client/homebrew/navbar/issue.navitem.jsx b/client/homebrew/navbar/issue.navitem.jsx index d0dfd88bba..529744c29f 100644 --- a/client/homebrew/navbar/issue.navitem.jsx +++ b/client/homebrew/navbar/issue.navitem.jsx @@ -6,8 +6,8 @@ module.exports = function(props){ return report issue ; -}; \ No newline at end of file +}; diff --git a/client/homebrew/navbar/patreon.navitem.jsx b/client/homebrew/navbar/patreon.navitem.jsx index 03fb69af4b..555e3a15c4 100644 --- a/client/homebrew/navbar/patreon.navitem.jsx +++ b/client/homebrew/navbar/patreon.navitem.jsx @@ -8,7 +8,7 @@ module.exports = function(props){ newTab={true} href='https://www.patreon.com/NaturalCrit' color='green' - icon='fa-heart'> + icon='fas fa-heart'> help out ; }; diff --git a/client/homebrew/navbar/print.navitem.jsx b/client/homebrew/navbar/print.navitem.jsx index 7d1509a57c..4907cad734 100644 --- a/client/homebrew/navbar/print.navitem.jsx +++ b/client/homebrew/navbar/print.navitem.jsx @@ -3,7 +3,7 @@ const createClass = require('create-react-class'); const Nav = require('naturalcrit/nav/nav.jsx'); module.exports = function(props){ - return + return get PDF ; -}; \ No newline at end of file +}; diff --git a/client/homebrew/navbar/recent.navitem.jsx b/client/homebrew/navbar/recent.navitem.jsx index 5b2895ad74..d123899484 100644 --- a/client/homebrew/navbar/recent.navitem.jsx +++ b/client/homebrew/navbar/recent.navitem.jsx @@ -143,7 +143,7 @@ const RecentItems = createClass({ }, render : function(){ - return this.handleDropdown(true)} onMouseLeave={()=>this.handleDropdown(false)}> {this.props.text} diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index d75b647e6c..4e58e9f1cf 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -41,17 +41,18 @@ const EditPage = createClass({ tags : '', published : false, authors : [], - systems : [] + systems : [], + renderer : 'legacy' } }; }, getInitialState : function() { return { - brew : this.props.brew, - + brew : this.props.brew, isSaving : false, isPending : false, + alertRenderChange : false, saveGoogle : this.props.brew.googleId ? true : false, confirmGoogleTransfer : false, errors : null, @@ -66,6 +67,8 @@ const EditPage = createClass({ url : window.location.href }); + this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy + this.trySave(); window.onbeforeunload = ()=>{ if(this.state.isSaving || this.state.isPending){ @@ -101,6 +104,11 @@ const EditPage = createClass({ }, handleMetadataChange : function(metadata){ + if(metadata.renderer != this.savedBrew.renderer){ + this.setState({ + alertRenderChange : true + }); + } this.setState((prevState)=>({ brew : _.merge({}, prevState.brew, metadata), isPending : true, @@ -122,8 +130,7 @@ const EditPage = createClass({ }, hasChanges : function(){ - const savedBrew = this.savedBrew ? this.savedBrew : this.props.brew; - return !_.isEqual(this.state.brew, savedBrew); + return !_.isEqual(this.state.brew, this.savedBrew); }, trySave : function(){ @@ -142,6 +149,12 @@ const EditPage = createClass({ this.clearErrors(); }, + closeAlerts : function(){ + this.setState({ + alertRenderChange : false + }); + }, + toggleGoogleStorage : function(){ this.setState((prevState)=>({ saveGoogle : !prevState.saveGoogle, @@ -294,7 +307,7 @@ const EditPage = createClass({ } catch (e){} if(this.state.errors.status == '401'){ - return + return Oops!
You must be signed in to a Google account @@ -312,7 +325,7 @@ const EditPage = createClass({ ; } - return + return Oops!
Looks like there was a problem saving.
@@ -325,16 +338,25 @@ const EditPage = createClass({ } if(this.state.isSaving){ - return saving...; + return saving...; } if(this.state.isPending && this.hasChanges()){ - return Save Now; + return Save Now; } if(!this.state.isPending && !this.state.isSaving){ return saved.; } }, + // {this.state.alertRenderChange && + //
+ // Rendering mode for this brew has been changed! Refresh the page to load the new renderer.
+ //
+ // OK + //
+ //
+ // } + processShareId : function() { return this.state.brew.googleId ? this.state.brew.googleId + this.state.brew.shareId : @@ -351,7 +373,7 @@ const EditPage = createClass({ {this.renderGoogleDriveIcon()} {this.renderSaveButton()} - + Share @@ -374,8 +396,9 @@ const EditPage = createClass({ onChange={this.handleTextChange} metadata={this.state.brew} onMetadataChange={this.handleMetadataChange} + renderer={this.state.brew.renderer} /> - +
; diff --git a/client/homebrew/pages/editPage/editPage.less b/client/homebrew/pages/editPage/editPage.less index 381b29fca8..b73467d50c 100644 --- a/client/homebrew/pages/editPage/editPage.less +++ b/client/homebrew/pages/editPage/editPage.less @@ -1,8 +1,14 @@ - +@keyframes glideDown { + 0% {transform : translate(-50% + 3px, 0px); + opacity : 0;} + 100% {transform : translate(-50% + 3px, 10px); + opacity : 1;} +} .editPage{ .navItem.save{ width : 106px; text-align : center; + position : relative; &.saved{ cursor : initial; color : #666; @@ -21,12 +27,15 @@ margin : -5px; } .errorContainer{ + animation-name: glideDown; + animation-duration: 0.4s; position : absolute; top : 100%; left : 50%; - z-index : 1000; + z-index : 100000; width : 140px; padding : 3px; + color : white; background-color : #333; border : 3px solid #444; border-radius : 5px; diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index e245b990b6..d782b8b819 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -55,7 +55,7 @@ const HomePage = createClass({ return - + Changelog @@ -77,11 +77,11 @@ const HomePage = createClass({
- Save current + Save current
- Create your own + Create your own
; } diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 5e491ee0ee..89727cbdb2 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -127,11 +127,11 @@ const NewPage = createClass({ renderSaveButton : function(){ if(this.state.isSaving){ - return + return save... ; } else { - return + return save ; } @@ -143,7 +143,7 @@ const NewPage = createClass({ }, renderLocalPrintButton : function(){ - return + return get PDF ; }, diff --git a/client/homebrew/pages/printPage/printPage.jsx b/client/homebrew/pages/printPage/printPage.jsx index 3ac01b2afb..dee417f743 100644 --- a/client/homebrew/pages/printPage/printPage.jsx +++ b/client/homebrew/pages/printPage/printPage.jsx @@ -4,6 +4,7 @@ const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); const { Meta } = require('vitreum/headtags'); +const MarkdownLegacy = require('naturalcrit/markdownLegacy.js'); const Markdown = require('naturalcrit/markdown.js'); const PrintPage = createClass({ @@ -11,7 +12,8 @@ const PrintPage = createClass({ return { query : {}, brew : { - text : '', + text : '', + renderer : 'legacy' } }; }, @@ -33,13 +35,24 @@ const PrintPage = createClass({ }, renderPages : function(){ - return _.map(this.state.brewText.split('\\page'), (page, index)=>{ - return
; - }); + if(this.props.brew.renderer == 'legacy') { + return _.map(this.state.brewText.split('\\page'), (page, index)=>{ + return
; + }); + } else { + return _.map(this.state.brewText.split(/^\\page/gm), (page, index)=>{ + return
; + }); + } + }, render : function(){ diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index c94473b1da..a1490ed288 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -22,7 +22,8 @@ const SharePage = createClass({ shareId : null, createdAt : null, updatedAt : null, - views : 0 + views : 0, + renderer : '' } }; }, @@ -59,7 +60,7 @@ const SharePage = createClass({ - + source @@ -68,7 +69,7 @@ const SharePage = createClass({
- +
; } diff --git a/client/homebrew/pages/userPage/brewItem/brewItem.jsx b/client/homebrew/pages/userPage/brewItem/brewItem.jsx index bddf246bf4..b7b13ff4c3 100644 --- a/client/homebrew/pages/userPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/userPage/brewItem/brewItem.jsx @@ -48,7 +48,7 @@ const BrewItem = createClass({ if(!this.props.brew.editId) return; return - + ; }, @@ -61,7 +61,7 @@ const BrewItem = createClass({ } return - + ; }, @@ -74,7 +74,7 @@ const BrewItem = createClass({ } return - + ; }, @@ -95,13 +95,13 @@ const BrewItem = createClass({
- {brew.authors.join(', ')} + {brew.authors.join(', ')} - {brew.views} + {brew.views} - {moment(brew.updatedAt).fromNow()} + {moment(brew.updatedAt).fromNow()} {this.renderGoogleDriveIcon()}
diff --git a/client/homebrew/pages/userPage/brewItem/brewItem.less b/client/homebrew/pages/userPage/brewItem/brewItem.less index 9338ff23d0..6ab0a893c2 100644 --- a/client/homebrew/pages/userPage/brewItem/brewItem.less +++ b/client/homebrew/pages/userPage/brewItem/brewItem.less @@ -22,6 +22,9 @@ font-size : 2.2em; } .info{ + position: absolute; + bottom: 0px; + margin-bottom: 4px; font-family : ScalySans; font-size : 1.2em; &>span{ diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index 5081a49424..6697dd20de 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -59,7 +59,7 @@ const UserPage = createClass({ -
+

{this.getUsernameWithS()} brews

diff --git a/client/homebrew/phbStyle/phb.style.less b/client/homebrew/phbStyle/phb.style.less index 95bf5f23b8..792755adea 100644 --- a/client/homebrew/phbStyle/phb.style.less +++ b/client/homebrew/phbStyle/phb.style.less @@ -1,7 +1,6 @@ -@import (less) 'shared/naturalcrit/styles/reset.less'; @import (less) './client/homebrew/phbStyle/phb.fonts.css'; @import (less) './client/homebrew/phbStyle/phb.assets.less'; -@import (less) './client/homebrew/phbStyle/phb.depricated.less'; + //Colors @background : #EEE5CE; @noteGreen : #e0e5c1; @@ -18,12 +17,11 @@ body { } .useSansSerif(){ font-family : ScalySans; + font-size : 10pt; em{ - font-family : ScalySans; font-style : italic; } strong{ - font-family : ScalySans; font-weight : 800; letter-spacing : -0.02em; } @@ -40,7 +38,7 @@ body { -webkit-column-gap : 1cm; -moz-column-gap : 1cm; } -.phb{ +.phb3{ .useColumns(); counter-increment : phb-page-numbers; position : relative; @@ -49,8 +47,7 @@ body { overflow : hidden; height : 279.4mm; width : 215.9mm; - padding : 1.0cm 1.7cm; - padding-bottom : 1.5cm; + padding : 1.0cm 1.7cm 1.5cm; background-color : @background; background-image : @backgroundImage; font-family : BookSanity; @@ -62,6 +59,7 @@ body { // * BASE // *****************************/ p{ + overflow-wrap : break-word; padding-bottom : 0.8em; line-height : 1.3em; &+p{ @@ -124,9 +122,20 @@ body { &+p::first-letter{ float : left; font-family : Solberry; - font-size : 10em; - color : #222; line-height : 0.8em; + font-size: 3.1cm; + padding-top: 4px; + padding-bottom: 4px; + margin-top: -6px; + margin-left: -6px; + background-image: linear-gradient(-45deg, #322814, #998250, #322814); + background-clip: text; + -webkit-background-clip: text; + color: rgba(0, 0, 0, 0); + } + &+p::first-line{ + font-size : .385cm; + font-variant : small-caps; } } h2{ @@ -327,12 +336,12 @@ body { text-indent : -1em; list-style-type : none; } - //Column Break - pre, code{ + .columnSplit { visibility : hidden; -webkit-column-break-after : always; break-after : always; -moz-column-break-after : always; + break-before : column; } //Avoid breaking up p,blockquote,table{ @@ -363,7 +372,7 @@ body { //***************************** // * SPELL LIST // *****************************/ -.phb .spellList{ +.phb3 .spellList{ .useSansSerif(); column-count : 4; column-span : all; @@ -389,7 +398,7 @@ body { //***************************** // * WIDE // *****************************/ -.phb .wide{ +.phb3 .wide{ column-span : all; -webkit-column-span : all; -moz-column-span : all; @@ -397,7 +406,7 @@ body { //***************************** // * CLASS TABLE // *****************************/ -.phb .classTable{ +.phb3 .classTable{ margin-top : 25px; margin-bottom : 40px; border-collapse : separate; @@ -416,7 +425,7 @@ body { //************************************ // * DESCRIPTIVE TEXT BOX // ************************************/ -.phb .descriptive{ +.phb3 .descriptive{ display : block-inline; margin-bottom : 1em; background-color : #faf7ea; @@ -444,13 +453,13 @@ body { letter-spacing : -0.02em; } } -.phb pre+.descriptive{ +.phb3 pre+.descriptive{ margin-top : 8px; } //***************************** // * TABLE OF CONTENTS // *****************************/ -.phb .toc{ +.phb3 .toc{ -webkit-column-break-inside : avoid; page-break-inside : avoid; break-inside : avoid; diff --git a/client/homebrew/phbStyle/phb.styleLegacy.less b/client/homebrew/phbStyle/phb.styleLegacy.less new file mode 100644 index 0000000000..183c89659f --- /dev/null +++ b/client/homebrew/phbStyle/phb.styleLegacy.less @@ -0,0 +1,469 @@ +@import (less) './client/homebrew/phbStyle/phb.fonts.css'; +@import (less) './client/homebrew/phbStyle/phb.assets.less'; +@import (less) './client/homebrew/phbStyle/phb.depricated.less'; +//Colors +@background : #EEE5CE; +@noteGreen : #e0e5c1; +@headerUnderline : #c9ad6a; +@horizontalRule : #9c2b1b; +@headerText : #58180D; +@monsterStatBackground : #FDF1DC; +@page { margin: 0; } +body { + counter-reset : phb-page-numbers; +} +*{ + -webkit-print-color-adjust : exact; +} +.useSansSerif(){ + font-family : ScalySans; + em{ + font-family : ScalySans; + font-style : italic; + } + strong{ + font-family : ScalySans; + font-weight : 800; + letter-spacing : -0.02em; + } +} +.useColumns(@multiplier : 1){ + column-count : 2; + column-fill : auto; + column-gap : 1cm; + column-width : 8cm * @multiplier; + -webkit-column-count : 2; + -moz-column-count : 2; + -webkit-column-width : 8cm * @multiplier; + -moz-column-width : 8cm * @multiplier; + -webkit-column-gap : 1cm; + -moz-column-gap : 1cm; +} +.phb{ + .useColumns(); + counter-increment : phb-page-numbers; + position : relative; + z-index : 15; + box-sizing : border-box; + overflow : hidden; + height : 279.4mm; + width : 215.9mm; + padding : 1.0cm 1.7cm; + padding-bottom : 1.5cm; + background-color : @background; + background-image : @backgroundImage; + font-family : BookSanity; + font-size : 0.317cm; + text-rendering : optimizeLegibility; + page-break-before : always; + page-break-after : always; + //***************************** + // * BASE + // *****************************/ + p{ + padding-bottom : 0.8em; + line-height : 1.3em; + &+p{ + margin-top : -0.8em; + } + } + ul{ + margin-bottom : 0.8em; + padding-left : 1.4em; + line-height : 1.3em; + list-style-position : outside; + list-style-type : disc; + } + ol{ + margin-bottom : 0.8em; + padding-left : 1.4em; + line-height : 1.3em; + list-style-position : outside; + list-style-type : decimal; + } + //Indents after p or lists + p+p, ul+p, ol+p{ + text-indent : 1em; + } + img{ + z-index : -1; + } + strong{ + font-weight : bold; + letter-spacing : 0.03em; + } + em{ + font-style : italic; + } + sup{ + vertical-align : super; + font-size : smaller; + line-height : 0; + } + sub{ + vertical-align : sub; + font-size : smaller; + line-height : 0; + } + //***************************** + // * HEADERS + // *****************************/ + h1,h2,h3,h4{ + margin-top : 0.2em; + margin-bottom : 0.2em; + font-family : MrJeeves; + font-weight : 800; + color : @headerText; + } + h1{ + column-span : all; + font-size : 0.987cm; + -webkit-column-span : all; + -moz-column-span : all; + &+p::first-letter{ + float : left; + font-family : Solberry; + font-size : 10em; + color : #222; + line-height : 0.8em; + } + } + h2{ + font-size : 0.705cm; + } + h3{ + font-size : 0.529cm; + border-bottom : 2px solid @headerUnderline; + } + h4{ + margin-bottom : 0.00em; + font-size : 0.458cm; + } + h5{ + margin-bottom : 0.2em; + font-family : ScalySansSmallCaps; + font-size : 0.423cm; + font-weight : 900; + } + //***************************** + // * TABLE + // *****************************/ + table{ + .useSansSerif(); + width : 100%; + margin-bottom : 1em; + font-size : 10pt; + thead{ + display: table-row-group; + font-weight : 800; + th{ + vertical-align : bottom; + padding-bottom : 0.3em; + padding-right : 0.1em; + padding-left : 0.1em; + } + } + tbody{ + tr{ + td{ + padding : 0.3em 0.1em; + } + &:nth-child(odd){ + background-color : @noteGreen; + } + } + } + } + //***************************** + // * NOTE + // *****************************/ + blockquote{ + .useSansSerif(); + box-sizing : border-box; + margin-bottom : 1em; + padding : 5px 10px; + background-color : @noteGreen; + border-style : solid; + border-width : 11px; + border-image : @noteBorderImage 11; + border-image-outset : 9px 0px; + box-shadow : 1px 4px 14px #888; + p, ul{ + font-size : 0.352cm; + line-height : 1.1em; + } + } + //If a note starts a column, give it space at the top to render border + pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote { + margin-top : 13px; + } + //***************************** + // * MONSTER STAT BLOCK + // *****************************/ + hr+blockquote{ + position : relative; + padding-top : 15px; + background-color : @monsterStatBackground; + border-style : solid; + border-width : 10px; + border-image : @monsterBorderImage 10; + h2{ + margin-top : -8px; + margin-bottom : 0px; + &+p{ + padding-bottom : 0px; + } + } + h3{ + font-family : ScalySans; + font-weight : 400; + border-bottom : 1px solid @headerText; + } + hr+ul{ + color : @headerText; + } + ul{ + .useSansSerif(); + padding-left : 1em; + font-size : 0.352cm; + } + // Monster Ability table + hr+table{ + margin : 0; + column-span : 1; + background-color : transparent; + border-style : none; + border-image : none; + -webkit-column-span : 1; + tbody{ + tr:nth-child(odd), tr:nth-child(even){ + background-color : transparent; + } + } + } + table{ + color : @headerText; + } + p+p{ + margin-top : 0em; + padding-bottom : 0.5em; + text-indent : 0em; + } + //Triangle dividers + hr{ + visibility : visible; + height : 6px; + margin : 4px 0px; + background-image : @redTriangleImage; + background-size : 100% 100%; + border : none; + } + } + //Full Width + hr+hr+blockquote{ + .useColumns(0.96); + } + //***************************** + // * FOOTER + // *****************************/ + &:after{ + content : ""; + position : absolute; + bottom : 0px; + left : 0px; + z-index : 100; + height : 50px; + width : 100%; + background-image : @footerAccentImage; + background-size : cover; + } + &:nth-child(even){ + &:after{ + transform : scaleX(-1); + } + .pageNumber{ + left : 2px; + } + .footnote{ + left : 80px; + text-align : left; + } + } + .pageNumber{ + position : absolute; + right : 2px; + bottom : 22px; + width : 50px; + font-size : 0.9em; + color : #c9ad6a; + text-align : center; + &.auto::after { + content : counter(phb-page-numbers); + } + } + .footnote{ + position : absolute; + right : 80px; + bottom : 32px; + z-index : 150; + width : 200px; + font-size : 0.8em; + color : #c9ad6a; + text-align : right; + } + //***************************** + // * EXTRAS + // *****************************/ + hr{ + visibility : hidden; + margin : 0px; + } + //Modified unorder list, used in spells + hr+ul{ + margin-bottom : 0.5em; + padding-left : 1em; + text-indent : -1em; + list-style-type : none; + } + //Column Break + pre, code{ + visibility : hidden; + -webkit-column-break-after : always; + break-after : always; + -moz-column-break-after : always; + } + //Avoid breaking up + p,blockquote,table{ + z-index : 15; + -webkit-column-break-inside : avoid; + page-break-inside : avoid; + break-inside : avoid; + } + //Better spacing for spell blocks + h4+p+hr+ul{ + margin-top : -0.5em + } + //Text indent right after table + table+p{ + text-indent : 1em; + } + // Nested lists + ul ul,ol ol,ul ol,ol ul{ + margin-bottom : 0px; + margin-left : 1.5em; + } + li{ + -webkit-column-break-inside : avoid; + page-break-inside : avoid; + break-inside : avoid; + } +} +//***************************** +// * SPELL LIST +// *****************************/ +.phb .spellList{ + .useSansSerif(); + column-count : 4; + column-span : all; + -webkit-column-span : all; + -moz-column-span : all; + ul+h5{ + margin-top : 15px; + } + p, ul{ + font-size : 0.352cm; + line-height : 1.3em; + } + ul{ + margin-bottom : 0.5em; + padding-left : 1em; + text-indent : -1em; + list-style-type : none; + -webkit-column-break-inside : auto; + page-break-inside : auto; + break-inside : auto; + } +} +//***************************** +// * WIDE +// *****************************/ +.phb .wide{ + column-span : all; + -webkit-column-span : all; + -moz-column-span : all; +} +//***************************** +// * CLASS TABLE +// *****************************/ +.phb .classTable{ + margin-top : 25px; + margin-bottom : 40px; + border-collapse : separate; + background-color : white; + border : initial; + border-style : solid; + border-image-outset : 25px 17px; + border-image-repeat : stretch; + border-image-slice : 150 200 150 200; + border-image-source : @frameBorderImage; + border-image-width : 47px; + h5{ + margin-bottom : 10px; + } +} +//************************************ +// * DESCRIPTIVE TEXT BOX +// ************************************/ +.phb .descriptive{ + display : block-inline; + margin-bottom : 1em; + background-color : #faf7ea; + font-family : ScalySans; + border-style : solid; + border-width : 7px; + border-image : @descriptiveBoxImage 12 stretch; + border-image-outset : 4px; + box-shadow : 0px 0px 6px #faf7ea; + p{ + display : block; + padding-bottom : 0px; + line-height : 1.5em; + } + p + p { + padding-top : .8em; + } + em { + font-family : ScalySans; + font-style : italic; + } + strong { + font-family : ScalySans; + font-weight : 800; + letter-spacing : -0.02em; + } +} +.phb pre+.descriptive{ + margin-top : 8px; +} +//***************************** +// * TABLE OF CONTENTS +// *****************************/ +.phb .toc{ + -webkit-column-break-inside : avoid; + page-break-inside : avoid; + break-inside : avoid; + a{ + color : black; + text-decoration : none; + &:hover{ + text-decoration : underline; + } + } + ul{ + padding-left : 0; + list-style-type : none; + } + &>ul>li{ + margin-bottom : 10px; + } +} diff --git a/client/template.js b/client/template.js index fec379b9d4..6307b744b7 100644 --- a/client/template.js +++ b/client/template.js @@ -3,7 +3,7 @@ module.exports = async(name, title = '', props = {})=>{ - + @@ -16,4 +16,4 @@ module.exports = async(name, title = '', props = {})=>{ `; -}; \ No newline at end of file +}; diff --git a/package-lock.json b/package-lock.json index daee93bbd3..0a72720bfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4410,7 +4410,12 @@ } }, "marked": { - "version": "0.3.19", + "version": "npm:marked@1.2.7", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.7.tgz", + "integrity": "sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA==" + }, + "markedLegacy": { + "version": "npm:marked@0.3.19", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==" }, diff --git a/package.json b/package.json index 15f1083517..c516cb83c5 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,11 @@ "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.20", - "marked": "^0.3.19", "moment": "^2.29.1", "mongoose": "^5.11.13", "nanoid": "3.1.20", + "markedLegacy": "npm:marked@^0.3.19", + "marked": "npm:marked@^1.2.7", "nconf": "^0.11.1", "prop-types": "15.7.2", "query-string": "6.13.8", diff --git a/scripts/buildHomebrew.js b/scripts/buildHomebrew.js index 76ad07d32b..43acd73185 100644 --- a/scripts/buildHomebrew.js +++ b/scripts/buildHomebrew.js @@ -21,10 +21,16 @@ const build = async ({ bundle, render, ssr })=>{ await fs.outputFile('./build/homebrew/ssr.js', ssr); await fs.outputFile('./build/homebrew/render.js', render); - //compress files - await fs.outputFile('./build/homebrew/bundle.css.br', zlib.brotliCompressSync(css)); - await fs.outputFile('./build/homebrew/bundle.js.br', zlib.brotliCompressSync(bundle)); - await fs.outputFile('./build/homebrew/ssr.js.br', zlib.brotliCompressSync(ssr)); + //compress files in production + if(!isDev){ + await fs.outputFile('./build/homebrew/bundle.css.br', zlib.brotliCompressSync(css)); + await fs.outputFile('./build/homebrew/bundle.js.br', zlib.brotliCompressSync(bundle)); + await fs.outputFile('./build/homebrew/ssr.js.br', zlib.brotliCompressSync(ssr)); + } else { + await fs.remove('./build/homebrew/bundle.css.br'); + await fs.remove('./build/homebrew/bundle.js.br'); + await fs.remove('./build/homebrew/ssr.js.br'); + } }; fs.emptyDirSync('./build/homebrew'); diff --git a/server.js b/server.js index 5528a37628..c149e113a3 100644 --- a/server.js +++ b/server.js @@ -222,6 +222,7 @@ app.use((req, res)=>{ brews : req.brews, googleBrews : req.googleBrews, account : req.account, + enable_v3 : config.get('enable_v3') }; templateFn('homebrew', title = req.brew ? req.brew.title : '', props) .then((page)=>{ res.send(page); }) diff --git a/server/googleActions.js b/server/googleActions.js index 1fc93a8619..1a8d656acd 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -157,6 +157,7 @@ GoogleActions = { lastViewed : brew.lastViewed, views : brew.views, version : brew.version, + renderer : brew.renderer, tags : brew.tags, systems : brew.systems.join() } }, @@ -230,6 +231,7 @@ GoogleActions = { description : brew.description, tags : '', published : brew.published, + renderer : brew.renderer, authors : [], systems : [] }; @@ -291,6 +293,7 @@ GoogleActions = { lastViewed : obj.data.properties.lastViewed, views : parseInt(obj.data.properties.views) || 0, //brews with no view parameter will return undefined version : parseInt(obj.data.properties.version) || 0, + renderer : obj.data.properties.renderer ? obj.data.properties.renderer : 'legacy', gDrive : true, googleId : id diff --git a/server/homebrew.api.js b/server/homebrew.api.js index e934aa8031..e88a3ef886 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -122,8 +122,6 @@ const newGoogleBrew = async (req, res, next)=>{ req.body = brew; - console.log(oAuth2Client); - const newBrew = await GoogleActions.newGoogleBrew(oAuth2Client, brew); return res.status(200).send(newBrew); diff --git a/server/homebrew.model.js b/server/homebrew.model.js index 05a9ab6732..9a4b144275 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -13,6 +13,7 @@ const HomebrewSchema = mongoose.Schema({ description : { type: String, default: '' }, tags : { type: String, default: '' }, systems : [String], + renderer : { type: String, default: '' }, authors : [String], published : { type: Boolean, default: false }, @@ -55,6 +56,8 @@ HomebrewSchema.statics.get = function(query){ unzipped = zlib.inflateRawSync(brews[0].textBin); brews[0].text = unzipped.toString(); } + if(!brews[0].renderer) + brews[0].renderer = 'legacy'; return resolve(brews[0]); }); }); diff --git a/shared/homebrewery/renderWarnings/renderWarnings.jsx b/shared/homebrewery/renderWarnings/renderWarnings.jsx index 4e52fa8166..3fd290260b 100644 --- a/shared/homebrewery/renderWarnings/renderWarnings.jsx +++ b/shared/homebrewery/renderWarnings/renderWarnings.jsx @@ -53,8 +53,8 @@ const RenderWarnings = createClass({ if(_.isEmpty(this.state.warnings)) return null; return
- - + +

Render Warnings

If this homebrew is rendering badly if might be because of the following:
    {_.values(this.state.warnings)}
diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx index e8d9cb69f9..0f881883d2 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -61,16 +61,12 @@ const CodeEditor = createClass({ } }, - componentWillReceiveProps : function(nextProps){ - if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) { - this.codeMirror.setValue(nextProps.value); + componentDidUpdate : function(prevProps) { + if(this.codeMirror && this.codeMirror.getValue() != this.props.value) { + this.codeMirror.setValue(this.props.value); } }, - shouldComponentUpdate : function(nextProps, nextState) { - return false; - }, - setCursorPosition : function(line, char){ setTimeout(()=>{ this.codeMirror.focus(); diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 9dc4fa9c9e..355620d1f6 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ const _ = require('lodash'); const Markdown = require('marked'); const renderer = new Markdown.Renderer(); @@ -10,9 +11,87 @@ renderer.html = function (html) { html = html.substring(0, html.lastIndexOf('
')); return `${openTag} ${Markdown(html)}
`; } + // if(_.startsWith(_.trim(html), '')){ + // const openTag = html.substring(0, html.indexOf('>')+1); + // html = html.substring(html.indexOf('>')+1); + // html = html.substring(0, html.lastIndexOf('')); + // html = html.replaceAll(/\s(\.[^{]*)/gm, '.V3 $1'); + // return `${openTag} ${html} `; + // } return html; }; +// Ensure {{ Divs don't confuse paragraph parsing (else it renders empty paragraphs) +renderer.paragraph = function(text){ + if(text.startsWith('${text}

\n`; +}; + +// Mustache-style Divs {{class \n content ... \n}} +let blockCount = 0; +const blockRegex = /^ *{{(?:="[\w, ]*"|[^"'\s])*$|^ *}}$/gm; +const inlineFullRegex = /{{[^\n]*}}/g; +const inlineRegex = /{{(?:="[\w, ]*"|[^"'\s])*\s*|}}/g; + +renderer.text = function(text){ + const newText = text.replaceAll('"', '"'); + let matches; + + //DIV - BLOCK-LEVEL + if(matches = newText.match(blockRegex)) { + let matchIndex = 0; + const res = _.reduce(newText.split(blockRegex), (r, splitText)=>{ + if(splitText) r.push(Markdown.parseInline(splitText, { renderer: renderer })); + + const block = matches[matchIndex] ? matches[matchIndex].trimLeft() : ''; + if(block && block.startsWith('{')) { + const values = processStyleTags(block.substring(2)); + r.push(`
`); + blockCount++; + } else if(block == '}}' && blockCount !== 0){ + r.push('
'); + blockCount--; + } + + matchIndex++; + + return r; + }, []).join(''); + return res; + } else if(matches = newText.match(inlineFullRegex)) { + + //SPAN - INLINE + matches = newText.match(inlineRegex); + let matchIndex = 0; + const res = _.reduce(newText.split(inlineRegex), (r, splitText)=>{ + + if(splitText) r.push(Markdown.parseInline(splitText, { renderer: renderer })); + + const block = matches[matchIndex] ? matches[matchIndex].trimLeft() : ''; + if(block && block.startsWith('{{')) { + const values = processStyleTags(block.substring(2)); + r.push(`tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; + const classes = _.remove(tags, (tag)=>!tag.includes('"')); + const styles = tags.map((tag)=>tag.replace(/="(.*)"/g, ':$1;')); + return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles ? `style="${styles.join(' ')}"` : ''}`; +}; module.exports = { marked : Markdown, render : (rawBrewText)=>{ + blockCount = 0; + rawBrewText = rawBrewText.replace(/^\\column/gm, `
`) + .replace(/^}}/gm, '\n}}') + .replace(/^({{[^\n]*)$/gm, '$1\n'); return Markdown( sanatizeScriptTags(rawBrewText), { renderer: renderer } diff --git a/shared/naturalcrit/markdownLegacy.js b/shared/naturalcrit/markdownLegacy.js new file mode 100644 index 0000000000..27cf10e9c9 --- /dev/null +++ b/shared/naturalcrit/markdownLegacy.js @@ -0,0 +1,166 @@ +const _ = require('lodash'); +const Markdown = require('markedLegacy'); +const renderer = new Markdown.Renderer(); + +//Processes the markdown within an HTML block if it's just a class-wrapper +renderer.html = function (html) { + if(_.startsWith(_.trim(html), '')){ + const openTag = html.substring(0, html.indexOf('>')+1); + html = html.substring(html.indexOf('>')+1); + html = html.substring(0, html.lastIndexOf('
')); + return `${openTag} ${Markdown(html)}
`; + } + // if(_.startsWith(_.trim(html), '')){ + // const openTag = html.substring(0, html.indexOf('>')+1); + // html = html.substring(html.indexOf('>')+1); + // html = html.substring(0, html.lastIndexOf('')); + // html = html.replaceAll(/\s(\.[^{]*)/gm, '.legacy $1'); + // return `${openTag} ${html} `; + // } + return html; +}; + +renderer.link = function (href, title, text) { + let self = false; + if(href[0] == '#') { + self = true; + } + href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); + + if(href === null) { + return text; + } + let out = `${text}`; + return out; +}; + +const nonWordAndColonTest = /[^\w:]/g; +const cleanUrl = function (sanitize, base, href) { + if(sanitize) { + let prot; + try { + prot = decodeURIComponent(unescape(href)) + .replace(nonWordAndColonTest, '') + .toLowerCase(); + } catch (e) { + return null; + } + if(prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { + return null; + } + } + try { + href = encodeURI(href).replace(/%25/g, '%'); + } catch (e) { + return null; + } + return href; +}; + +const escapeTest = /[&<>"']/; +const escapeReplace = /[&<>"']/g; +const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; +const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; +const escapeReplacements = { + '&' : '&', + '<' : '<', + '>' : '>', + '"' : '"', + '\'' : ''' +}; +const getEscapeReplacement = (ch)=>escapeReplacements[ch]; +const escape = function (html, encode) { + if(encode) { + if(escapeTest.test(html)) { + return html.replace(escapeReplace, getEscapeReplacement); + } + } else { + if(escapeTestNoEncode.test(html)) { + return html.replace(escapeReplaceNoEncode, getEscapeReplacement); + } + } + + return html; +}; + +const sanatizeScriptTags = (content)=>{ + return content + .replace(/