From e2cd7d9f074ff106a107057733e11e971cedd373 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Tue, 2 Feb 2021 20:38:25 -0500 Subject: [PATCH] Legacy renderer (#1184) * Include two versions of Marked.js * Include two versions of Marked.js * Working two different render pipelines Adds stylesheet "styleLegacy.less" Adds markdownHandler "markdownLegacy.js" The BrewRenderer will switch between these and the new pipeline dependent on the "version" prop passed in. * Mustache-style div blocks * Legacy snippets & columnbreak * Codemirror styling for Div Blocks * Lint * Codemirror highlights for inline Divs as well These will turn red `{{class Content}}` Multi-line divs will turn purple ``` {{class,class2 content }} ``` No real need for these to be different colors. Just for testing. * More lint * Update dependencies. * Adding Button to switch render pipelines * Update Marked.js * Popup alert to refresh page when renderer changed * Don't compress files in Development (very slow) * Block DIV or inline Span depending on {{ placement * \column emits a Div instead of Span * Allow share page to use new renderer * {{ divs no longer need empty lines. Spans work in lists. * Typo * Typo * Enforce \page must be at start of line. Code cleanup. * Inject newlines after/before {{/}} to avoid needing blank lines * Fixes issues with tables. * Remove console.log * Fix spacing issue for Spans * Move things from Brewrenderer to Markdown Try to keep all custom text fiddling in one spot. * Rename variables * Update Font-Awesome to v5.15. Fix style issues on popups. * Update {{ Divs/Spans, Fix nested hilighting * Fixed Spans/divs with no tags or just commas * Use blacklist for {{ to allow more characters * Update package-lock.json * Update all icons to Font-awesome 5 * V3 hidden behind config variable Add "globalThis.enable_v3 = true" in the console to enable. * lint --- client/admin/admin.jsx | 2 +- client/admin/brewCleanup/brewCleanup.jsx | 6 +- client/admin/brewCompress/brewCompress.jsx | 6 +- client/admin/brewLookup/brewLookup.jsx | 2 +- client/admin/stats/stats.jsx | 2 +- client/homebrew/brewRenderer/brewRenderer.jsx | 25 +- .../homebrew/brewRenderer/brewRenderer.less | 12 +- .../brewRenderer/brewRendererLegacy.jsx | 152 ++++++ .../brewRenderer/errorBar/errorBar.jsx | 2 +- .../notificationPopup/notificationPopup.jsx | 4 +- client/homebrew/editor/editor.jsx | 60 ++- client/homebrew/editor/editor.less | 18 +- .../editor/metadataEditor/metadataEditor.jsx | 50 +- .../homebrew/editor/snippetbar/snippetbar.jsx | 25 +- .../editor/snippetbar/snippets/snippets.js | 89 ++-- .../snippetsLegacy/classfeature.gen.js | 42 ++ .../snippetsLegacy/classtable.gen.js | 114 +++++ .../snippetsLegacy/coverpage.gen.js | 117 +++++ .../snippetsLegacy/fullclass.gen.js | 43 ++ .../snippetbar/snippetsLegacy/magic.gen.js | 91 ++++ .../snippetsLegacy/monsterblock.gen.js | 200 ++++++++ .../snippetbar/snippetsLegacy/snippets.js | 268 ++++++++++ .../snippetsLegacy/tableOfContents.gen.js | 72 +++ client/homebrew/homebrew.jsx | 2 + client/homebrew/navbar/account.navitem.jsx | 4 +- client/homebrew/navbar/issue.navitem.jsx | 4 +- client/homebrew/navbar/patreon.navitem.jsx | 2 +- client/homebrew/navbar/print.navitem.jsx | 4 +- client/homebrew/navbar/recent.navitem.jsx | 2 +- client/homebrew/pages/editPage/editPage.jsx | 48 +- client/homebrew/pages/editPage/editPage.less | 13 +- client/homebrew/pages/homePage/homePage.jsx | 6 +- client/homebrew/pages/newPage/newPage.jsx | 6 +- client/homebrew/pages/sharePage/sharePage.jsx | 7 +- .../pages/userPage/brewItem/brewItem.jsx | 12 +- .../pages/userPage/brewItem/brewItem.less | 3 + client/homebrew/pages/userPage/userPage.jsx | 2 +- client/homebrew/phbStyle/phb.style.less | 7 +- client/homebrew/phbStyle/phb.styleLegacy.less | 469 ++++++++++++++++++ client/template.js | 4 +- package-lock.json | 7 +- package.json | 3 +- scripts/buildHomebrew.js | 14 +- server.js | 1 + server/homebrew.model.js | 1 + .../renderWarnings/renderWarnings.jsx | 4 +- shared/naturalcrit/markdown.js | 87 +++- shared/naturalcrit/markdownLegacy.js | 159 ++++++ shared/naturalcrit/nav/nav.jsx | 2 +- shared/naturalcrit/splitPane/splitPane.jsx | 6 +- 50 files changed, 2127 insertions(+), 154 deletions(-) create mode 100644 client/homebrew/brewRenderer/brewRendererLegacy.jsx create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/classfeature.gen.js create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/classtable.gen.js create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/coverpage.gen.js create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/fullclass.gen.js create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/magic.gen.js create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/monsterblock.gen.js create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js create mode 100644 client/homebrew/editor/snippetbar/snippetsLegacy/tableOfContents.gen.js create mode 100644 client/homebrew/phbStyle/phb.styleLegacy.less create mode 100644 shared/naturalcrit/markdownLegacy.js diff --git a/client/admin/admin.jsx b/client/admin/admin.jsx index a481c9c14..92e0b2aee 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 55efbb0e1..b55a70bef 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 ad4e0cd8e..c12f430a2 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 c6103bef4..c9212d990 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 d34025a0a..20256f76f 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,16 @@ const PPR_THRESHOLD = 50; const BrewRenderer = createClass({ getDefaultProps : function() { return { - text : '', - errors : [] + text : '', + renderer : '', + errors : [] }; }, getInitialState : function() { - const pages = this.props.text.split('\\page'); + const pages = this.props.text.split(/^\\page/gm); + let renderer = 'legacy'; + if(this.props.renderer) + renderer = this.props.renderer; return { viewablePageNumber : 0, @@ -33,8 +38,9 @@ const BrewRenderer = createClass({ pages : pages, usePPR : pages.length >= PPR_THRESHOLD, visibility : 'hidden', + renderer : renderer, initialContent : ` - + @@ -49,7 +55,7 @@ const BrewRenderer = createClass({ }, componentWillReceiveProps : function(nextProps) { - const pages = nextProps.text.split('\\page'); + const pages = nextProps.text.split(/^\\page/gm); this.setState({ pages : pages, usePPR : pages.length >= PPR_THRESHOLD @@ -103,12 +109,15 @@ const BrewRenderer = createClass({ renderDummyPage : function(index){ return
- +
; }, renderPage : function(pageText, index){ - return
; + if(this.state.renderer == 'legacy') + return
; + else + return
; }, renderPages : function(){ @@ -159,7 +168,7 @@ const BrewRenderer = createClass({ : null} -
diff --git a/client/homebrew/brewRenderer/brewRenderer.less b/client/homebrew/brewRenderer/brewRenderer.less index 6dee6433f..6fb6f8497 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'; +.V3 {@import (multiple, less) './client/homebrew/phbStyle/phb.style.less';} +.legacy {@import (multiple, less) './client/homebrew/phbStyle/phb.styleLegacy.less';} -@import (less) './client/homebrew/phbStyle/phb.style.less'; -.pane{ - position : relative; -} .brewRenderer{ will-change : transform; overflow-y : scroll; @@ -16,6 +15,9 @@ } } } +.pane{ + position : relative; +} .pageInfo{ position : absolute; right : 17px; @@ -37,4 +39,4 @@ font-size : 10px; font-weight : 800; color : white; -} \ No newline at end of file +} diff --git a/client/homebrew/brewRenderer/brewRendererLegacy.jsx b/client/homebrew/brewRenderer/brewRendererLegacy.jsx new file mode 100644 index 000000000..70fa593dd --- /dev/null +++ b/client/homebrew/brewRenderer/brewRendererLegacy.jsx @@ -0,0 +1,152 @@ +const React = require('react'); +const createClass = require('create-react-class'); +const _ = require('lodash'); +const cx = require('classnames'); + +const Markdown = require('naturalcrit/markdown.js'); +const ErrorBar = require('./errorBar/errorBar.jsx'); + +//TODO: move to the brew renderer +const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx'); +const NotificationPopup = require('./notificationPopup/notificationPopup.jsx'); + +const PAGE_HEIGHT = 1056; +const PPR_THRESHOLD = 50; + +const BrewRenderer = createClass({ + getDefaultProps : function() { + return { + text : '', + errors : [] + }; + }, + getInitialState : function() { + const pages = this.props.text.split('\\page'); + + return { + viewablePageNumber : 0, + height : 0, + isMounted : false, + + pages : pages, + usePPR : pages.length >= PPR_THRESHOLD, + }; + }, + height : 0, + lastRender :
, + + componentDidMount : function() { + this.updateSize(); + window.addEventListener('resize', this.updateSize); + }, + componentWillUnmount : function() { + window.removeEventListener('resize', this.updateSize); + }, + + componentWillReceiveProps : function(nextProps) { + const pages = nextProps.text.split('\\page'); + this.setState({ + pages : pages, + usePPR : pages.length >= PPR_THRESHOLD + }); + }, + + updateSize : function() { + this.setState({ + height : this.refs.main.parentNode.clientHeight, + isMounted : true + }); + }, + + handleScroll : function(e){ + const target = e.target; + this.setState((prevState)=>({ + viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * prevState.pages.length) + })); + }, + + shouldRender : function(pageText, index){ + if(!this.state.isMounted) return false; + + const viewIndex = this.state.viewablePageNumber; + if(index == viewIndex - 3) return true; + if(index == viewIndex - 2) return true; + if(index == viewIndex - 1) return true; + if(index == viewIndex) return true; + if(index == viewIndex + 1) return true; + if(index == viewIndex + 2) return true; + if(index == viewIndex + 3) return true; + + //Check for style tages + if(pageText.indexOf(' + +
+ +# ${_.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 000000000..5ede9e501 --- /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 000000000..ed17f8692 --- /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 000000000..1e8a0eebd --- /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 000000000..b8410bd9f --- /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 000000000..ca49526d4 --- /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 93ee8f31f..640a73f77 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 3d36e5bc6..f40fc92de 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 d0dfd88bb..529744c29 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 03fb69af4..555e3a15c 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 7d1509a57..4907cad73 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 5b2895ad7..d12389948 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 d75b647e6..c8d5b6920 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 : '' } }; }, 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,12 @@ const EditPage = createClass({ }, handleMetadataChange : function(metadata){ + if(metadata.renderer != this.savedBrew.renderer){ + console.log('renderer changed!'); + this.setState({ + alertRenderChange : true + }); + } this.setState((prevState)=>({ brew : _.merge({}, prevState.brew, metadata), isPending : true, @@ -122,8 +131,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 +150,12 @@ const EditPage = createClass({ this.clearErrors(); }, + closeAlerts : function(){ + this.setState({ + alertRenderChange : false + }); + }, + toggleGoogleStorage : function(){ this.setState((prevState)=>({ saveGoogle : !prevState.saveGoogle, @@ -294,7 +308,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 +326,7 @@ const EditPage = createClass({ ; } - return + return Oops!
Looks like there was a problem saving.
@@ -325,13 +339,22 @@ 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.; + return saved. + {this.state.alertRenderChange && +
+ Rendering mode for this brew has been changed! Refresh the page to load the new renderer.
+
+ OK +
+
+ } +
; } }, @@ -351,7 +374,7 @@ const EditPage = createClass({ {this.renderGoogleDriveIcon()} {this.renderSaveButton()} - + Share @@ -374,8 +397,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 381b29fca..b73467d50 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 e245b990b..d782b8b81 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 5e491ee0e..89727cbdb 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/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index c94473b1d..a1490ed28 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 bddf246bf..b7b13ff4c 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 9338ff23d..6ab0a893c 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 5081a4942..6697dd20d 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 95bf5f23b..4a3b632e2 100644 --- a/client/homebrew/phbStyle/phb.style.less +++ b/client/homebrew/phbStyle/phb.style.less @@ -1,10 +1,9 @@ -@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; +@noteGreen : #FF0000; @headerUnderline : #c9ad6a; @horizontalRule : #9c2b1b; @headerText : #58180D; @@ -327,12 +326,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{ diff --git a/client/homebrew/phbStyle/phb.styleLegacy.less b/client/homebrew/phbStyle/phb.styleLegacy.less new file mode 100644 index 000000000..183c89659 --- /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 fec379b9d..6307b744b 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 daee93bbd..0a72720bf 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 15f108351..c516cb83c 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 76ad07d32..43acd7318 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 5528a3762..c149e113a 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/homebrew.model.js b/server/homebrew.model.js index 05a9ab673..1eeb4409b 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 }, diff --git a/shared/homebrewery/renderWarnings/renderWarnings.jsx b/shared/homebrewery/renderWarnings/renderWarnings.jsx index 4e52fa816..3fd290260 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/markdown.js b/shared/naturalcrit/markdown.js index 9dc4fa9c9..5c8a1e85e 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(); @@ -13,6 +14,77 @@ renderer.html = function (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 000000000..454f5b1da --- /dev/null +++ b/shared/naturalcrit/markdownLegacy.js @@ -0,0 +1,159 @@ +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)}
`; + } + 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(/