0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-27 16:03:07 +00:00

Compare commits

..

14 Commits

Author SHA1 Message Date
Trevor Buckner
d3359991f0 Merge branch 'PRODUCTION' of https://github.com/naturalcrit/homebrewery into PRODUCTION 2021-09-18 00:39:20 -04:00
Trevor Buckner
c5dca338e1 Merge branch 'PRODUCTION' of https://github.com/naturalcrit/homebrewery into PRODUCTION 2021-09-11 00:39:55 -04:00
Trevor Buckner
304825a9dd Merge branch 'PRODUCTION' of https://github.com/naturalcrit/homebrewery into PRODUCTION 2021-09-09 23:06:21 -04:00
Trevor Buckner
5da1c2e754 Merge branch 'PRODUCTION' of https://github.com/naturalcrit/homebrewery into PRODUCTION 2021-08-17 23:34:42 -04:00
Trevor Buckner
7221d693c6 Popup error when gDrive credentials are expired on both /edit and /new 2021-08-11 15:40:05 -04:00
Trevor Buckner
8af6a04c58 Merge branch 'PRODUCTION' of https://github.com/naturalcrit/homebrewery into PRODUCTION 2021-08-09 22:54:23 -04:00
Trevor Buckner
ac1fdb8474 Merge pull request #1451 from naturalcrit/master
Backing up live version before v3 beta opens
2021-07-24 22:14:25 -04:00
Trevor Buckner
b7f287db82 Merge commit '2661e2cf' into PRODUCTION 2021-07-12 21:44:17 -04:00
Trevor Buckner
7090c33a9d Merge pull request #1385 from naturalcrit/master
v2.12.0
2021-06-20 13:49:58 -04:00
Trevor Buckner
1e4aa4b3a7 Merge pull request #1342 from naturalcrit/master
v2.11.2
2021-05-02 22:15:43 -04:00
Trevor Buckner
7c92aae61b Merge pull request #1292 from naturalcrit/master
V2.11.1
2021-03-20 23:59:32 -04:00
Trevor Buckner
522fcda547 Merge pull request #1280 from naturalcrit/master
Send v2.11.0 live
2021-03-13 21:53:49 -05:00
Trevor Buckner
63ed68b527 Merge pull request #1266 from naturalcrit/master
Sync master & Production
2021-03-01 15:50:56 -05:00
Trevor Buckner
e3f9ef0117 v2.11.0 (#1265)
* 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

* Legacy renderer (#1229)

* 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

* Give user styles higher priority to still allow overrides

* Apply style priority to *all* user styles

* Change .legacy .v3 to .phb, .phb3

* Revert accidental color change

* Fix brew styles overwriting each other. (#1230)

* Fix /page not working in legacy mode. (#1233)

* Fix brew styles overwriting each other.

* Word wrapping, start fixing spacing on Title letter

* Fix \page in legacy brews when not at line start

* Default 'legacy' if not set. Auto-change styles.

* Fix brew styles overwriting each other.

* Word wrapping, start fixing spacing on Title letter

* Fix \page in legacy brews when not at line start

* Fix Page Padding

* Set 'legacy' as default value if not set in brew saved file.

* Apply Legacy\v3 renderer to print page (#1235)

* Update robots.txt (#1239)

* Enable caching of static assets (#1217)

* Enable caching of static assets

* Remove dependency on mime package

Since we only care about two file extensions at the moment,
there is no need to grab the whole package just to avoid
calling 'endsWith' twice.

* Add QR-Code as snippet under Editor (#539)

* Add snippet for QR-code

* Add snippet for QR-code

* Refactor to expose metadata to snippets

* Lint

Co-authored-by: Rasmus Bækgaard <git@bakgaard.net>
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>

* Unify brew structure in all pages

* Implementing magic item snippet from Issue 671. (#842)

* Implementing magic item snippet from Issue 671.

* Fixes syntax errors. Function moved into existing magic module.

* Implementing magic item snippet from Issue 671.

* Fixes syntax errors. Function moved into existing magic module.

* Magic Item Snippet, <dl>, `:` for blank line

Co-authored-by: Trevor Buckner <calculuschild@gmail.com>

* Bump codemirror from 5.59.2 to 5.59.4 (#1258)

Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.59.2 to 5.59.4.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.59.2...5.59.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump query-string from 6.13.8 to 6.14.0 (#1236)

Bumps [query-string](https://github.com/sindresorhus/query-string) from 6.13.8 to 6.14.0.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v6.13.8...v6.14.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump @babel/preset-env from 7.12.11 to 7.13.5 (#1257)

Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.12.11 to 7.13.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.13.5/packages/babel-preset-env)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump lodash from 4.17.20 to 4.17.21 (#1252)

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump mongoose from 5.11.13 to 5.11.18 (#1256)

Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.11.13 to 5.11.18.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.11.13...5.11.18)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump googleapis from 67.0.0 to 67.1.0 (#1245)

Bumps [googleapis](https://github.com/googleapis/google-api-nodejs-client) from 67.0.0 to 67.1.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/master/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/v67.0.0...v67.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump eslint from 7.18.0 to 7.20.0 (#1244)

Bumps [eslint](https://github.com/eslint/eslint) from 7.18.0 to 7.20.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.18.0...v7.20.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Update Marked.js version

* Bump @babel/preset-react from 7.12.10 to 7.12.13 (#1225)

Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.12.10 to 7.12.13.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.12.13/packages/babel-preset-react)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump @babel/core from 7.12.10 to 7.13.1 (#1254)

Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.12.10 to 7.13.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.13.1/packages/babel-core)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Bump nconf from 0.11.1 to 0.11.2 (#1216)

Bumps [nconf](https://github.com/flatiron/nconf) from 0.11.1 to 0.11.2.
- [Release notes](https://github.com/flatiron/nconf/releases)
- [Changelog](https://github.com/indexzero/nconf/blob/master/CHANGELOG.md)
- [Commits](https://github.com/flatiron/nconf/compare/v0.11.1...v0.11.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Fix title issue (#1251)

* Maximum title length set to 100 characters.

* Reverse unnecessary change that was incorrectly included in previous commit.

* Reduced code change to one addition on a single line.

* Revert "Reduced code change to one addition on a single line."

This reverts commit 2a355cf115.

* Use newer syntax to shorten

Co-authored-by: Trevor Buckner <calculuschild@gmail.com>

* Updated extraKeys (bold and italic) and added new shortcut (for span tags) (#1191)

* Updated extraKeys (bold and italic) and added new shortcut (for span)

* Updated makeSpan shortcut to Ctrl/Cmd-M

* ESLint

* Space after {{ so text appears

Co-authored-by: Trevor Buckner <calculuschild@gmail.com>

Co-authored-by: G.Ambatte <sean@robertson-family.nz>
Co-authored-by: Alexey Sachkov <sachkov2011@gmail.com>
Co-authored-by: Rasmus Bækgaard <rasmus@bakgaard.net>
Co-authored-by: Rasmus Bækgaard <git@bakgaard.net>
Co-authored-by: Christian Brickhouse <chrisbrickhouse@users.noreply.github.com>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Rodrigo Kuerten <30464993+RKuerten@users.noreply.github.com>
2021-03-01 15:34:36 -05:00
48 changed files with 2955 additions and 3816 deletions

View File

@@ -6,8 +6,8 @@ version: 2
jobs: jobs:
build: build:
docker: docker:
- image: circleci/node:16.10.0 - image: circleci/node:12.16.3
- image: circleci/mongo:4.4 - image: circleci/mongo:3.4-jessie
working_directory: ~/repo working_directory: ~/repo

View File

@@ -1,5 +1,4 @@
FROM node:16.11-alpine FROM node:14.15
RUN apk --no-cache add git
ENV NODE_ENV=docker ENV NODE_ENV=docker

View File

@@ -3,12 +3,11 @@ h5 {
font-size: .35cm !important; font-size: .35cm !important;
} }
.page ul ul { .taskList li {
margin-left: 0px; list-style-type : none;
} }
.taskList li input { .taskList li input {
list-style-type : none;
margin-left : -0.52cm; margin-left : -0.52cm;
transform: translateY(.05cm); transform: translateY(.05cm);
filter: brightness(1.1) drop-shadow(1px 2px 1px #222); filter: brightness(1.1) drop-shadow(1px 2px 1px #222);
@@ -31,173 +30,7 @@ pre {
} }
``` ```
## changelog # changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Saturday 18/12/2021 - v3.0.6
{{taskList
* [x] Fixed text wrapping for long strings in code blocks.
Fixes issues: [#1736](https://github.com/naturalcrit/homebrewery/issues/1736)
* [x] Code search/replace `CTRL F / CTRL SHIFT F`
Fixes issues: [#1201](https://github.com/naturalcrit/homebrewery/issues/1201)
* [x] Auto-closing HTML tags and curly braces `{{ }}`
* [x] Highlight current active line
Fixes issues: [#1202](https://github.com/naturalcrit/homebrewery/issues/1202)
* [x] Display tabs and trailing spaces
Fixes issues: [#1622](https://github.com/naturalcrit/homebrewery/issues/1622)
* [x] Make columns even in V3 Table of Contents.
Fixes issues: [#1671](https://github.com/naturalcrit/homebrewery/issues/1671)
* [x] Fix `CTRL P` failing to print from `/new` pages.
Fixes issues: [#1815](https://github.com/naturalcrit/homebrewery/issues/1815)
}}
\page
### Tuesday 07/12/2021 - v3.0.5
{{taskList
* [x] Fixed paragraph spacing for **note** and **descriptive** boxes in V3.
Fixes issues: [#1836](https://github.com/naturalcrit/homebrewery/issues/1836)
* [x] Added a whole bunch of hotkeys:
* Page Break `CTRL + ENTER`
* Column Break `CTRL + SHIFT + ENTER`
* Bulleted Lists `CTRL + L`
* Numbered Lists `CTRL + SHIFT + L`
* Headers `CTRL + SHIFT + (1-6)`
* Underline `CTRL + U`
* Link `CTRL + K`
* Non-breaking space (\&nbsp;) `CTRL + .`
* Add Horizontal Space `CTRL + SHIFT + .`
* Remove Horizontal Space `CTRL + SHIFT + ,`
* Curly Span `CTRL + M`
* Curly Div `CTRL + SHIFT + M`
* [x] Fixed page numbers in the editor panel getting scrambled when scrolling up and down.
* [x] Faster swapping between tabs on long brews.
* [x] Better error messages for common issue with Google Drive credentials expiring.
}}
### Wednesday 17/11/2021 - v3.0.4
{{taskList
* [x] Fixed incorrect sorting of Google brews by page count and views on the user page.
Fixes issues: [#1793](https://github.com/naturalcrit/homebrewery/issues/1793)
* [x] Added code folding! Only on a page-level for now. Hotkeys `CTRL + [` and `CTRL + ]` to fold/unfold all pages. (Thanks jeddai, new contributor!)
Fixes issues: [#629](https://github.com/naturalcrit/homebrewery/issues/629)
* [x] Fixed rendering issues due to the latest Chrome update to version 96. (Also thanks to jeddai!)
Fixes issues: [#1828](https://github.com/naturalcrit/homebrewery/issues/1828)
}}
### Wednesday 27/10/2021 - v3.0.3
{{taskList
* [x] Moved **Post To Reddit** button from {{fa,fa-info-circle}} **Properties** menu to the **SHARE** {{fa,fa-share-alt}} button as a dropdown.
* [x] Added a **Copy URL** button to the **SHARE** {{fa,fa-share-alt}} button as a dropdown.
* [x] Fixed pages being printed directly from `/new` not recognizing the V3 renderer.
Fixes issues: [#1702](https://github.com/naturalcrit/homebrewery/issues/1702)
* [x] Updated links to [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) on home page.
Fixes issues: [#1744](https://github.com/naturalcrit/homebrewery/issues/1744)
* [x] Added a [FAQ page](https://homebrewery.naturalcrit.com/faq).
Fixes issues: [#810](https://github.com/naturalcrit/homebrewery/issues/810)
* [x] Added {{fa,fa-undo}} **Undo** and {{fa,fa-redo}} **Redo** buttons to the snippet bar.
}}
\column
{{taskList
* [x] Switching between the {{fa,fa-beer}} **Brew** and {{fa,fa-paint-brush}} **Style** tabs no longer loses your scroll position or undo history.
Fixes issues: [#1735](https://github.com/naturalcrit/homebrewery/issues/1735)
* [x] Divider bar between editor and preview panels can no longer be dragged off the edge of the screen.
Fixes issues: [#1674](https://github.com/naturalcrit/homebrewery/issues/1674)
}}
### Wednesday 06/10/2021 - v3.0.2
{{taskList
* [x] Fixed V3 **EDITOR → QR Code** snippet not working on `/new` (unsaved) pages.
Fixes issues: [#1710](https://github.com/naturalcrit/homebrewery/issues/1710)
* [x] Reorganized several snippets from the **Brew Editor** panel into the **Style Editor** panel.
Fixes issues: [Reported on Reddit](https://www.reddit.com/r/homebrewery/comments/pm6ki7/two_version_of_class_features_making_it_look_more/)
* [x] Added a page counter to the right of each `\page` line in V3 to help navigate your brews. Starts counting from page 2.
Fixes issues: [#846](https://github.com/naturalcrit/homebrewery/issues/846)
* [x] Moved the changelog to be accessible by clicking on the Homebrewery version number.
Fixes issues: [#1166](https://github.com/naturalcrit/homebrewery/issues/1166)
}}
### Friday, 17/09/2021 - v3.0.1
{{taskList
* [x] Updated V3 **PHB → Class Feature** snippet to use V3 syntax.
Fixes issues: [Reported on Reddit](https://www.reddit.com/r/homebrewery/comments/pm6ki7/two_version_of_class_features_making_it_look_more/)
* [x] Improved V3 **PHB → Monster Stat Block** snippet and styling to allow for easier control of paragraph indentation in the Abilities text.
Fixes issues: [#181](https://github.com/naturalcrit/homebrewery/issues/181)
* [x] Improved Legacy **TABLES → Split Table** snippet by removing unneeded column-break backticks.
Fixes issues: [#844](https://github.com/naturalcrit/homebrewery/issues/844)
* [x] Changed block elements to use CSS `width` instead of `min-width`. This should make custom styles behave more predictably when trying to resize items.
Fixes issues: [Reported on Reddit](https://www.reddit.com/r/homebrewery/comments/pohoy3/looking_for_help_with_basic_stuff_in_v3/)
* [x] Fixed Partial Page Rendering in V3 for large brews
Fixes issues: [Reported on Reddit](https://www.reddit.com/r/homebrewery/comments/pori3a/weird_behaviour_of_the_brew_after_page_50/)
* [x] Fixed HTML validation to handle tags starting with 'a', as in `<aside>`.
Fixes issues: [#230](https://github.com/naturalcrit/homebrewery/issues/230)
* [x] Fixed page footers switching side when printing.
Fixes issues: [#1612](https://github.com/naturalcrit/homebrewery/issues/1612)
}}
\page
### Saturday, 11/09/2021 - v3.0.0 ### Saturday, 11/09/2021 - v3.0.0

View File

@@ -117,7 +117,7 @@ const BrewRenderer = createClass({
}, },
renderDummyPage : function(index){ renderDummyPage : function(index){
return <div className='phb page' id={`p${index + 1}`} key={index}> return <div className='phb' id={`p${index + 1}`} key={index}>
<i className='fas fa-spinner fa-spin' /> <i className='fas fa-spinner fa-spin' />
</div>; </div>;
}, },
@@ -188,6 +188,7 @@ const BrewRenderer = createClass({
: null} : null}
<Frame initialContent={this.state.initialContent} <Frame initialContent={this.state.initialContent}
head = <link href={`${this.props.renderer == 'legacy' ? '/themes/5ePhbLegacy.style.css' : '/themes/5ePhb.style.css'}`} rel='stylesheet'/>
style={{ width: '100%', height: '100%', visibility: this.state.visibility }} style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
contentDidMount={this.frameDidMount}> contentDidMount={this.frameDidMount}>
<div className={'brewRenderer'} <div className={'brewRenderer'}
@@ -199,17 +200,17 @@ const BrewRenderer = createClass({
<RenderWarnings /> <RenderWarnings />
<NotificationPopup /> <NotificationPopup />
</div> </div>
<link href={`${this.props.renderer == 'legacy' ? '/themes/5ePhbLegacy.style.css' : '/themes/5ePhb.style.css'}`} rel='stylesheet'/>
{/* Apply CSS from Style tab and render pages from Markdown tab */} <div className='pages' ref='pages'>
{this.state.isMounted {/* Apply CSS from Style tab and render pages from Markdown tab */}
&& {this.state.isMounted
<> &&
{this.renderStyle()} <>
<div className='pages' ref='pages'> {this.renderStyle()}
{this.renderPages()} {this.renderPages()}
</div> </>
</> }
} </div>
</div> </div>
</Frame> </Frame>
{this.renderPageInfo()} {this.renderPageInfo()}

View File

@@ -1,4 +1,3 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./editor.less'); require('./editor.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
@@ -60,10 +59,6 @@ const Editor = createClass({
window.removeEventListener('resize', this.updateEditorSize); window.removeEventListener('resize', this.updateEditorSize);
}, },
componentDidUpdate : function() {
this.highlightCustomMarkdown();
},
updateEditorSize : function() { updateEditorSize : function() {
if(this.refs.codeEditor) { if(this.refs.codeEditor) {
let paneHeight = this.refs.main.parentNode.clientHeight; let paneHeight = this.refs.main.parentNode.clientHeight;
@@ -107,69 +102,67 @@ const Editor = createClass({
if(this.state.view === 'text') { if(this.state.view === 'text') {
const codeMirror = this.refs.codeEditor.codeMirror; const codeMirror = this.refs.codeEditor.codeMirror;
codeMirror.operation(()=>{ // Batch CodeMirror styling //reset custom text styles
//reset custom text styles const customHighlights = codeMirror.getAllMarks();
const customHighlights = codeMirror.getAllMarks().filter((mark)=>!mark.__isFold); //Don't undo code folding for (let i=0;i<customHighlights.length;i++) customHighlights[i].clear();
for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear();
let editorPageCount = 2; // start page count from page 2 const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{
_.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{ //reset custom line styles
codeMirror.removeLineClass(lineNumber, 'background');
codeMirror.removeLineClass(lineNumber, 'text');
//reset custom line styles // Legacy Codemirror styling
codeMirror.removeLineClass(lineNumber, 'background', 'pageLine'); if(this.props.renderer == 'legacy') {
codeMirror.removeLineClass(lineNumber, 'text'); if(line.includes('\\page')){
// Styling for \page breaks
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
(this.props.renderer == 'V3' && line.match(/^\\page$/))) {
// add back the original class 'background' but also add the new class '.pageline'
codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
const pageCountElement = Object.assign(document.createElement('span'), { r.push(lineNumber);
className : 'editor-page-count',
textContent : editorPageCount
});
codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement);
editorPageCount += 1;
};
// New Codemirror styling for V3 renderer
if(this.props.renderer == 'V3') {
if(line.match(/^\\column$/)){
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
}
// Highlight inline spans {{content}}
if(line.includes('{{') && line.includes('}}')){
const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
let match;
let blockCount = 0;
while ((match = regex.exec(line)) != null) {
if(match[0].startsWith('{')) {
blockCount += 1;
} else {
blockCount -= 1;
}
if(blockCount < 0) {
blockCount = 0;
continue;
}
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
}
} else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){
// Highlight block divs {{\n Content \n}}
let endCh = line.length+1;
const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/);
if(match)
endCh = match.index+match[0].length;
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
}
} }
}); }
});
// New Codemirror styling for V3 renderer
if(this.props.renderer == 'V3') {
if(line.match(/^\\page$/)){
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
r.push(lineNumber);
}
if(line.match(/^\\column$/)){
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
r.push(lineNumber);
}
// Highlight inline spans {{content}}
if(line.includes('{{') && line.includes('}}')){
const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
let match;
let blockCount = 0;
while ((match = regex.exec(line)) != null) {
if(match[0].startsWith('{')) {
blockCount += 1;
} else {
blockCount -= 1;
}
if(blockCount < 0) {
blockCount = 0;
continue;
}
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
}
} else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){
// Highlight block divs {{\n Content \n}}
let endCh = line.length+1;
const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/);
if(match)
endCh = match.index+match[0].length;
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
}
}
return r;
}, []);
return lineNumbers;
} }
}, },
@@ -183,60 +176,30 @@ const Editor = createClass({
this.refs.codeEditor?.updateSize(); this.refs.codeEditor?.updateSize();
}, },
//Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory
rerenderParent : function (){
this.forceUpdate();
},
renderEditor : function(){ renderEditor : function(){
if(this.isText()){ if(this.isText()){
return <> return <CodeEditor key='text'
<CodeEditor key='codeEditor' ref='codeEditor'
ref='codeEditor' language='gfm'
language='gfm' value={this.props.brew.text}
view={this.state.view} onChange={this.props.onTextChange} />;
value={this.props.brew.text}
onChange={this.props.onTextChange}
rerenderParent={this.rerenderParent} />
</>;
} }
if(this.isStyle()){ if(this.isStyle()){
return <> return <CodeEditor key='style'
<CodeEditor key='codeEditor' ref='codeEditor'
ref='codeEditor' language='css'
language='css' value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
view={this.state.view} onChange={this.props.onStyleChange} />;
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
onChange={this.props.onStyleChange}
rerenderParent={this.rerenderParent} />
</>;
} }
if(this.isMeta()){ if(this.isMeta()){
return <> return <MetadataEditor
<CodeEditor key='codeEditor' metadata={this.props.brew}
view={this.state.view} onChange={this.props.onMetaChange} />;
style={{ display: 'none' }}
rerenderParent={this.rerenderParent} />
<MetadataEditor
metadata={this.props.brew}
onChange={this.props.onMetaChange} />
</>;
} }
}, },
redo : function(){
return this.refs.codeEditor?.redo();
},
historySize : function(){
return this.refs.codeEditor?.historySize();
},
undo : function(){
return this.refs.codeEditor?.undo();
},
render : function(){ render : function(){
this.highlightCustomMarkdown();
return ( return (
<div className='editor' ref='main'> <div className='editor' ref='main'>
<SnippetBar <SnippetBar
@@ -245,10 +208,7 @@ const Editor = createClass({
onViewChange={this.handleViewChange} onViewChange={this.handleViewChange}
onInject={this.handleInject} onInject={this.handleInject}
showEditButtons={this.props.showEditButtons} showEditButtons={this.props.showEditButtons}
renderer={this.props.renderer} renderer={this.props.renderer} />
undo={this.undo}
redo={this.redo}
historySize={this.historySize()} />
{this.renderEditor()} {this.renderEditor()}
</div> </div>

View File

@@ -4,58 +4,42 @@
width : 100%; width : 100%;
.codeEditor{ .codeEditor{
height : 100%; height : 100%;
.pageLine{ .pageLine{
background : #33333328; background-color : fade(#333, 15%);
border-top : #339 solid 1px; border-bottom : #333 solid 1px;
}
.editor-page-count{
color : grey;
float : right;
} }
.columnSplit{ .columnSplit{
font-style : italic; font-style : italic;
color : grey; color : grey;
background-color : fade(#299, 15%); background-color : fade(#299, 15%);
border-bottom : #299 solid 1px; border-bottom : #299 solid 1px;
} }
.block{ .block{
color : purple; color : purple;
font-weight : bold; font-weight : bold;
//font-style: italic; //font-style: italic;
} }
.inline-block{ .inline-block{
color : red; color : red;
font-weight : bold; font-weight : bold;
//font-style: italic; //font-style: italic;
} }
} }
.brewJump{ .brewJump{
position : absolute; position: absolute;
background-color : @teal; background-color: @teal;
cursor : pointer; cursor: pointer;
width : 30px; width : 30px;
height : 30px; height : 30px;
display : flex; display : flex;
align-items : center; align-items : center;
bottom : 20px; bottom : 20px;
right : 20px; right : 20px;
z-index : 1000000; z-index: 1000000;
justify-content : center; justify-content:center;
.tooltipLeft("Jump to brew page"); .tooltipLeft("Jump to brew page");
} }
.editorToolbar{
position: absolute;
top: 5px;
left: 50%;
color: black;
font-size: 13px;
z-index: 9;
span {
padding: 2px 5px;
}
}
} }

View File

@@ -65,6 +65,18 @@ const MetadataEditor = createClass({
}); });
}, },
getRedditLink : function(){
const meta = this.props.metadata;
const shareLink = (meta.googleId || '') + meta.shareId;
const title = `${meta.title} [${meta.systems.join(' ')}]`;
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`;
},
renderSystems : function(){ renderSystems : function(){
return _.map(SYSTEMS, (val)=>{ return _.map(SYSTEMS, (val)=>{
return <label key={val}> return <label key={val}>
@@ -115,6 +127,21 @@ const MetadataEditor = createClass({
</div>; </div>;
}, },
renderShareToReddit : function(){
if(!this.props.metadata.shareId) return;
return <div className='field reddit'>
<label>reddit</label>
<div className='value'>
<a href={this.getRedditLink()} target='_blank' rel='noopener noreferrer'>
<button className='publish'>
<i className='fab fa-reddit-alien' /> share to reddit
</button>
</a>
</div>
</div>;
},
renderRenderOptions : function(){ renderRenderOptions : function(){
if(!global.enable_v3) return; if(!global.enable_v3) return;
@@ -188,6 +215,8 @@ const MetadataEditor = createClass({
</div> </div>
</div> </div>
{this.renderShareToReddit()}
{this.renderDelete()} {this.renderDelete()}
</div>; </div>;

View File

@@ -77,6 +77,11 @@
.button(@red); .button(@red);
} }
} }
.reddit.field .value{
button{
.button(@purple);
}
}
.authors.field .value{ .authors.field .value{
font-size: 0.8em; font-size: 0.8em;
line-height : 1.5em; line-height : 1.5em;

View File

@@ -22,10 +22,7 @@ const Snippetbar = createClass({
onInject : ()=>{}, onInject : ()=>{},
onToggle : ()=>{}, onToggle : ()=>{},
showEditButtons : true, showEditButtons : true,
renderer : 'legacy', renderer : 'legacy'
undo : ()=>{},
redo : ()=>{},
historySize : ()=>{}
}; };
}, },
@@ -63,15 +60,6 @@ const Snippetbar = createClass({
if(!this.props.showEditButtons) return; if(!this.props.showEditButtons) return;
return <div className='editors'> return <div className='editors'>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} >
<i className='fas fa-undo' />
</div>
<div className={`editorTool redo ${this.props.historySize.redo ? 'active' : ''}`}
onClick={this.props.redo} >
<i className='fas fa-redo' />
</div>
<div className='divider'></div>
<div className={cx('text', { selected: this.props.view === 'text' })} <div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}> onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' /> <i className='fa fa-beer' />

View File

@@ -10,7 +10,7 @@
top : 0px; top : 0px;
right : 0px; right : 0px;
height : @menuHeight; height : @menuHeight;
width : 125px; width : 90px;
justify-content : space-between; justify-content : space-between;
&>div{ &>div{
height : @menuHeight; height : @menuHeight;
@@ -30,29 +30,6 @@
&.meta{ &.meta{
.tooltipLeft('Properties'); .tooltipLeft('Properties');
} }
&.undo{
.tooltipLeft('Undo');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
}
&.redo{
.tooltipLeft('Redo');
font-size : 0.75em;
color : grey;
&.active{
color : black;
}
}
&.divider {
background: linear-gradient(#000, #000) no-repeat center/1px 100%;
width: 5px;
&:hover{
background-color: inherit;
}
}
} }
} }
.snippetBarButton{ .snippetBarButton{

View File

@@ -100,25 +100,25 @@ const subtitles = [
module.exports = ()=>{ module.exports = ()=>{
return `<style> return `<style>
.page#p1{ text-align:center; counter-increment: none; } .phb#p1{ text-align:center; }
.page#p1:after{ display:none; } .phb#p1:after{ display:none; }
.page:nth-child(2n) .pageNumber { left: inherit !important; right: 2px !important; } .phb#p2 { counter-reset:phb-page-numbers; }
.page:nth-child(2n+1) .pageNumber { right: inherit !important; left: 2px !important; } .phb:nth-child(2n) .pageNumber { left: inherit !important; right: 2px !important; }
.page:nth-child(2n)::after { transform: scaleX(1); } .phb:nth-child(2n+1) .pageNumber { right: inherit !important; left: 2px !important; }
.page:nth-child(2n+1)::after { transform: scaleX(-1); } .phb:nth-child(2n)::after { transform: scaleX(1); }
.page:nth-child(2n) .footnote { left: inherit; text-align: right; } .phb:nth-child(2n+1)::after { transform: scaleX(-1); }
.page:nth-child(2n+1) .footnote { left: 80px; text-align: left; } .phb:nth-child(2n) .footnote { left: inherit; text-align: right; }
.phb:nth-child(2n+1) .footnote { left: 80px; text-align: left; }
</style> </style>
{{margin-top:225px}} <div style='margin-top:450px;'></div>
# ${_.sample(titles)} # ${_.sample(titles)}
{{margin-top:25px}} <div style='margin-top:25px'></div>
<div class='wide'>
{{wide
##### ${_.sample(subtitles)} ##### ${_.sample(subtitles)}
}} </div>
\\page`; \\page`;
}; };

View File

@@ -105,20 +105,6 @@ const genAbilities = function(){
]); ]);
}; };
const genLongAbilities = function(){
return _.sample([
dedent`***Pack Tactics.*** These guys work together like peanut butter and jelly. Jelly and peanut butter.
When one of these guys attacks, the target is covered with, well, peanut butter and jelly.`,
dedent`***Hangriness.*** This creature is angry, and hungry. It will refuse to do anything with you until its hunger is satisfied.
When in visual contact with this creature, you must purchase an extra order of fries, even if they say they aren't hungry.`,
dedent`***Full of Detergent.*** This creature has swallowed an entire bottle of dish detergent and is actually having a pretty good time.
While walking near this creature, you must make a dexterity check or become "a soapy mess" for three hours, after which your skin will get all dry and itchy.`
]);
};
const genAction = function(){ const genAction = function(){
const name = _.sample([ const name = _.sample([
'Abdominal Drop', 'Abdominal Drop',
@@ -173,11 +159,11 @@ module.exports = {
**Languages** :: ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)} **Languages** :: ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}
**Challenge** :: ${_.random(0, 15)} (${_.random(10, 10000)} XP) **Challenge** :: ${_.random(0, 15)} (${_.random(10, 10000)} XP)
___ ___
${_.times(_.random(genLines, genLines + 2), function(){return genAbilities();}).join('\n:\n')}
: :
${genLongAbilities()} ${_.times(_.random(genLines, genLines + 2), function(){return genAbilities();}).join('\n\t\t\t\n\t\t\t')}
:
### Actions ### Actions
${_.times(_.random(genLines, genLines + 2), function(){return genAction();}).join('\n:\n')} ${_.times(_.random(genLines, genLines + 2), function(){return genAction();}).join('\n\t\t\t\n\t\t\t')}
}} }}
\n`; \n`;
} }

View File

@@ -13,7 +13,7 @@ const watercolorGen = require('./watercolor.gen.js');
module.exports = [ module.exports = [
{ {
groupName : 'Text Editor', groupName : 'Editor',
icon : 'fas fa-pencil-alt', icon : 'fas fa-pencil-alt',
view : 'text', view : 'text',
snippets : [ snippets : [
@@ -55,7 +55,7 @@ module.exports = [
gen : (brew)=>{ gen : (brew)=>{
return `![]` + return `![]` +
`(https://api.qrserver.com/v1/create-qr-code/?data=` + `(https://api.qrserver.com/v1/create-qr-code/?data=` +
`https://homebrewery.naturalcrit.com${brew.shareId ? `/share/${brew.shareId}` : ''}` + `https://homebrewery.naturalcrit.com/share/${brew.shareId}` +
`&amp;size=100x100) {width:100px;mix-blend-mode:multiply}`; `&amp;size=100x100) {width:100px;mix-blend-mode:multiply}`;
} }
}, },
@@ -79,41 +79,35 @@ module.exports = [
icon : 'fas fa-book', icon : 'fas fa-book',
gen : TableOfContentsGen gen : TableOfContentsGen
}, },
{
name : 'Add Comment',
icon : 'fas fa-code',
gen : '<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->'
},
]
},
{
groupName : 'Style Editor',
icon : 'fas fa-pencil-alt',
view : 'style',
snippets : [
{ {
name : 'Remove Drop Cap', name : 'Remove Drop Cap',
icon : 'fas fa-remove-format', icon : 'fas fa-remove-format',
gen : dedent`/* Removes Drop Caps */ gen : '<style>\n' +
.page h1+p:first-letter { ' .phb3 h1+p:first-letter {\n' +
all: unset; ' all: unset;\n' +
}\n\n` ' }\n' +
'</style>'
}, },
{ {
name : 'Tweak Drop Cap', name : 'Tweak Drop Cap',
icon : 'fas fa-sliders-h', icon : 'fas fa-sliders-h',
gen : dedent`/* Drop Cap settings */ gen : '<style>\n' +
.page h1 + p::first-letter { ' /* Drop Cap settings */\n' +
font-family: SolberaImitationRemake; ' .phb3 h1 + p::first-letter {\n' +
font-size: 3.5cm; ' float: left;\n' +
background-image: linear-gradient(-45deg, #322814, #998250, #322814); ' font-family: SolberaImitationRemake;\n' +
line-height: 1em; ' font-size: 3.5cm;\n' +
}\n\n` ' color: #222;\n' +
' line-height: .8em;\n' +
' }\n' +
'</style>'
}, },
{ {
name : 'Add Comment', name : 'Add Comment',
icon : 'fas fa-code', icon : 'fas fa-code', /* might need to be fa-solid fa-comment-code --not sure, Gazook */
gen : '/* This is a comment that will not be rendered into your brew. */' gen : dedent`\n
<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->
`
}, },
] ]
}, },
@@ -130,7 +124,7 @@ module.exports = [
gen : dedent` gen : dedent`
![cat warrior](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width:325px,mix-blend-mode:multiply} ![cat warrior](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width:325px,mix-blend-mode:multiply}
{{artist,position:relative,top:-230px,left:10px,margin-bottom:-30px {{artist,position:relative,top:-230px,left:-100px,margin-bottom:-30px
##### Cat Warrior ##### Cat Warrior
[Kyoung Hwan Kim](https://www.artstation.com/tahra) [Kyoung Hwan Kim](https://www.artstation.com/tahra)
}}` }}`
@@ -141,7 +135,7 @@ module.exports = [
gen : dedent` gen : dedent`
![homebrew mug](http://i.imgur.com/hMna6G0.png) {position:absolute,top:50px,right:30px,width:280px} ![homebrew mug](http://i.imgur.com/hMna6G0.png) {position:absolute,top:50px,right:30px,width:280px}
{{artist,top:80px,right:30px {{artist,top:90px,right:30px
##### Homebrew Mug ##### Homebrew Mug
[naturalcrit](https://homebrew.naturalcrit.com) [naturalcrit](https://homebrew.naturalcrit.com)
}}` }}`
@@ -260,6 +254,36 @@ module.exports = [
icon : 'fas fa-table', icon : 'fas fa-table',
view : 'text', view : 'text',
snippets : [ snippets : [
{
name : 'Class Table',
icon : 'fas fa-table',
gen : ClassTableGen.full('classTable,frame,decoration,wide'),
},
{
name : 'Class Table (unframed)',
icon : 'fas fa-border-none',
gen : ClassTableGen.full('classTable,wide'),
},
{
name : '1/2 Class Table',
icon : 'fas fa-list-alt',
gen : ClassTableGen.half('classTable,decoration,frame'),
},
{
name : '1/2 Class Table (unframed)',
icon : 'fas fa-border-none',
gen : ClassTableGen.half('classTable'),
},
{
name : '1/3 Class Table',
icon : 'fas fa-border-all',
gen : ClassTableGen.third('classTable,frame'),
},
{
name : '1/3 Class Table (unframed)',
icon : 'fas fa-border-none',
gen : ClassTableGen.third('classTable'),
},
{ {
name : 'Table', name : 'Table',
icon : 'fas fa-th-list', icon : 'fas fa-th-list',
@@ -319,36 +343,6 @@ module.exports = [
}} }}
\n`; \n`;
} }
},
{
name : 'Class Table',
icon : 'fas fa-table',
gen : ClassTableGen.full('classTable,frame,decoration,wide'),
},
{
name : 'Class Table (unframed)',
icon : 'fas fa-border-none',
gen : ClassTableGen.full('classTable,wide'),
},
{
name : '1/2 Class Table',
icon : 'fas fa-list-alt',
gen : ClassTableGen.half('classTable,decoration,frame'),
},
{
name : '1/2 Class Table (unframed)',
icon : 'fas fa-border-none',
gen : ClassTableGen.half('classTable'),
},
{
name : '1/3 Class Table',
icon : 'fas fa-border-all',
gen : ClassTableGen.third('classTable,frame'),
},
{
name : '1/3 Class Table (unframed)',
icon : 'fas fa-border-none',
gen : ClassTableGen.third('classTable'),
} }
] ]
}, },
@@ -366,36 +360,44 @@ module.exports = [
{ {
name : 'A4 Page Size', name : 'A4 Page Size',
icon : 'far fa-file', icon : 'far fa-file',
gen : dedent`/* A4 Page Size */ gen : ['/* A4 Page Size */',
.page{ '.page{',
width : 210mm; ' width : 210mm;',
height : 296.8mm; ' height : 296.8mm;',
}\n\n` '}',
''
].join('\n')
}, },
{ {
name : 'Square Page Size', name : 'Square Page Size',
icon : 'far fa-file', icon : 'far fa-file',
gen : dedent`/* Square Page Size */ gen : ['/* Square Page Size */',
.page { '.page {',
width : 125mm; ' width : 125mm;',
height : 125mm; ' height : 125mm;',
padding : 12.5mm; ' padding : 12.5mm;',
columns : unset; ' columns : unset;',
}\n\n` '}',
''
].join('\n')
}, },
{ {
name : 'Ink Friendly', name : 'Ink Friendly',
icon : 'fas fa-tint', icon : 'fas fa-tint',
gen : dedent` gen : dedent`
/* Ink Friendly */ /* Ink Friendly */
*:is(.page,.monster,.note,.descriptive) { .pages *:is(.page,.monster,.note,.descriptive) {
background : white !important; background : white !important;
filter : drop-shadow(0px 0px 3px #888) !important; box-shadow : 0px 0px 3px !important;
}
.page .note:before {
box-shadow : 0px 0px 3px;
} }
.page img { .page img {
visibility : hidden; visibility : hidden;
}\n\n` }`
}, },
] ]
}, },

View File

@@ -53,19 +53,19 @@ module.exports = function(brew){
const TOC = getTOC(pages); const TOC = getTOC(pages);
const markdown = _.reduce(TOC, (r, g1, idx1)=>{ const markdown = _.reduce(TOC, (r, g1, idx1)=>{
if(g1.title !== null) { if(g1.title !== null) {
r.push(`- ### [{{ ${g1.title}}}{{ ${g1.page}}}](#p${g1.page})`); r.push(`\t\t- ### [{{ ${g1.title}}}{{ ${g1.page}}}](#p${g1.page})`);
} }
if(g1.children.length){ if(g1.children.length){
_.each(g1.children, (g2, idx2)=>{ _.each(g1.children, (g2, idx2)=>{
if(g2.title !== null) { if(g2.title !== null) {
r.push(` - #### [{{ ${g2.title}}}{{ ${g2.page}}}](#p${g2.page})`); r.push(`\t\t - #### [{{ ${g2.title}}}{{ ${g2.page}}}](#p${g2.page})`);
} }
if(g2.children.length){ if(g2.children.length){
_.each(g2.children, (g3, idx3)=>{ _.each(g2.children, (g3, idx3)=>{
if(g2.title !== null) { if(g2.title !== null) {
r.push(` - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`); r.push(`\t\t - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
} else { // Don't over-indent if no level-2 parent entry } else { // Don't over-indent if no level-2 parent entry
r.push(` - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`); r.push(`\t\t - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
} }
}); });
} }
@@ -78,7 +78,7 @@ module.exports = function(brew){
{{toc,wide {{toc,wide
# Table Of Contents # Table Of Contents
${markdown} ${markdown}
}} }}
\n`; \n`;
}; };

View File

@@ -11,7 +11,7 @@ const dedent = require('dedent-tabs').default;
module.exports = [ module.exports = [
{ {
groupName : 'Text Editor', groupName : 'Editor',
icon : 'fas fa-pencil-alt', icon : 'fas fa-pencil-alt',
view : 'text', view : 'text',
snippets : [ snippets : [
@@ -78,44 +78,33 @@ module.exports = [
icon : 'fas fa-book', icon : 'fas fa-book',
gen : TableOfContentsGen gen : TableOfContentsGen
}, },
{
name : 'Add Comment',
icon : 'fas fa-code',
gen : '<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->'
}
]
},
{
groupName : 'Style Editor',
icon : 'fas fa-pencil-alt',
view : 'style',
snippets : [
{ {
name : 'Remove Drop Cap', name : 'Remove Drop Cap',
icon : 'fas fa-remove-format', icon : 'fas fa-remove-format',
gen : dedent`/* Removes Drop Caps */ gen : '<style>\n' +
.phb h1+p:first-letter { ' .phb h1+p:first-letter {\n' +
all: unset; ' all: unset;\n' +
}\n\n` ' }\n' +
'</style>'
}, },
{ {
name : 'Tweak Drop Cap', name : 'Tweak Drop Cap',
icon : 'fas fa-sliders-h', icon : 'fas fa-sliders-h',
gen : dedent`/* Drop Cap Settings */ gen : '<style>\n' +
.phb h1 + p::first-letter { ' /* Drop Cap settings */\n' +
float: left; ' .phb h1 + p::first-letter {\n' +
font-family: Solberry; ' float: left;\n' +
font-size: 10em; ' font-family: Solberry;\n' +
color: #222; ' font-size: 10em;\n' +
line-height: .8em; ' color: #222;\n' +
}\n\n` ' line-height: .8em;\n' +
' }\n' +
'</style>'
}, },
{ {
name : 'Add Comment', name : 'Add Comment',
icon : 'fas fa-code', icon : 'fas fa-code',
gen : '/* This is a comment that will not be rendered into your brew. */' gen : `\n<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->\n\n`
} }
] ]
}, },
@@ -251,25 +240,30 @@ module.exports = [
{ {
name : 'Split Table', name : 'Split Table',
icon : 'fas fa-th-large', icon : 'fas fa-th-large',
gen : dedent`\n gen : function(){
<div style='column-count:2'> return [
| d10 | Damage Type | '<div style=\'column-count:2\'>',
|:---:|:------------| '| d10 | Damage Type |',
| 1 | Acid | '|:---:|:------------|',
| 2 | Cold | '| 1 | Acid |',
| 3 | Fire | '| 2 | Cold |',
| 4 | Force | '| 3 | Fire |',
| 5 | Lightning | '| 4 | Force |',
'| 5 | Lightning |',
| d10 | Damage Type | '',
|:---:|:------------| '```',
| 6 | Necrotic | '```',
| 7 | Poison | '',
| 8 | Psychic | '| d10 | Damage Type |',
| 9 | Radiant | '|:---:|:------------|',
| 10 | Thunder | '| 6 | Necrotic |',
</div> '| 7 | Poison |',
\n` '| 8 | Psychic |',
'| 9 | Radiant |',
'| 10 | Thunder |',
'</div>\n\n',
].join('\n');
},
} }
] ]
}, },

View File

@@ -49,7 +49,6 @@ const Homebrew = createClass({
<Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} />}/> <Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} />}/>
<Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} />}/> <Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} />}/>
<Route path='/changelog' exact component={()=><SharePage brew={this.props.brew} />}/> <Route path='/changelog' exact component={()=><SharePage brew={this.props.brew} />}/>
<Route path='/faq' exact component={()=><SharePage brew={this.props.brew} />}/>
<Route path='/v3_preview' exact component={()=><HomePage brew={this.props.brew} />}/> <Route path='/v3_preview' exact component={()=><HomePage brew={this.props.brew} />}/>
<Route path='/' component={()=><HomePage brew={this.props.brew} />}/> <Route path='/' component={()=><HomePage brew={this.props.brew} />}/>
</Switch> </Switch>

View File

@@ -39,9 +39,7 @@ const Navbar = createClass({
<Nav.item href='/' className='homebrewLogo'> <Nav.item href='/' className='homebrewLogo'>
<div>The Homebrewery</div> <div>The Homebrewery</div>
</Nav.item> </Nav.item>
<Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'> <Nav.item>{`v${this.state.ver}`}</Nav.item>
{`v${this.state.ver}`}
</Nav.item>
<PatreonNavItem /> <PatreonNavItem />
{/*this.renderChromeWarning()*/} {/*this.renderChromeWarning()*/}
</Nav.section> </Nav.section>

View File

@@ -349,14 +349,14 @@ const EditPage = createClass({
</Nav.item>; </Nav.item>;
} }
if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){ if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops! Oops!
<div className='errorContainer' onClick={this.clearErrors}> <div className='errorContainer' onClick={this.clearErrors}>
Looks like your Google credentials have Looks like your Google credentials have
expired! Visit our log in page to sign out expired! Visit the log in page to sign out
and sign back in with Google, and sign back in with Google
then try saving again! to save this to Google Drive!
<a target='_blank' rel='noopener noreferrer' <a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}> href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'> <div className='confirm'>
@@ -399,21 +399,7 @@ const EditPage = createClass({
this.state.brew.shareId; this.state.brew.shareId;
}, },
getRedditLink : function(){
const shareLink = this.processShareId();
const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : '';
const title = `${this.props.brew.title} ${systems}`;
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`;
},
renderNavbar : function(){ renderNavbar : function(){
const shareLink = this.processShareId();
return <Navbar> return <Navbar>
{this.state.alertTrashedGoogleBrew && {this.state.alertTrashedGoogleBrew &&
@@ -434,20 +420,9 @@ const EditPage = createClass({
{this.renderSaveButton()} {this.renderSaveButton()}
<NewBrew /> <NewBrew />
<ReportIssue /> <ReportIssue />
<Nav.dropdown> <Nav.item newTab={true} href={`/share/${this.processShareId()}`} color='teal' icon='fas fa-share-alt'>
<Nav.item color='teal' icon='fas fa-share-alt'> Share
share </Nav.item>
</Nav.item>
<Nav.item color='blue' href={`/share/${shareLink}`}>
view
</Nav.item>
<Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`https://homebrewery.naturalcrit.com/share/${shareLink}`);}}>
copy url
</Nav.item>
<Nav.item color='blue' href={this.getRedditLink()} newTab={true} rel='noopener noreferrer'>
post to reddit
</Nav.item>
</Nav.dropdown>
<PrintLink shareId={this.processShareId()} /> <PrintLink shareId={this.processShareId()} />
<RecentNavItem brew={this.state.brew} storageKey='edit' /> <RecentNavItem brew={this.state.brew} storageKey='edit' />
<Account /> <Account />

View File

@@ -59,6 +59,9 @@ const HomePage = createClass({
<Nav.section> <Nav.section>
<NewBrewItem /> <NewBrewItem />
<IssueNavItem /> <IssueNavItem />
<Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'>
Changelog
</Nav.item>
<RecentNavItem /> <RecentNavItem />
<AccountNavItem /> <AccountNavItem />
</Nav.section> </Nav.section>

View File

@@ -45,11 +45,7 @@ With the latest major update to *The Homebrewery* we've implemented an extended
What's new in the latest update? Check out the full changelog [here](/changelog) What's new in the latest update? Check out the full changelog [here](/changelog)
### Bugs, Issues, Suggestions? ### Bugs, Issues, Suggestions?
Take a quick look at our [Frequently Asked Questions page](/faq) to see if your question has a handy answer. Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let me know!.
Need help getting started or just the right look for your brew? Head to [r/Homebrewery](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let us know!
Have an idea to make The Homebrewery better? Or did you find something that wasn't quite right? Check out the [GitHub Repo](https://github.com/naturalcrit/homebrewery/) to report technical issues.
### Legal Junk ### Legal Junk
The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/homebrewery/blob/master/license). This means you are free to use The Homebrewery codebase any way that you want, except for claiming that you made it yourself. The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/homebrewery/blob/master/license). This means you are free to use The Homebrewery codebase any way that you want, except for claiming that you made it yourself.
@@ -57,7 +53,7 @@ The Homebrewery is licensed using the [MIT License](https://github.com/naturalcr
If you wish to sell or in some way gain profit for what you make on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used. If you wish to sell or in some way gain profit for what you make on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
### More Resources ### More Resources
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/wiki/resources). If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/comments/3uwxx9/resources_open_to_the_community/).

View File

@@ -34,18 +34,13 @@ After clicking the "Print" item in the navbar a new page will open and a print d
* In **Options** make sure "Background Images" is selected. * In **Options** make sure "Background Images" is selected.
* Hit print and enjoy! You're done! * Hit print and enjoy! You're done!
If you want to save ink or have a monochrome printer, add the **PRINT → {{fas,fa-tint}} Ink Friendly** snippet to your brew! If you want to save ink or have a monochrome printer, add the **PRINT → {{fas,fa-tint}} Ink Friendly** snippet to your brew before you print
}} }}
![homebrew mug](http://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px} <img src='https://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;left:120px;width:180px' />
{{artist,bottom:160px,left:100px <div class='pageNumber'>1</div>
##### Homebrew Mug <div class='footnote'>PART 1 | FANCINESS</div>
[naturalcrit](https://homebrew.naturalcrit.com)
}}
{{pageNumber 1}}
{{footnote PART 1 | FANCINESS}}
\column \column
@@ -57,7 +52,7 @@ Much of the syntax and styling has changed in V3. Code in one version may be bro
Scroll down to the next page for a brief summary of the changes and new features available in V3! Scroll down to the next page for a brief summary of the changes and new features available in V3!
#### New Things All The Time! #### New Things All The Time!
Check out the latest updates in the full changelog [here](/changelog). What's new in the latest update? Check out the full changelog [here](/changelog).
### Helping out ### Helping out
Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/Naturalcrit) to help me keep the servers running. Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/Naturalcrit) to help me keep the servers running.
@@ -65,8 +60,6 @@ Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/Natur
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever. This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
### Bugs, Issues, Suggestions? ### Bugs, Issues, Suggestions?
Take a quick look at our [Frequently Asked Questions page](/faq) to see if your question has a handy answer.
Need help getting started or just the right look for your brew? Head to [r/Homebrewery](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let us know! Need help getting started or just the right look for your brew? Head to [r/Homebrewery](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let us know!
Have an idea to make The Homebrewery better? Or did you find something that wasn't quite right? Check out the [GitHub Repo](https://github.com/naturalcrit/homebrewery/) to report technical issues. Have an idea to make The Homebrewery better? Or did you find something that wasn't quite right? Check out the [GitHub Repo](https://github.com/naturalcrit/homebrewery/) to report technical issues.
@@ -79,8 +72,8 @@ If you wish to sell or in some way gain profit for what's created on this site,
#### Crediting Me #### Crediting Me
If you'd like to credit me in your brew, I'd be flattered! Just reference that you made it with The Homebrewery. If you'd like to credit me in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
### More Homebrew Resources ### More Resources
Check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/wiki/resources). If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/comments/3uwxx9/resources_open_to_the_community/).
\page \page
@@ -91,6 +84,7 @@ The Homebrewery aims to make homebrewing as simple as possible, providing a live
In version 3.0.0, with a goal of adding maximum flexibility without users resorting to complex HTML to accomplish simple tasks, Homebrewery provides an extended verision of Markdown with additional syntax. In version 3.0.0, with a goal of adding maximum flexibility without users resorting to complex HTML to accomplish simple tasks, Homebrewery provides an extended verision of Markdown with additional syntax.
**You can enable V3 via the {{fa,fa-info-circle}} Properties button!** **You can enable V3 via the {{fa,fa-info-circle}} Properties button!**
### Curly Brackets ### Curly Brackets
The biggest change in V3 is the replacement of `<span></span>` and `<div></div>` with `{{ }}` for a cleaner custom formatting. Inline spans and block elements can be created and given ID's and Classes, as well as css properties, each of which are comma separated with no spaces. Use double quotes if a value requires spaces. Spans and Blocks start the same: The biggest change in V3 is the replacement of `<span></span>` and `<div></div>` with `{{ }}` for a cleaner custom formatting. Inline spans and block elements can be created and given ID's and Classes, as well as css properties, each of which are comma separated with no spaces. Use double quotes if a value requires spaces. Spans and Blocks start the same:
@@ -103,6 +97,7 @@ My favorite author is {{pen,#author,color:orange,font-family:"trebuchet ms" Bran
My favorite book is Wheel of Time. This block has a class of `purple`, an id of `book`, and centered text with a colored background. The opening and closing brackets are on lines separate from the block contents. My favorite book is Wheel of Time. This block has a class of `purple`, an id of `book`, and centered text with a colored background. The opening and closing brackets are on lines separate from the block contents.
}} }}
#### Injection #### Injection
For any element not inside a span or block, you can *inject* attributes using the same syntax but with single brackets in a single line immediately after the element. For any element not inside a span or block, you can *inject* attributes using the same syntax but with single brackets in a single line immediately after the element.
@@ -164,8 +159,12 @@ Using *Curly Injection* you can assign an id, classes, or specific inline CSS pr
## Snippets ## Snippets
Homebrewery comes with a series of *code snippets* found at the top of the editor pane that make it easy to create brews as quickly as possible. Just set your cursor where you want the code to appear in the editor pane, choose a snippet, and make the adjustments you need. Homebrewery comes with a series of *code snippets* found at the top of the editor pane that make it easy to create brews as quickly as possible. Just set your cursor where you want the code to appear in the editor pane, choose a snippet, and make the adjustments you need.
## Style Editor Panel ## Style Editor Panel
{{fa,fa-paint-brush}} Technically released prior to v3 but still new to many users, check out the new **Style Editor** located on the right side of the Snippet bar. This editor accepts CSS for styling without requiring `<style>` tags-- anything that would have gone inside style tags before can now be placed here, and snippets that insert CSS styles are now located on that tab. {{fa,fa-paint-brush}} Technically released prior to v3 but still new to many users, check out the new **Style Editor** located on the right side of the Snippet bar. This editor accepts CSS for styling without requiring `<style>` tags-- anything that would have gone inside style tags before can now be placed here, and snippets that insert CSS styles are now located on that tab.
{{pageNumber 2}}
{{footnote PART 2 | BORING STUFF}}
<div class='pageNumber'>2</div>
<div class='footnote'>PART 2 | BORING STUFF</div>

View File

@@ -45,44 +45,47 @@ const NewPage = createClass({
}, },
getInitialState : function() { getInitialState : function() {
const brew = this.props.brew;
if(typeof window !== 'undefined') { //Load from localStorage if in client browser
const brewStorage = localStorage.getItem(BREWKEY);
const styleStorage = localStorage.getItem(STYLEKEY);
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
if(!brew.text || !brew.style){
brew.text = brew.text || (brewStorage ?? '');
brew.style = brew.style || (styleStorage ?? undefined);
// brew.title = metaStorage?.title || this.state.brew.title;
// brew.description = metaStorage?.description || this.state.brew.description;
brew.renderer = metaStorage?.renderer || brew.renderer;
}
}
return { return {
brew : { brew : {
text : brew.text || '', text : this.props.brew.text || '',
style : brew.style || undefined, style : this.props.brew.style || undefined,
gDrive : false, gDrive : false,
title : brew.title || '', title : this.props.brew.title || '',
description : brew.description || '', description : this.props.brew.description || '',
tags : brew.tags || '', tags : this.props.brew.tags || '',
published : false, published : false,
authors : [], authors : [],
systems : brew.systems || [], systems : this.props.brew.systems || [],
renderer : brew.renderer || 'legacy' renderer : this.props.brew.renderer || 'legacy'
}, },
isSaving : false, isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false), saveGoogle : (global.account && global.account.googleId ? true : false),
errors : null, errors : null,
htmlErrors : Markdown.validate(brew.text) htmlErrors : Markdown.validate(this.props.brew.text)
}; };
}, },
componentDidMount : function() { componentDidMount : function() {
const brewStorage = localStorage.getItem(BREWKEY);
const styleStorage = localStorage.getItem(STYLEKEY);
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
const brew = this.state.brew;
if(!this.state.brew.text || !this.state.brew.style){
brew.text = this.state.brew.text || (brewStorage ?? '');
brew.style = this.state.brew.style || (styleStorage ?? undefined);
// brew.title = metaStorage?.title || this.state.brew.title;
// brew.description = metaStorage?.description || this.state.brew.description;
brew.renderer = metaStorage?.renderer || this.state.brew.renderer;
}
this.setState((prevState)=>({
brew : brew,
htmlErrors : Markdown.validate(prevState.brew.text)
}));
document.addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys);
}, },
componentWillUnmount : function() { componentWillUnmount : function() {
@@ -143,6 +146,14 @@ const NewPage = createClass({
}); });
}, },
clearErrors : function(){
this.setState({
errors : null,
isSaving : false
});
},
save : async function(){ save : async function(){
this.setState({ this.setState({
isSaving : true isSaving : true
@@ -226,14 +237,14 @@ const NewPage = createClass({
</Nav.item>; </Nav.item>;
} }
if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){ if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops! Oops!
<div className='errorContainer' onClick={this.clearErrors}> <div className='errorContainer' onClick={this.clearErrors}>
Looks like your Google credentials have Looks like your Google credentials have
expired! Visit our log in page to sign out expired! Visit the log in page to sign out
and sign back in with Google, and sign back in with Google
then try saving again! to save this to Google Drive!
<a target='_blank' rel='noopener noreferrer' <a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}> href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'> <div className='confirm'>
@@ -271,6 +282,7 @@ const NewPage = createClass({
}, },
print : function(){ print : function(){
localStorage.setItem('print', `<style>\n${this.state.brew.style}\n</style>\n\n${this.state.brew.text}`);
window.open('/print?dialog=true&local=print', '_blank'); window.open('/print?dialog=true&local=print', '_blank');
}, },

View File

@@ -7,10 +7,6 @@ const { Meta } = require('vitreum/headtags');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js'); const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
const PrintPage = createClass({ const PrintPage = createClass({
getDefaultProps : function() { getDefaultProps : function() {
return { return {
@@ -25,42 +21,23 @@ const PrintPage = createClass({
getInitialState : function() { getInitialState : function() {
return { return {
brew : { brewText : this.props.brew.text
text : this.props.brew.text || '',
style : this.props.brew.style || undefined,
renderer : this.props.brew.renderer || 'legacy'
}
}; };
}, },
componentDidMount : function() { componentDidMount : function() {
if(this.props.query.local == 'print'){ if(this.props.query.local){
const brewStorage = localStorage.getItem(BREWKEY); this.setState((prevState, prevProps)=>({
const styleStorage = localStorage.getItem(STYLEKEY); brewText : localStorage.getItem(prevProps.query.local)
const metaStorage = JSON.parse(localStorage.getItem(METAKEY)); }));
this.setState((prevState, prevProps)=>{
return {
brew : {
text : brewStorage,
style : styleStorage,
renderer : metaStorage.renderer || 'legacy'
}
};
});
} }
if(this.props.query.dialog) window.print(); if(this.props.query.dialog) window.print();
}, },
renderStyle : function() {
if(!this.state.brew.style) return;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.state.brew.style} </style>` }} />;
},
renderPages : function(){ renderPages : function(){
if(this.state.brew.renderer == 'legacy') { if(this.props.brew.renderer == 'legacy') {
return _.map(this.state.brew.text.split('\\page'), (pageText, index)=>{ return _.map(this.state.brewText.split('\\page'), (pageText, index)=>{
return <div return <div
className='phb page' className='phb page'
id={`p${index + 1}`} id={`p${index + 1}`}
@@ -68,7 +45,7 @@ const PrintPage = createClass({
key={index} />; key={index} />;
}); });
} else { } else {
return _.map(this.state.brew.text.split(/^\\page$/gm), (pageText, index)=>{ return _.map(this.state.brewText.split(/^\\page$/gm), (pageText, index)=>{
pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear) pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
return ( return (
<div className='page' id={`p${index + 1}`} key={index} > <div className='page' id={`p${index + 1}`} key={index} >
@@ -83,12 +60,10 @@ const PrintPage = createClass({
render : function(){ render : function(){
return <div> return <div>
<Meta name='robots' content='noindex, nofollow' /> <Meta name='robots' content='noindex, nofollow' />
<link href={`${this.state.brew.renderer == 'legacy' ? '/themes/5ePhbLegacy.style.css' : '/themes/5ePhb.style.css'}`} rel='stylesheet'/> <link href={`${this.props.brew.renderer == 'legacy' ? '/themes/5ePhbLegacy.style.css' : '/themes/5ePhb.style.css'}`} rel='stylesheet'/>
{/* Apply CSS from Style tab */} {/* Apply CSS from Style tab */}
{this.renderStyle()} <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.brew.style} </style>` }} />
<div className='pages' ref='pages'> {this.renderPages()}
{this.renderPages()}
</div>
</div>; </div>;
} }
}); });

View File

@@ -29,19 +29,23 @@ const SharePage = createClass({
}; };
}, },
getInitialState : function() {
return {
showDropdown : false
};
},
componentDidMount : function() { componentDidMount : function() {
document.addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys);
}, },
componentWillUnmount : function() { componentWillUnmount : function() {
document.removeEventListener('keydown', this.handleControlKeys); document.removeEventListener('keydown', this.handleControlKeys);
}, },
handleControlKeys : function(e){ handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return; if(!(e.ctrlKey || e.metaKey)) return;
const P_KEY = 80; const P_KEY = 80;
if(e.keyCode == P_KEY){ if(e.keyCode == P_KEY){
window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} }
@@ -53,6 +57,28 @@ const SharePage = createClass({
this.props.brew.shareId; this.props.brew.shareId;
}, },
handleDropdown : function(show){
this.setState({
showDropdown : show
});
},
renderDropdown : function(){
if(!this.state.showDropdown) return null;
return <div className='dropdown'>
<a href={`/source/${this.processShareId()}`} className='item'>
view
</a>
<a href={`/download/${this.processShareId()}`} className='item'>
download
</a>
<a href={`/new/${this.processShareId()}`} className='item'>
clone to new
</a>
</div>;
},
render : function(){ render : function(){
return <div className='sharePage sitePage'> return <div className='sharePage sitePage'>
<Meta name='robots' content='noindex, nofollow' /> <Meta name='robots' content='noindex, nofollow' />
@@ -64,20 +90,12 @@ const SharePage = createClass({
<Nav.section> <Nav.section>
{this.props.brew.shareId && <> {this.props.brew.shareId && <>
<PrintLink shareId={this.processShareId()} /> <PrintLink shareId={this.processShareId()} />
<Nav.dropdown> <Nav.item icon='fas fa-code' color='red' className='source'
<Nav.item color='red' icon='fas fa-code'> onMouseEnter={()=>this.handleDropdown(true)}
source onMouseLeave={()=>this.handleDropdown(false)}>
</Nav.item> source
<Nav.item color='blue' href={`/source/${this.processShareId()}`}> {this.renderDropdown()}
view </Nav.item>
</Nav.item>
<Nav.item color='blue' href={`/download/${this.processShareId()}`}>
download
</Nav.item>
<Nav.item color='blue' href={`/new/${this.processShareId()}`}>
clone to new
</Nav.item>
</Nav.dropdown>
</>} </>}
<RecentNavItem brew={this.props.brew} storageKey='view' /> <RecentNavItem brew={this.props.brew} storageKey='view' />
<Account /> <Account />

View File

@@ -2,4 +2,49 @@
.content{ .content{
overflow-y : hidden; overflow-y : hidden;
} }
} .source.navItem{
position : relative;
.dropdown{
position : absolute;
top : 28px;
left : 0px;
z-index : 10000;
width : 100%;
h4{
display : block;
box-sizing : border-box;
padding : 5px 0px;
background-color : #333;
font-size : 0.8em;
color : #bbb;
text-align : center;
border-top : 1px solid #888;
&:nth-of-type(1){ background-color: darken(@teal, 20%); }
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
}
.item{
.animate(background-color);
position : relative;
display : block;
width : 100%;
vertical-align : middle;
padding : 13px 5px;
box-sizing : border-box;
background-color : #333;
color : white;
text-decoration : none;
border-top : 1px solid #888;
&:hover{
background-color : @blue;
}
.title{
display : inline-block;
overflow : hidden;
width : 100%;
text-overflow : ellipsis;
white-space : nowrap;
}
}
}
}
}

View File

@@ -7,7 +7,6 @@ const moment = require('moment');
const request = require('superagent'); const request = require('superagent');
const googleDriveIcon = require('../../../googleDrive.png'); const googleDriveIcon = require('../../../googleDrive.png');
const dedent = require('dedent-tabs').default;
const BrewItem = createClass({ const BrewItem = createClass({
getDefaultProps : function() { getDefaultProps : function() {
@@ -48,7 +47,7 @@ const BrewItem = createClass({
renderDeleteBrewLink : function(){ renderDeleteBrewLink : function(){
if(!this.props.brew.editId) return; if(!this.props.brew.editId) return;
return <a className='deleteLink' onClick={this.deleteBrew}> return <a onClick={this.deleteBrew}>
<i className='fas fa-trash-alt' title='Delete' /> <i className='fas fa-trash-alt' title='Delete' />
</a>; </a>;
}, },
@@ -61,7 +60,7 @@ const BrewItem = createClass({
editLink = this.props.brew.googleId + editLink; editLink = this.props.brew.googleId + editLink;
} }
return <a className='editLink' href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'> return <a href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fas fa-pencil-alt' title='Edit' /> <i className='fas fa-pencil-alt' title='Edit' />
</a>; </a>;
}, },
@@ -74,7 +73,7 @@ const BrewItem = createClass({
shareLink = this.props.brew.googleId + shareLink; shareLink = this.props.brew.googleId + shareLink;
} }
return <a className='shareLink' href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'> return <a href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fas fa-share-alt' title='Share' /> <i className='fas fa-share-alt' title='Share' />
</a>; </a>;
}, },
@@ -87,7 +86,7 @@ const BrewItem = createClass({
shareLink = this.props.brew.googleId + shareLink; shareLink = this.props.brew.googleId + shareLink;
} }
return <a className='downloadLink' href={`/download/${shareLink}`}> return <a href={`/download/${shareLink}`}>
<i className='fas fa-download' title='Download' /> <i className='fas fa-download' title='Download' />
</a>; </a>;
}, },
@@ -111,10 +110,6 @@ const BrewItem = createClass({
</div> </div>
<hr /> <hr />
<div className='info'> <div className='info'>
<span title={`Authors:\n${brew.authors.join('\n')}`}>
<i className='fas fa-user'/> {brew.authors.join(', ')}
</span>
<br />
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}> <span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
<i className='fas fa-eye'/> {brew.views} <i className='fas fa-eye'/> {brew.views}
</span> </span>
@@ -123,12 +118,14 @@ const BrewItem = createClass({
<i className='far fa-file' /> {brew.pageCount} <i className='far fa-file' /> {brew.pageCount}
</span> </span>
} }
<span title={dedent` <span>
Created: ${moment(brew.createdAt).local().format(dateFormatString)}
Last updated: ${moment(brew.updatedAt).local().format(dateFormatString)}`}>
<i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()} <i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()}
</span> </span>
{this.renderGoogleDriveIcon()} {this.renderGoogleDriveIcon()}
<br />
<span title={`Authors:\n${brew.authors.join('\n')}`}>
<i className='fas fa-user'/> {brew.authors.join(', ')}
</span>
</div> </div>
<div className='links'> <div className='links'>

View File

@@ -27,11 +27,12 @@
.info{ .info{
position: initial; position: initial;
bottom: 2px; bottom: 2px;
margin-bottom: 4px;
font-family : ScalySans; font-family : ScalySans;
font-size : 1.2em; font-size : 1.2em;
&>span{ &>span{
display : float;
margin-right : 12px; margin-right : 12px;
line-height : 1.5em;
} }
} }
&:hover{ &:hover{

View File

@@ -13,7 +13,6 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const BrewItem = require('./brewItem/brewItem.jsx'); const BrewItem = require('./brewItem/brewItem.jsx');
const ReportIssue = require('../../navbar/issue.navitem.jsx');
// const brew = { // const brew = {
// title : 'SUPER Long title woah now', // title : 'SUPER Long title woah now',
@@ -86,7 +85,7 @@ const UserPage = createClass({
<button <button
value={`${sortValue}`} value={`${sortValue}`}
onClick={this.handleSortOptionChange} onClick={this.handleSortOptionChange}
className={`sortOption ${(this.state.sortType == sortValue ? 'active' : '')}`} className={`${(this.state.sortType == sortValue ? 'active' : '')}`}
> >
{`${sortTitle}`} {`${sortTitle}`}
</button> </button>
@@ -102,7 +101,7 @@ const UserPage = createClass({
renderFilterOption : function(){ renderFilterOption : function(){
return <td> return <td>
<label className='filterOption'> <label>
<i className='fas fa-search'></i> <i className='fas fa-search'></i>
<input <input
type='search' type='search'
@@ -163,7 +162,6 @@ const UserPage = createClass({
<Navbar> <Navbar>
<Nav.section> <Nav.section>
<NewBrew /> <NewBrew />
<ReportIssue />
<RecentNavItem /> <RecentNavItem />
<Account /> <Account />
</Nav.section> </Nav.section>

View File

@@ -2,6 +2,5 @@
"host" : "homebrewery.local.naturalcrit.com:8000", "host" : "homebrewery.local.naturalcrit.com:8000",
"naturalcrit_url" : "local.naturalcrit.com:8010", "naturalcrit_url" : "local.naturalcrit.com:8010",
"secret" : "secret", "secret" : "secret",
"web_port" : 8000, "web_port" : 8000
"enable_v3" : true
} }

152
faq.md
View File

@@ -1,152 +0,0 @@
```css
h5 {
font-size: .35cm !important;
}
.taskList li {
list-style-type : none;
}
.taskList li input {
margin-left : -0.52cm;
transform: translateY(.05cm);
filter: brightness(1.1) drop-shadow(1px 2px 1px #222);
}
.taskList li input[checked] {
filter: sepia(100%) hue-rotate(60deg) saturate(3.5) contrast(4) brightness(1.1) drop-shadow(1px 2px 1px #222);
}
pre + * {
margin-top: 0.17cm;
}
pre {
margin-top: 0.17cm;
}
.page pre code {
word-break:break-word;
}
.page p + pre {
margin-top : 0.1cm;
}
.page h1 + p:first-letter {
all:unset;
}
.page .toc ul {
margin-top:0;
}
.page h3 {
font-family:inherit;
font-size:inherit;
border:inherit;
margin-top:12px;
margin-bottom:5px
}
.page h3:before {
content:'Q.';
position:absolute;
font-size:2em;
margin-left:-1.2em;
}
.page .columnSplit + h3 {
margin-top:0;
}
```
# FAQ
{{wide Updated Oct. 11, 2021}}
### The site is down for me! Anyone else?
You can check the site status here: [Everyone or Just Me](https://downforeveryoneorjustme.com/homebrewery.naturalcrit.com)
### How do I log out?
Go to https://homebrewery.naturalcrit.com/login, and hit the "*logout*" link.
### Why am I getting an error when trying to save, and my account is linked to Google?
A sign-in with Google only lasts a year until the authentication expires. You must go [here](https://www.naturalcrit.com/login), click the *Log-out* button, and then sign back in using your Google account.
### I lost my password, how do I reset it? How do I change my password?
Homebrewery is specifically designed to not hold personal information as a measure to protect both users and admin, and does not require an email address. Thus it would be difficult to send a new password to a user. Reach out to the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username.
If you have linked your account with a Google account, you would change your password within Google.
### Is there a way to restore a previous version of my brew?
Currently, there is no way to do this through the site yourself. This would take too much of a toll on the amount of storage the homebrewery requires. However, we do have daily backups of our database that we keep for 8 days, and you can contact the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username, the name of the lost brew, and the last known time it was working properly. We can manually look through our backups and restore it if it exists.
### I worked on a brew for X hours, and suddenly all the text disappeared!
This usually happens if you accidentally drag-select all of your text and then start typing which overwrites the selection. Do not panic, and do not refresh the page or reload your brew quite yet as it is probably auto-saved in this state already. Simply press CTRL+Z as many times as needed to undo your last few changes and you will be back to where you were, then make sure to save your brew in the "good" state.
\column
### Why is only Chrome supported?
Different browsers have differing abilities to handle web styling (or "CSS"). For example, Firefox is not currently capable of handling column breaks well but Chrome has no problem. Also, each browser has slight differences in how they display pages which can make it a nightmare to compensate for. These capabilities change over time and we are hopeful that each browser update bridges these gaps and adds more features; until then, we will develop with one browser in mind.
### Both my friend and myself are using Chrome, but the brews still look different. Why?
A pixel can be rendered differently depending on the browser, operating system, computer, or screen. Unless you and your friend have exactly the same setup, it is likely your online brew will have very tiny differences. However, sometimes a few pixels is all it takes to create *big* differences....for example, an extra pixel can cause a whole line of text or even a monster stat block to run out of space in it's current column and be pushed to the next column or even off the page.
The best way to avoid this is to leave space at the end of a column equal to one or two lines of text. Or, create a PDF from your document for sharing--- PDF's are designed to be rendered the same on all devices.
### Why do I need to manually create a new page? Why doesn't text flow between pages?
A Homebrewery document is at it's core an HTML & CSS document, and currently limited by the specs of those technologies. It is currently not possible to flow content from inside one box ("page") to the inside of another box. It seems likely that someday CSS will add this capability, and if/when that happens, Homebrewery will adopt it as soon as possible.
### Where do I get images?
The Homebrewery does not provide images for use besides some page elements and example images for snippets. You will need to find your own images for use and be sure you are following the appropriate license requirements.
Once you have an image you would like to use, it is recommended to host it somewhere that won't disappear; commonly, people host their images on [Imgur](https://www.imgur.com). Create an account and upload your images there, and use the *Direct Link* that is shown when you click into the image from the gallery in your Homebrewery document.
\page
### A particular font does not work for my language, what do I do?
The fonts used were originally created for use with the English language, though revisions since then have added more support for other languages. They are still not complete sets and may be missing a glyph/character you need. Unfortunately, the volunteer group as it stands at the time of this writing does not have a font guru, so it would be difficult to add more glyphs (especially complicated glyphs). Let us know which glyph is missing on the subreddit, but you may need to search [Google Fonts](https://fonts.google.com) for an alternative font if you need something fast.
### Whenever I click on the "Get PDF" button, instead of getting a download, it opens Print Preview in another tab.
Yes, this is by design. In the print preview, select "Save as PDF" as the Destination, and then click "Save". There will be a normal download dialog where you can save your brew as a PDF.
### The preview window is suddenly gone, I can only see the editor side of the Homebrewery (or the other way around).
1. Press `CTRL`+`SHIFT`+`i` (or right-click and select "Inspect") while in the Homebrewery.
2. Expand...
```
- `body`
- `main`
- `div class="homebrew"`
- `div class="editPage page"`
- `div class="content"`
- `div class="splitPane"`
```
There you will find 3 divs: `div class="pane" [...]`, `div class="divider" [...]`, and `div class="pane" [...]`.
The `class="pane"` looks similar to this: `div class="pane" data-reactid="36" style="flex: 0 0 auto; width: 925px;"`.
Change whatever stands behind width: to something smaller than your display width.
### I have white borders on the bottom/sides of the print preview.
The Homebrewery paper size and your print paper size do not match.
The Homebrewery defaults to creating US Letter page sizes. If you are printing with A4 size paper, you must add the "A4 Page Size" snippet. In the "Print" dialog be sure your Paper Size matches the page size in Homebrewery.
### Typing `#### Adhesion` in the text editor doesn't show the header at all in the completed page?
Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.

4519
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
{ {
"name": "homebrewery", "name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown", "description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.0.6", "version": "3.0.0",
"engines": { "engines": {
"node": "16.11.x" "node": "14.15.x"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -40,31 +40,29 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.16.5", "@babel/core": "^7.15.0",
"@babel/plugin-transform-runtime": "^7.16.5", "@babel/plugin-transform-runtime": "^7.15.0",
"@babel/preset-env": "^7.16.5", "@babel/preset-env": "^7.15.4",
"@babel/preset-react": "^7.16.5", "@babel/preset-react": "^7.14.5",
"body-parser": "^1.19.1", "body-parser": "^1.19.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"codemirror": "^5.64.0", "codemirror": "^5.62.3",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.5",
"create-react-class": "^15.7.0", "create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.1", "dedent-tabs": "^0.9.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.1.4",
"express-static-gzip": "2.1.1", "express-static-gzip": "2.1.1",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"googleapis": "92.0.0", "googleapis": "85.0.0",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "4.0.7", "marked": "3.0.3",
"marked-extended-tables": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1", "moment": "^2.29.1",
"mongoose": "^6.1.2", "mongoose": "^5.13.7",
"nanoid": "3.1.30", "nanoid": "3.1.25",
"nconf": "^0.11.3", "nconf": "^0.11.3",
"prop-types": "15.7.2", "prop-types": "15.7.2",
"query-string": "7.0.1", "query-string": "7.0.1",
@@ -77,8 +75,8 @@
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.4.1", "eslint": "^7.32.0",
"eslint-plugin-react": "^7.27.1", "eslint-plugin-react": "^7.25.1",
"pico-check": "^2.2.0" "pico-check": "^2.1.3"
} }
} }

View File

@@ -12,18 +12,6 @@
"codemirror/mode/gfm/gfm.js", "codemirror/mode/gfm/gfm.js",
"codemirror/mode/css/css.js", "codemirror/mode/css/css.js",
"codemirror/mode/javascript/javascript.js", "codemirror/mode/javascript/javascript.js",
"codemirror/addon/fold/foldcode.js",
"codemirror/addon/fold/foldgutter.js",
"codemirror/addon/fold/xml-fold.js",
"codemirror/addon/search/search.js",
"codemirror/addon/search/searchcursor.js",
"codemirror/addon/search/jump-to-line.js",
"codemirror/addon/search/match-highlighter.js",
"codemirror/addon/search/matchesonscrollbar.js",
"codemirror/addon/dialog/dialog.js",
"codemirror/addon/edit/closetag.js",
"codemirror/addon/edit/trailingspace.js",
"codemirror/addon/selection/active-line.js",
"moment", "moment",
"superagent", "superagent",
"marked" "marked"

View File

@@ -2,7 +2,6 @@
const _ = require('lodash'); const _ = require('lodash');
const jwt = require('jwt-simple'); const jwt = require('jwt-simple');
const express = require('express'); const express = require('express');
const yaml = require('js-yaml');
const app = express(); const app = express();
const homebrewApi = require('./server/homebrew.api.js'); const homebrewApi = require('./server/homebrew.api.js');
@@ -33,7 +32,7 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{
if(accessType == 'raw') { if(accessType == 'raw') {
return brew; return brew;
} }
splitTextStyleAndMetadata(brew); splitTextAndStyle(brew);
return brew; return brew;
}); });
@@ -46,15 +45,8 @@ const sanitizeBrew = (brew, full=false)=>{
return brew; return brew;
}; };
const splitTextStyleAndMetadata = (brew)=>{ const splitTextAndStyle = (brew)=>{
brew.text = brew.text.replaceAll('\r\n', '\n'); brew.text = brew.text.replaceAll('\r\n', '\n');
if(brew.text.startsWith('```metadata')) {
const index = brew.text.indexOf('```\n\n');
const metadataSection = brew.text.slice(12, index - 1);
const metadata = yaml.load(metadataSection);
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer']));
brew.text = brew.text.slice(index + 5);
}
if(brew.text.startsWith('```css')) { if(brew.text.startsWith('```css')) {
const index = brew.text.indexOf('```\n\n'); const index = brew.text.indexOf('```\n\n');
brew.style = brew.text.slice(7, index - 1); brew.style = brew.text.slice(7, index - 1);
@@ -80,11 +72,10 @@ const config = require('nconf')
//DB //DB
const mongoose = require('mongoose'); const mongoose = require('mongoose');
mongoose.connect(config.get('mongodb_uri') || config.get('mongolab_uri') || 'mongodb://localhost/naturalcrit', mongoose.connect(config.get('mongodb_uri') || config.get('mongolab_uri') || 'mongodb://localhost/naturalcrit',
{ retryWrites: false }); { retryWrites: false, useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true });
mongoose.connection.on('error', (err)=>{ mongoose.connection.on('error', ()=>{
console.log('Error : Could not connect to a Mongo Database.'); console.log('Error : Could not connect to a Mongo Database.');
console.log(' If you are running locally, make sure mongodb.exe is running.'); console.log(' If you are running locally, make sure mongodb.exe is running.');
console.log(err);
throw 'Can not connect to Mongo'; throw 'Can not connect to Mongo';
}); });
@@ -112,7 +103,6 @@ const HomebrewModel = require('./server/homebrew.model.js').model;
const welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.md', 'utf8'); const welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
const welcomeTextV3 = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg_v3.md', 'utf8'); const welcomeTextV3 = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg_v3.md', 'utf8');
const changelogText = require('fs').readFileSync('./changelog.md', 'utf8'); const changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
const faqText = require('fs').readFileSync('./faq.md', 'utf8');
String.prototype.replaceAll = function(s, r){return this.split(s).join(r);}; String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
@@ -136,7 +126,7 @@ app.get('/v3_preview', async (req, res, next)=>{
text : welcomeTextV3, text : welcomeTextV3,
renderer : 'V3' renderer : 'V3'
}; };
splitTextStyleAndMetadata(brew); splitTextAndStyle(brew);
req.brew = brew; req.brew = brew;
return next(); return next();
}); });
@@ -148,19 +138,7 @@ app.get('/changelog', async (req, res, next)=>{
text : changelogText, text : changelogText,
renderer : 'V3' renderer : 'V3'
}; };
splitTextStyleAndMetadata(brew); splitTextAndStyle(brew);
req.brew = brew;
return next();
});
//FAQ page
app.get('/faq', async (req, res, next)=>{
const brew = {
title : 'FAQ',
text : faqText,
renderer : 'V3'
};
splitTextStyleAndMetadata(brew);
req.brew = brew; req.brew = brew;
return next(); return next();
}); });
@@ -304,6 +282,5 @@ app.use((err, req, res, next)=>{
//^=====--------------------------------------=====^// //^=====--------------------------------------=====^//
const PORT = process.env.PORT || config.get('web_port') || 8000; const PORT = process.env.PORT || config.get('web_port') || 8000;
app.listen(PORT, ()=>{ app.listen(PORT);
console.log(`server on port:${PORT}`); console.log(`server on port:${PORT}`);
});

View File

@@ -120,10 +120,10 @@ GoogleActions = {
updatedAt : file.modifiedTime, updatedAt : file.modifiedTime,
gDrive : true, gDrive : true,
googleId : file.id, googleId : file.id,
pageCount : parseInt(file.properties.pageCount), pageCount : file.properties.pageCount,
title : file.properties.title, title : file.properties.title,
description : file.description, description : file.description,
views : parseInt(file.properties.views), views : file.properties.views,
tags : '', tags : '',
published : file.properties.published ? file.properties.published == 'true' : false, published : file.properties.published ? file.properties.published == 'true' : false,
authors : [req.account.username], //TODO: properly save and load authors to google drive authors : [req.account.username], //TODO: properly save and load authors to google drive

View File

@@ -4,7 +4,6 @@ const router = require('express').Router();
const zlib = require('zlib'); const zlib = require('zlib');
const GoogleActions = require('./googleActions.js'); const GoogleActions = require('./googleActions.js');
const Markdown = require('../shared/naturalcrit/markdown.js'); const Markdown = require('../shared/naturalcrit/markdown.js');
const yaml = require('js-yaml');
// const getTopBrews = (cb) => { // const getTopBrews = (cb) => {
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) { // HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
@@ -12,22 +11,6 @@ const yaml = require('js-yaml');
// }); // });
// }; // };
const mergeBrewText = (brew)=>{
let text = brew.text;
if(brew.style !== undefined) {
text = `\`\`\`css\n` +
`${brew.style || ''}\n` +
`\`\`\`\n\n` +
`${text}`;
}
const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer']);
text = `\`\`\`metadata\n` +
`${yaml.dump(metadata)}\n` +
`\`\`\`\n\n` +
`${text}`;
return text;
};
const MAX_TITLE_LENGTH = 100; const MAX_TITLE_LENGTH = 100;
const getGoodBrewTitle = (text)=>{ const getGoodBrewTitle = (text)=>{
@@ -45,6 +28,16 @@ const excludePropsFromUpdate = (brew)=>{
return brew; return brew;
}; };
const mergeBrewText = (text, style)=>{
if(typeof style !== 'undefined') {
text = `\`\`\`css\n` +
`${style}\n` +
`\`\`\`\n\n` +
`${text}`;
}
return text;
};
const newBrew = (req, res)=>{ const newBrew = (req, res)=>{
const brew = req.body; const brew = req.body;
@@ -53,7 +46,7 @@ const newBrew = (req, res)=>{
} }
brew.authors = (req.account) ? [req.account.username] : []; brew.authors = (req.account) ? [req.account.username] : [];
brew.text = mergeBrewText(brew); brew.text = mergeBrewText(brew.text, brew.style);
delete brew.editId; delete brew.editId;
delete brew.shareId; delete brew.shareId;
@@ -82,7 +75,7 @@ const updateBrew = (req, res)=>{
.then((brew)=>{ .then((brew)=>{
const updateBrew = excludePropsFromUpdate(req.body); const updateBrew = excludePropsFromUpdate(req.body);
brew = _.merge(brew, updateBrew); brew = _.merge(brew, updateBrew);
brew.text = mergeBrewText(brew); brew.text = mergeBrewText(brew.text, brew.style);
// Compress brew text to binary before saving // Compress brew text to binary before saving
brew.textBin = zlib.deflateRawSync(brew.text); brew.textBin = zlib.deflateRawSync(brew.text);
@@ -150,7 +143,7 @@ const newGoogleBrew = async (req, res, next)=>{
} }
brew.authors = (req.account) ? [req.account.username] : []; brew.authors = (req.account) ? [req.account.username] : [];
brew.text = mergeBrewText(brew); brew.text = mergeBrewText(brew.text, brew.style);
delete brew.editId; delete brew.editId;
delete brew.shareId; delete brew.shareId;
@@ -172,13 +165,13 @@ const updateGoogleBrew = async (req, res, next)=>{
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); } try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
const brew = excludePropsFromUpdate(req.body); const brew = excludePropsFromUpdate(req.body);
brew.text = mergeBrewText(brew); brew.text = mergeBrewText(brew.text, brew.style);
try { try {
const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew); const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew);
return res.status(200).send(updatedBrew); return res.status(200).send(updatedBrew);
} catch (err) { } catch (err) {
return res.status(err.response?.status || 500).send(err); return res.status(err.response.status).send(err);
} }
}; };

View File

@@ -1,48 +0,0 @@
const autoCloseCurlyBraces = function(CodeMirror, cm, typingClosingBrace) {
const ranges = cm.listSelections(), replacements = [];
for (let i = 0; i < ranges.length; i++) {
if(!ranges[i].empty()) return CodeMirror.Pass;
const pos = ranges[i].head, line = cm.getLine(pos.line), tok = cm.getTokenAt(pos);
if(!typingClosingBrace && (tok.type == 'string' || tok.string.charAt(0) != '{' || tok.start != pos.ch - 1))
return CodeMirror.Pass;
else if(typingClosingBrace) {
let hasUnclosedBraces = false, index = -1;
do {
index = line.indexOf('{{', index + 1);
if(index !== -1 && line.indexOf('}}', index + 1) === -1) {
hasUnclosedBraces = true;
break;
}
} while (index !== -1);
if(!hasUnclosedBraces) return CodeMirror.Pass;
}
replacements[i] = typingClosingBrace ? {
text : '}}',
newPos : CodeMirror.Pos(pos.line, pos.ch + 2)
} : {
text : '{}}',
newPos : CodeMirror.Pos(pos.line, pos.ch + 1)
};
}
for (let i = ranges.length - 1; i >= 0; i--) {
const info = replacements[i];
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, '+insert');
const sel = cm.listSelections().slice(0);
sel[i] = {
head : info.newPos,
anchor : info.newPos
};
cm.setSelections(sel);
}
};
module.exports = {
autoCloseCurlyBraces : function(CodeMirror, codeMirror) {
const map = { name: 'autoCloseCurlyBraces' };
map[`'{'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm); };
map[`'}'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm, true); };
codeMirror.addKeyMap(map);
}
};

View File

@@ -1,10 +1,9 @@
/* eslint-disable max-lines */
require('./codeEditor.less'); require('./codeEditor.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const closeTag = require('./close-tag');
let CodeMirror; let CodeMirror;
if(typeof navigator !== 'undefined'){ if(typeof navigator !== 'undefined'){
@@ -14,29 +13,6 @@ if(typeof navigator !== 'undefined'){
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
require('codemirror/mode/css/css.js'); require('codemirror/mode/css/css.js');
require('codemirror/mode/javascript/javascript.js'); require('codemirror/mode/javascript/javascript.js');
//Addons
//Code folding
require('codemirror/addon/fold/foldcode.js');
require('codemirror/addon/fold/foldgutter.js');
//Search and replace
require('codemirror/addon/search/search.js');
require('codemirror/addon/search/searchcursor.js');
require('codemirror/addon/search/jump-to-line.js');
require('codemirror/addon/search/match-highlighter.js');
require('codemirror/addon/search/matchesonscrollbar.js');
require('codemirror/addon/dialog/dialog.js');
//Trailing space highlighting
require('codemirror/addon/edit/trailingspace.js');
//Active line highlighting
require('codemirror/addon/selection/active-line.js');
//Auto-closing
//XML code folding is a requirement of the auto-closing tag feature and is not enabled
require('codemirror/addon/fold/xml-fold.js');
require('codemirror/addon/edit/closetag.js');
const foldCode = require('./fold-code');
foldCode.registerHomebreweryHelper(CodeMirror);
} }
const CodeEditor = createClass({ const CodeEditor = createClass({
@@ -49,145 +25,44 @@ const CodeEditor = createClass({
}; };
}, },
getInitialState : function() {
return {
docs : {}
};
},
componentDidMount : function() { componentDidMount : function() {
this.buildEditor(); this.buildEditor();
const newDoc = CodeMirror.Doc(this.props.value, this.props.language);
this.codeMirror.swapDoc(newDoc);
}, },
componentDidUpdate : function(prevProps) { componentDidUpdate : function(prevProps) {
if(prevProps.view !== this.props.view){ //view changed; swap documents if(prevProps.language !== this.props.language){ //rebuild editor when switching tabs
let newDoc; this.buildEditor();
}
if(!this.state.docs[this.props.view]) { if(this.codeMirror && this.codeMirror.getValue() != this.props.value) { //update editor contents if brew.text is changed from outside
newDoc = CodeMirror.Doc(this.props.value, this.props.language);
} else {
newDoc = this.state.docs[this.props.view];
}
const oldDoc = { [prevProps.view]: this.codeMirror.swapDoc(newDoc) };
this.setState((prevState)=>({
docs : _.merge({}, prevState.docs, oldDoc)
}));
this.props.rerenderParent();
} else if(this.codeMirror?.getValue() != this.props.value) { //update editor contents if brew.text is changed from outside
this.codeMirror.setValue(this.props.value); this.codeMirror.setValue(this.props.value);
} }
}, },
buildEditor : function() { buildEditor : function() {
this.codeMirror = CodeMirror(this.refs.editor, { this.codeMirror = CodeMirror(this.refs.editor, {
lineNumbers : true, value : this.props.value,
lineWrapping : this.props.wrap, lineNumbers : true,
indentWithTabs : true, lineWrapping : this.props.wrap,
tabSize : 2, mode : this.props.language, //TODO: CSS MODE DOESN'T SEEM TO LOAD PROPERLY
historyEventDelay : 250, indentWithTabs : true,
extraKeys : { tabSize : 2,
'Ctrl-B' : this.makeBold, extraKeys : {
'Cmd-B' : this.makeBold, 'Ctrl-B' : this.makeBold,
'Ctrl-I' : this.makeItalic, 'Cmd-B' : this.makeBold,
'Cmd-I' : this.makeItalic, 'Ctrl-I' : this.makeItalic,
'Ctrl-U' : this.makeUnderline, 'Cmd-I' : this.makeItalic,
'Cmd-U' : this.makeUnderline, 'Ctrl-M' : this.makeSpan,
'Ctrl-.' : this.makeNbsp, 'Cmd-M' : this.makeSpan,
'Cmd-.' : this.makeNbsp, 'Ctrl-/' : this.makeComment,
'Shift-Ctrl-.' : this.makeSpace, 'Cmd-/' : this.makeComment
'Shift-Cmd-.' : this.makeSpace,
'Shift-Ctrl-,' : this.removeSpace,
'Shift-Cmd-,' : this.removeSpace,
'Ctrl-M' : this.makeSpan,
'Cmd-M' : this.makeSpan,
'Shift-Ctrl-M' : this.makeDiv,
'Shift-Cmd-M' : this.makeDiv,
'Ctrl-/' : this.makeComment,
'Cmd-/' : this.makeComment,
'Ctrl-K' : this.makeLink,
'Cmd-K' : this.makeLink,
'Ctrl-L' : ()=>this.makeList('UL'),
'Cmd-L' : ()=>this.makeList('UL'),
'Shift-Ctrl-L' : ()=>this.makeList('OL'),
'Shift-Cmd-L' : ()=>this.makeList('OL'),
'Shift-Ctrl-1' : ()=>this.makeHeader(1),
'Shift-Ctrl-2' : ()=>this.makeHeader(2),
'Shift-Ctrl-3' : ()=>this.makeHeader(3),
'Shift-Ctrl-4' : ()=>this.makeHeader(4),
'Shift-Ctrl-5' : ()=>this.makeHeader(5),
'Shift-Ctrl-6' : ()=>this.makeHeader(6),
'Shift-Cmd-1' : ()=>this.makeHeader(1),
'Shift-Cmd-2' : ()=>this.makeHeader(2),
'Shift-Cmd-3' : ()=>this.makeHeader(3),
'Shift-Cmd-4' : ()=>this.makeHeader(4),
'Shift-Cmd-5' : ()=>this.makeHeader(5),
'Shift-Cmd-6' : ()=>this.makeHeader(6),
'Shift-Ctrl-Enter' : this.newColumn,
'Shift-Cmd-Enter' : this.newColumn,
'Ctrl-Enter' : this.newPage,
'Cmd-Enter' : this.newPage,
'Ctrl-F' : 'findPersistent',
'Cmd-F' : 'findPersistent',
'Shift-Enter' : 'findPersistentPrevious',
'Ctrl-[' : this.foldAllCode,
'Cmd-[' : this.foldAllCode,
'Ctrl-]' : this.unfoldAllCode,
'Cmd-]' : this.unfoldAllCode
},
foldGutter : true,
foldOptions : {
scanUp : true,
rangeFinder : CodeMirror.fold.homebrewery,
widget : (from, to)=>{
let text = '';
let currentLine = from.line;
const maxLength = 50;
while (currentLine <= to.line && text.length <= maxLength) {
text += this.codeMirror.getLine(currentLine);
if(currentLine < to.line)
text += ' ';
currentLine += 1;
}
text = text.trim();
if(text.length > maxLength)
text = `${text.substr(0, maxLength)}...`;
return `\u21A4 ${text} \u21A6`;
}
},
gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseTags : true,
styleActiveLine : true,
showTrailingSpace : true,
specialChars : / /,
specialCharPlaceholder : function(char) {
const el = document.createElement('span');
el.className = 'cm-space';
el.innerHTML = ' ';
return el;
} }
}); });
closeTag.autoCloseCurlyBraces(CodeMirror, this.codeMirror);
// Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works. // Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works.
this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());}); this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());});
this.updateSize(); this.updateSize();
}, },
makeHeader : function (number) {
const selection = this.codeMirror.getSelection();
const header = Array(number).fill('#').join('');
this.codeMirror.replaceSelection(`${header} ${selection}`, 'around');
const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch + selection.length + number + 1 });
},
makeBold : function() { makeBold : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '**' && selection.slice(-2) === '**'; const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '**' && selection.slice(-2) === '**';
this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `**${selection}**`, 'around'); this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `**${selection}**`, 'around');
@@ -198,55 +73,14 @@ const CodeEditor = createClass({
}, },
makeItalic : function() { makeItalic : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 1) === '*' && selection.slice(-1) === '*'; const selection = this.codeMirror.getSelection(), t = selection.slice(0, 1) === '_' && selection.slice(-1) === '_';
this.codeMirror.replaceSelection(t ? selection.slice(1, -1) : `*${selection}*`, 'around'); this.codeMirror.replaceSelection(t ? selection.slice(1, -1) : `_${selection}_`, 'around');
if(selection.length === 0){ if(selection.length === 0){
const cursor = this.codeMirror.getCursor(); const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 1 }); this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 1 });
} }
}, },
makeNbsp : function() {
this.codeMirror.replaceSelection('&nbsp;', 'end');
},
makeSpace : function() {
const selection = this.codeMirror.getSelection();
const t = selection.slice(0, 8) === '{{width:' && selection.slice(0 -4) === '% }}';
if(t){
const percent = parseInt(selection.slice(8, -4)) + 10;
this.codeMirror.replaceSelection(percent < 90 ? `{{width:${percent}% }}` : '{{width:100% }}', 'around');
} else {
this.codeMirror.replaceSelection(`{{width:10% }}`, 'around');
}
},
removeSpace : function() {
const selection = this.codeMirror.getSelection();
const t = selection.slice(0, 8) === '{{width:' && selection.slice(0 -4) === '% }}';
if(t){
const percent = parseInt(selection.slice(8, -4)) - 10;
this.codeMirror.replaceSelection(percent > 10 ? `{{width:${percent}% }}` : '', 'around');
}
},
newColumn : function() {
this.codeMirror.replaceSelection('\n\\column\n\n', 'end');
},
newPage : function() {
this.codeMirror.replaceSelection('\n\\page\n\n', 'end');
},
makeUnderline : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 3) === '<u>' && selection.slice(-4) === '</u>';
this.codeMirror.replaceSelection(t ? selection.slice(3, -4) : `<u>${selection}</u>`, 'around');
if(selection.length === 0){
const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 4 });
}
},
makeSpan : function() { makeSpan : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '{{' && selection.slice(-2) === '}}'; const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '{{' && selection.slice(-2) === '}}';
this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `{{ ${selection}}}`, 'around'); this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `{{ ${selection}}}`, 'around');
@@ -256,83 +90,15 @@ const CodeEditor = createClass({
} }
}, },
makeDiv : function() {
const selection = this.codeMirror.getSelection(), t = selection.slice(0, 2) === '{{' && selection.slice(-2) === '}}';
this.codeMirror.replaceSelection(t ? selection.slice(2, -2) : `{{\n${selection}\n}}`, 'around');
if(selection.length === 0){
const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line - 1, ch: cursor.ch }); // set to -2? if wanting to enter classes etc. if so, get rid of first \n when replacing selection
}
},
makeComment : function() { makeComment : function() {
let regex; const selection = this.codeMirror.getSelection(), t = selection.slice(0, 4) === '<!--' && selection.slice(-3) === '-->';
let cursorPos; this.codeMirror.replaceSelection(t ? selection.slice(4, -3) : `<!-- ${selection} -->`, 'around');
let newComment;
const selection = this.codeMirror.getSelection();
if(this.props.language === 'gfm'){
regex = /^\s*(<!--\s?)(.*?)(\s?-->)\s*$/gs;
cursorPos = 4;
newComment = `<!-- ${selection} -->`;
} else {
regex = /^\s*(\/\*\s?)(.*?)(\s?\*\/)\s*$/gs;
cursorPos = 3;
newComment = `/* ${selection} */`;
}
this.codeMirror.replaceSelection(regex.test(selection) == true ? selection.replace(regex, '$2') : newComment, 'around');
if(selection.length === 0){ if(selection.length === 0){
const cursor = this.codeMirror.getCursor(); const cursor = this.codeMirror.getCursor();
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - cursorPos }); this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 4 });
};
},
makeLink : function() {
const isLink = /^\[(.*)\]\((.*)\)$/;
const selection = this.codeMirror.getSelection().trim();
let match;
if(match = isLink.exec(selection)){
const altText = match[1];
const url = match[2];
this.codeMirror.replaceSelection(`${altText} ${url}`);
const cursor = this.codeMirror.getCursor();
this.codeMirror.setSelection({ line: cursor.line, ch: cursor.ch - url.length }, { line: cursor.line, ch: cursor.ch });
} else {
this.codeMirror.replaceSelection(`[${selection || 'alt text'}](url)`);
const cursor = this.codeMirror.getCursor();
this.codeMirror.setSelection({ line: cursor.line, ch: cursor.ch - 4 }, { line: cursor.line, ch: cursor.ch - 1 });
} }
}, },
makeList : function(listType) {
const selectionStart = this.codeMirror.getCursor('from'), selectionEnd = this.codeMirror.getCursor('to');
this.codeMirror.setSelection(
{ line: selectionStart.line, ch: 0 },
{ line: selectionEnd.line, ch: this.codeMirror.getLine(selectionEnd.line).length }
);
const newSelection = this.codeMirror.getSelection();
const regex = /^\d+\.\s|^-\s/gm;
if(newSelection.match(regex) != null){ // if selection IS A LIST
this.codeMirror.replaceSelection(newSelection.replace(regex, ''), 'around');
} else { // if selection IS NOT A LIST
listType == 'UL' ? this.codeMirror.replaceSelection(newSelection.replace(/^/gm, `- `), 'around') :
this.codeMirror.replaceSelection(newSelection.replace(/^/gm, (()=>{
let n = 1;
return ()=>{
return `${n++}. `;
};
})()), 'around');
}
},
foldAllCode : function() {
this.codeMirror.execCommand('foldAll');
},
unfoldAllCode : function() {
this.codeMirror.execCommand('unfoldAll');
},
//=-- Externally used -==// //=-- Externally used -==//
setCursorPosition : function(line, char){ setCursorPosition : function(line, char){
setTimeout(()=>{ setTimeout(()=>{
@@ -346,19 +112,10 @@ const CodeEditor = createClass({
updateSize : function(){ updateSize : function(){
this.codeMirror.refresh(); this.codeMirror.refresh();
}, },
redo : function(){
return this.codeMirror.redo();
},
undo : function(){
return this.codeMirror.undo();
},
historySize : function(){
return this.codeMirror.doc.historySize();
},
//----------------------// //----------------------//
render : function(){ render : function(){
return <div className='codeEditor' ref='editor' style={this.props.style}/>; return <div className='codeEditor' ref='editor' />;
} }
}); });

View File

@@ -1,22 +1,5 @@
@import (less) 'codemirror/lib/codemirror.css'; @import (less) 'codemirror/lib/codemirror.css';
@import (less) 'codemirror/addon/fold/foldgutter.css';
@import (less) 'codemirror/addon/search/matchesonscrollbar.css';
@import (less) 'codemirror/addon/dialog/dialog.css';
.codeEditor{ .codeEditor{
.CodeMirror-foldmarker {
font-family: inherit;
text-shadow: none;
font-weight: 600;
}
.cm-tab {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
}
.cm-trailingspace {
.cm-space {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
}
}
} }

View File

@@ -1,26 +0,0 @@
module.exports = {
registerHomebreweryHelper : function(CodeMirror) {
CodeMirror.registerHelper('fold', 'homebrewery', function(cm, start) {
const matcher = /^\\page.*/;
const prevLine = cm.getLine(start.line - 1);
if(start.line === cm.firstLine() || prevLine.match(matcher)) {
const lastLineNo = cm.lastLine();
let end = start.line;
while (end < lastLineNo) {
if(cm.getLine(end + 1).match(matcher))
break;
++end;
}
return {
from : CodeMirror.Pos(start.line, 0),
to : CodeMirror.Pos(end, cm.getLine(end).length)
};
}
return null;
});
}
};

View File

@@ -1,8 +1,7 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
const _ = require('lodash'); const _ = require('lodash');
const Marked = require('marked'); const Markdown = require('marked');
const MarkedExtendedTables = require('marked-extended-tables'); const renderer = new Markdown.Renderer();
const renderer = new Marked.Renderer();
//Processes the markdown within an HTML block if it's just a class-wrapper //Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (html) { renderer.html = function (html) {
@@ -10,7 +9,7 @@ renderer.html = function (html) {
const openTag = html.substring(0, html.indexOf('>')+1); const openTag = html.substring(0, html.indexOf('>')+1);
html = html.substring(html.indexOf('>')+1); html = html.substring(html.indexOf('>')+1);
html = html.substring(0, html.lastIndexOf('</div>')); html = html.substring(0, html.lastIndexOf('</div>'));
return `${openTag} ${Marked.parse(html)} </div>`; return `${openTag} ${Markdown(html)} </div>`;
} }
return html; return html;
}; };
@@ -236,10 +235,200 @@ const definitionLists = {
} }
}; };
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] }); const spanTable = {
Marked.use(MarkedExtendedTables()); name : 'spanTable',
Marked.use(mustacheInjectBlock); level : 'block', // Is this a block-level or inline-level tokenizer?
Marked.use({ smartypants: true }); start(src) { return src.match(/^\n *([^\n ].*\|.*)\n/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
//const regex = this.tokenizer.rules.block.table;
const regex = new RegExp('^ *([^\\n ].*\\|.*\\n(?: *[^\\s].*\\n)*?)' // Header
+ ' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)\\|?' // Align
+ '(?:\\n *((?:(?!\\n| {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})' // Cells
+ '(?:\\n+|$)| {0,3}#{1,6} | {0,3}>| {4}[^\\n]| {0,3}(?:`{3,}'
+ '(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n| {0,3}(?:[*+-]|1[.)]) |'
+ '<\\/?(?:address|article|aside|base|basefont|blockquote|body|'
+ 'caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?: +|\\n|\\/?>)|<(?:script|pre|style|textarea|!--)).*(?:\\n|$))*)\\n*|$)'); // Cells
const cap = regex.exec(src);
if(cap) {
const item = {
type : 'spanTable',
header : cap[1].replace(/\n$/, '').split('\n'),
align : cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
rows : cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
};
// Get first header row to determine how many columns
item.header[0] = splitCells(item.header[0]);
const colCount = item.header[0].reduce((length, header)=>{
return length + header.colspan;
}, 0);
if(colCount === item.align.length) {
item.raw = cap[0];
let i, j, k, row;
// Get alignment row (:---:)
let l = item.align.length;
for (i = 0; i < l; i++) {
if(/^ *-+: *$/.test(item.align[i])) {
item.align[i] = 'right';
} else if(/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = 'center';
} else if(/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = 'left';
} else {
item.align[i] = null;
}
}
// Get any remaining header rows
l = item.header.length;
for (i = 1; i < l; i++) {
item.header[i] = splitCells(item.header[i], colCount, item.header[i-1]);
}
// Get main table cells
l = item.rows.length;
for (i = 0; i < l; i++) {
item.rows[i] = splitCells(item.rows[i], colCount, item.rows[i-1]);
}
// header child tokens
l = item.header.length;
for (j = 0; j < l; j++) {
row = item.header[j];
for (k = 0; k < row.length; k++) {
row[k].tokens = [];
this.lexer.inlineTokens(row[k].text, row[k].tokens);
}
}
// cell child tokens
l = item.rows.length;
for (j = 0; j < l; j++) {
row = item.rows[j];
for (k = 0; k < row.length; k++) {
row[k].tokens = [];
this.lexer.inlineTokens(row[k].text, row[k].tokens);
}
}
return item;
}
}
},
renderer(token) {
let i, j, row, cell, col, text;
let output = `<table>`;
output += `<thead>`;
for (i = 0; i < token.header.length; i++) {
row = token.header[i];
let col = 0;
output += `<tr>`;
for (j = 0; j < row.length; j++) {
cell = row[j];
text = this.parser.parseInline(cell.tokens);
output += getTableCell(text, cell, 'th', token.align[col]);
col += cell.colspan;
}
output += `</tr>`;
}
output += `</thead>`;
if(token.rows.length) {
output += `<tbody>`;
for (i = 0; i < token.rows.length; i++) {
row = token.rows[i];
col = 0;
output += `<tr>`;
for (j = 0; j < row.length; j++) {
cell = row[j];
text = this.parser.parseInline(cell.tokens);
output += getTableCell(text, cell, 'td', token.align[col]);
col += cell.colspan;
}
output += `</tr>`;
}
output += `</tbody>`;
}
output += `</table>`;
return output;
}
};
const getTableCell = (text, cell, type, align)=>{
if(!cell.rowspan) {
return '';
}
const tag = `<${type}`
+ `${cell.colspan > 1 ? ` colspan=${cell.colspan}` : ''}`
+ `${cell.rowspan > 1 ? ` rowspan=${cell.rowspan}` : ''}`
+ `${align ? ` align=${align}` : ''}>`;
return `${tag + text}</${type}>\n`;
};
const splitCells = (tableRow, count, prevRow = [])=>{
const cells = [...tableRow.matchAll(/(?:[^|\\]|\\.?)+(?:\|+|$)/g)].map((x)=>x[0]);
// Remove first/last cell in a row if whitespace only and no leading/trailing pipe
if(!cells[0]?.trim()) { cells.shift(); }
if(!cells[cells.length - 1]?.trim()) { cells.pop(); }
let numCols = 0;
let i, j, trimmedCell, prevCell, prevCols;
for (i = 0; i < cells.length; i++) {
trimmedCell = cells[i].split(/\|+$/)[0];
cells[i] = {
rowspan : 1,
colspan : Math.max(cells[i].length - trimmedCell.length, 1),
text : trimmedCell.trim().replace(/\\\|/g, '|')
// display escaped pipes as normal character
};
// Handle Rowspan
if(trimmedCell.slice(-1) == '^' && prevRow.length) {
// Find matching cell in previous row
prevCols = 0;
for (j = 0; j < prevRow.length; j++) {
prevCell = prevRow[j];
if((prevCols == numCols) && (prevCell.colspan == cells[i].colspan)) {
// merge into matching cell in previous row (the "target")
cells[i].rowSpanTarget = prevCell.rowSpanTarget ?? prevCell;
cells[i].rowSpanTarget.text += ` ${cells[i].text.slice(0, -1)}`;
cells[i].rowSpanTarget.rowspan += 1;
cells[i].rowspan = 0;
break;
}
prevCols += prevCell.colspan;
if(prevCols > numCols)
break;
}
}
numCols += cells[i].colspan;
}
// Force main cell rows to match header column count
if(numCols > count) {
cells.splice(count);
} else {
while (numCols < count) {
cells.push({
colspan : 1,
text : ''
});
numCols += 1;
}
}
return cells;
};
Markdown.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, spanTable] });
Markdown.use(mustacheInjectBlock);
Markdown.use({ smartypants: true });
//Fix local links in the Preview iFrame to link inside the frame //Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) { renderer.link = function (href, title, text) {
@@ -320,15 +509,9 @@ const sanatizeScriptTags = (content)=>{
const tagTypes = ['div', 'span', 'a']; const tagTypes = ['div', 'span', 'a'];
const tagRegex = new RegExp(`(${ const tagRegex = new RegExp(`(${
_.map(tagTypes, (type)=>{ _.map(tagTypes, (type)=>{
return `\\<${type}\\b|\\</${type}>`; return `\\<${type}|\\</${type}>`;
}).join('|')})`, 'g'); }).join('|')})`, 'g');
// Special "void" tags that can be self-closed but don't need to be.
const voidTags = new Set([
'area', 'base', 'br', 'col', 'command', 'hr', 'img',
'input', 'keygen', 'link', 'meta', 'param', 'source'
]);
const processStyleTags = (string)=>{ const processStyleTags = (string)=>{
//split tags up. quotes can only occur right after colons. //split tags up. quotes can only occur right after colons.
//TODO: can we simplify to just split on commas? //TODO: can we simplify to just split on commas?
@@ -343,11 +526,11 @@ const processStyleTags = (string)=>{
}; };
module.exports = { module.exports = {
marked : Marked, marked : Markdown,
render : (rawBrewText)=>{ render : (rawBrewText)=>{
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`) rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`)
.replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`); .replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`);
return Marked.parse( return Markdown(
sanatizeScriptTags(rawBrewText), sanatizeScriptTags(rawBrewText),
{ renderer: renderer } { renderer: renderer }
); );
@@ -369,13 +552,6 @@ module.exports = {
}); });
} }
if(match === `</${type}>`){ if(match === `</${type}>`){
// Closing tag: Check we expect it to be closed.
// The accumulator may contain a sequence of voidable opening tags,
// over which we skip before checking validity of the close.
while (acc.length && voidTags.has(_.last(acc).type) && _.last(acc).type != type) {
acc.pop();
}
// Now check that what remains in the accumulator is valid.
if(!acc.length){ if(!acc.length){
errors.push({ errors.push({
line : lineNumber, line : lineNumber,

View File

@@ -99,15 +99,9 @@ const sanatizeScriptTags = (content)=>{
const tagTypes = ['div', 'span', 'a']; const tagTypes = ['div', 'span', 'a'];
const tagRegex = new RegExp(`(${ const tagRegex = new RegExp(`(${
_.map(tagTypes, (type)=>{ _.map(tagTypes, (type)=>{
return `\\<${type}\\b|\\</${type}>`; return `\\<${type}|\\</${type}>`;
}).join('|')})`, 'g'); }).join('|')})`, 'g');
// Special "void" tags that can be self-closed but don't need to be.
const voidTags = new Set([
'area', 'base', 'br', 'col', 'command', 'hr', 'img',
'input', 'keygen', 'link', 'meta', 'param', 'source'
]);
module.exports = { module.exports = {
marked : Markdown, marked : Markdown,
@@ -134,13 +128,6 @@ module.exports = {
}); });
} }
if(match === `</${type}>`){ if(match === `</${type}>`){
// Closing tag: Check we expect it to be closed.
// The accumulator may contain a sequence of voidable opening tags,
// over which we skip before checking validity of the close.
while (acc.length && voidTags.has(_.last(acc).type) && _.last(acc).type != type) {
acc.pop();
}
// Now check that what remains in the accumulator is valid.
if(!acc.length){ if(!acc.length){
errors.push({ errors.push({
line : lineNumber, line : lineNumber,

View File

@@ -68,46 +68,6 @@ const Nav = {
} }
}), }),
dropdown : createClass({
getInitialState : function() {
return {
showDropdown : false
};
},
handleDropdown : function(show){
this.setState({
showDropdown : show
});
},
renderDropdown : function(dropdownChildren){
if(!this.state.showDropdown) return null;
return (
<div className='navDropdown'>
{dropdownChildren}
</div>
);
},
render : function () {
const dropdownChildren = React.Children.map(this.props.children, (child, i)=>{
// Ignore the first child
if(i < 1) return;
return child;
});
return (
<div className='navDropdownContainer'
onMouseEnter={()=>this.handleDropdown(true)}
onMouseLeave={()=>this.handleDropdown(false)}>
{this.props.children[0]}
{this.renderDropdown(dropdownChildren)}
</div>
);
}
})
}; };

View File

@@ -1,11 +1,3 @@
@keyframes glideDropDown {
0% {transform : translate(0px, -100%);
opacity : 0;
background-color: #333;}
100% {transform : translate(0px, 0px);
opacity : 1;
background-color: #333;}
}
nav{ nav{
background-color : #333; background-color : #333;
.navContent{ .navContent{
@@ -86,25 +78,4 @@ nav{
.navSection:last-child .navItem{ .navSection:last-child .navItem{
border-left : 1px solid #666; border-left : 1px solid #666;
} }
.navDropdownContainer{
position: relative;
.navDropdown {
position : absolute;
top : 28px;
left : 0px;
z-index : 10000;
width : 100%;
.navItem{
animation-name: glideDropDown;
animation-duration: 0.4s;
position : relative;
display : block;
width : 100%;
vertical-align : middle;
padding : 8px 5px;
border : 1px solid #888;
border-bottom : 0;
}
}
}
} }

View File

@@ -40,12 +40,8 @@ const SplitPane = createClass({
}, },
handleMove : function(e){ handleMove : function(e){
if(!this.state.isDragging) return; if(!this.state.isDragging) return;
const minWidth = 1;
const maxWidth = window.innerWidth - 13;
const newSize = Math.min(maxWidth, Math.max(minWidth, e.pageX));
this.setState({ this.setState({
size : newSize size : e.pageX
}); });
}, },
/* /*

View File

@@ -28,8 +28,5 @@
color : #666; color : #666;
} }
} }
&:hover{
background-color: #999;
}
} }
} }

View File

@@ -79,7 +79,7 @@ body {
p{ p{
overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
display : block; display : block;
line-height : 1.25em; line-height : 1.3em;
&+* { &+* {
margin-top : 0.325cm; margin-top : 0.325cm;
} }
@@ -90,14 +90,14 @@ body {
ul{ ul{
margin-bottom : 0.8em; margin-bottom : 0.8em;
padding-left : 1.4em; padding-left : 1.4em;
line-height : 1.25em; line-height : 1.3em;
list-style-position : outside; list-style-position : outside;
list-style-type : disc; list-style-type : disc;
} }
ol{ ol{
margin-bottom : 0.8em; margin-bottom : 0.8em;
padding-left : 1.4em; padding-left : 1.4em;
line-height : 1.25em; line-height : 1.3em;
list-style-position : outside; list-style-position : outside;
list-style-type : decimal; list-style-type : decimal;
} }
@@ -146,9 +146,9 @@ body {
font-size : 3.5cm; font-size : 3.5cm;
padding-left : 40px; //Allow background color to extend into margins padding-left : 40px; //Allow background color to extend into margins
margin-left : -40px; margin-left : -40px;
margin-top : -0.3cm; margin-top :-0.3cm;
padding-bottom : 2px; padding-bottom :2px;
margin-bottom : -20px; margin-bottom :-20px;
background-image : linear-gradient(-45deg, #322814, #998250, #322814); background-image : linear-gradient(-45deg, #322814, #998250, #322814);
background-clip : text; background-clip : text;
-webkit-background-clip : text; -webkit-background-clip : text;
@@ -162,20 +162,17 @@ body {
//margin-top : 0px; //Font is misaligned. Shift up slightly //margin-top : 0px; //Font is misaligned. Shift up slightly
//margin-bottom : 0.05cm; //margin-bottom : 0.05cm;
font-size : 0.75cm; font-size : 0.75cm;
line-height : 0.988em; //Font is misaligned. Shift up slightly
} }
h3{ h3{
//margin-top : -0.1cm; //Font is misaligned. Shift up slightly //margin-top : -0.1cm; //Font is misaligned. Shift up slightly
//margin-bottom : 0.1cm; //margin-bottom : 0.1cm;
font-size : 0.575cm; font-size : 0.575cm;
border-bottom : 2px solid @headerUnderline; border-bottom : 2px solid @headerUnderline;
line-height : 0.995em; //Font is misaligned. Shift up slightly
} }
h4{ h4{
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly //margin-top : -0.02cm; //Font is misaligned. Shift up slightly
//margin-bottom : 0.02cm; //margin-bottom : 0.02cm;
font-size : 0.458cm; font-size : 0.458cm;
line-height : 0.971em; //Font is misaligned. Shift up slightly
} }
h5{ h5{
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly //margin-top : -0.02cm; //Font is misaligned. Shift up slightly
@@ -183,7 +180,6 @@ body {
font-family : ScalySansSmallCapsRemake; font-family : ScalySansSmallCapsRemake;
font-size : 0.423cm; font-size : 0.423cm;
font-weight : 900; font-weight : 900;
line-height : 0.951em; //Font is misaligned. Shift up slightly
& + * { & + * {
margin-top : 0.2cm; margin-top : 0.2cm;
} }
@@ -242,6 +238,9 @@ body {
display : block; display : block;
padding-bottom : 0px; padding-bottom : 0px;
} }
p + p {
padding-top : .8em;
}
:last-child { :last-child {
margin-bottom : 0; margin-bottom : 0;
} }
@@ -272,6 +271,9 @@ body {
padding-bottom : 0px; padding-bottom : 0px;
line-height : 1.5em; line-height : 1.5em;
} }
p + p {
padding-top : .8em;
}
:last-child { :last-child {
margin-bottom : 0; margin-bottom : 0;
} }
@@ -283,7 +285,6 @@ body {
/* Arist Credit */ /* Arist Credit */
.artist { .artist {
position : absolute; position : absolute;
width : auto;
text-align : center; text-align : center;
font-family : WalterTurncoat; font-family : WalterTurncoat;
font-size : 0.27cm; font-size : 0.27cm;
@@ -308,21 +309,21 @@ body {
/* Watermark */ /* Watermark */
.watermark { .watermark {
display : grid !important; display : grid !important;
place-items : center; place-items : center;
justify-content : center; justify-content : center;
position : absolute; position : absolute;
top : 0; top : 0;
left : 0; left : 0;
width : 100%; width : 100%;
height : 100%; height : 100%;
font-size : 120px; font-size : 120px;
text-transform : uppercase; text-transform : uppercase;
color : black; color : black;
mix-blend-mode : overlay; mix-blend-mode : overlay;
opacity : 30%; opacity : 30%;
transform : rotate(-45deg); transform : rotate(-45deg);
z-index : 500; z-index : 500;
p { p {
margin-bottom : none; margin-bottom : none;
} }
@@ -374,15 +375,25 @@ body {
background-attachment : fixed; background-attachment : fixed;
filter : drop-shadow(1px 4px 6px #888); filter : drop-shadow(1px 4px 6px #888);
padding : 4px 2px; padding : 4px 2px;
margin-left : -0.16cm; margin-left : -6px;
margin-right : -0.16cm; margin-right : -6px;
width : calc(100% + 0.32cm);
} }
position : relative; position : relative;
padding : 0px; padding : 0px;
margin-bottom : 0.325cm; margin-bottom : 0.325cm;
p{
margin-bottom : 0.3cm;
}
p+p {
margin-top : 0; //May not be needed
text-indent : 0;
}
p:last-of-type {
margin-bottom: 0;
}
//Headers //Headers
h2{ h2{
font-size : 0.62cm; font-size : 0.62cm;
@@ -398,7 +409,7 @@ body {
font-weight : 800; font-weight : 800;
font-variant : small-caps; font-variant : small-caps;
border-bottom : 2px solid @headerText; border-bottom : 2px solid @headerText;
// margin-top : 0.05cm; //Font is misaligned. Shift up slightly margin-top : 0.05cm;
padding-bottom : 0.05cm; padding-bottom : 0.05cm;
} }
@@ -412,17 +423,12 @@ body {
border : none; border : none;
} }
//Attribute Lists - All text between HRs is red //Attribute Lists
hr ~ :is(dl,p) { dl {
color : @headerText; color : @headerText;
} }
hr:last-of-type { hr:last-of-type~dl{
& ~ :is(dl,p) { color : inherit; // After the HRs, hanging indents remain black.
color : inherit; // After the HRs, reset text to black
}
& + * {
margin-top : 0.325cm; // Space after last HR
}
} }
// Monster Ability table // Monster Ability table
@@ -441,10 +447,6 @@ body {
padding: 0px; padding: 0px;
} }
} }
:last-child {
margin-bottom : 0;
}
} }
//Full Width //Full Width
@@ -511,8 +513,7 @@ body {
color : #58180d; color : #58180d;
background-color : #faf7ea; background-color : #faf7ea;
border-radius : 4px; border-radius : 4px;
white-space : pre-wrap; white-space : pre-wrap
overflow-wrap : break-word;
} }
pre code{ pre code{
@@ -580,7 +581,7 @@ body {
} }
p, ul{ p, ul{
font-size : 0.352cm; font-size : 0.352cm;
line-height : 1.265em; line-height : 1.3em;
} }
ul{ ul{
margin-bottom : 0.5em; margin-bottom : 0.5em;
@@ -608,7 +609,6 @@ body {
margin-bottom : 1.05cm; margin-bottom : 1.05cm;
margin-left : -0.1cm; margin-left : -0.1cm;
margin-right : -0.1cm; margin-right : -0.1cm;
width : calc(100% + 0.2cm);
border-collapse : separate; border-collapse : separate;
background-color : white; background-color : white;
border : initial; border : initial;
@@ -653,7 +653,7 @@ body {
break-inside : avoid; break-inside : avoid;
h1 { h1 {
text-align : center; text-align : center;
margin-bottom : 0.3cm; margin-bottom : 0cm;
} }
a{ a{
display : table; display : table;
@@ -664,12 +664,14 @@ body {
} }
} }
h4 { h4 {
margin-top : 0.2cm; margin-top : 0.14cm;
line-height : 0.4cm;
& + ul li { & + ul li {
line-height: 1.2em; line-height: 1.2em;
} }
} }
& > ul {
margin-top: 0.52cm;
}
ul{ ul{
padding-left : 0; padding-left : 0;
list-style-type : none; list-style-type : none;
@@ -722,9 +724,7 @@ body {
.block { .block {
break-inside : avoid; break-inside : avoid;
display : inline-block; display : inline-block;
.page :where(&) { min-width : 100%;
width : 100%;
}
//-webkit-transform : translateZ(0); //Prevents shadows from breaking across columns //-webkit-transform : translateZ(0); //Prevents shadows from breaking across columns
} }
.inline-block { .inline-block {
@@ -738,7 +738,7 @@ body {
// *****************************/ // *****************************/
.page { .page {
dl { dl {
line-height : 1.25em; line-height : 1.3em;
padding-left : 1em; padding-left : 1em;
white-space : pre-line; white-space : pre-line;
& + * { & + * {
@@ -768,8 +768,10 @@ body {
// *****************************/ // *****************************/
.page { .page {
.blank { .blank {
height : 1em; height: 0.75em;
margin-top : 0; }
p + .blank {
margin-top: -1em;
} }
} }

View File

@@ -63,7 +63,7 @@ body {
// *****************************/ // *****************************/
p{ p{
padding-bottom : 0.8em; padding-bottom : 0.8em;
line-height : 1.269em; line-height : 1.3em;
&+p{ &+p{
margin-top : -0.8em; margin-top : -0.8em;
} }
@@ -71,14 +71,14 @@ body {
ul{ ul{
margin-bottom : 0.8em; margin-bottom : 0.8em;
padding-left : 1.4em; padding-left : 1.4em;
line-height : 1.269em; line-height : 1.3em;
list-style-position : outside; list-style-position : outside;
list-style-type : disc; list-style-type : disc;
} }
ol{ ol{
margin-bottom : 0.8em; margin-bottom : 0.8em;
padding-left : 1.4em; padding-left : 1.4em;
line-height : 1.269em; line-height : 1.3em;
list-style-position : outside; list-style-position : outside;
list-style-type : decimal; list-style-type : decimal;
} }
@@ -126,7 +126,7 @@ body {
font-family : Solberry; font-family : Solberry;
font-size : 10em; font-size : 10em;
color : #222; color : #222;
line-height : 0.795em; line-height : 0.8em;
} }
} }
h2{ h2{
@@ -191,7 +191,7 @@ body {
box-shadow : 1px 4px 14px #888; box-shadow : 1px 4px 14px #888;
p, ul{ p, ul{
font-size : 0.352cm; font-size : 0.352cm;
line-height : 1.083em; line-height : 1.1em;
} }
} }
//If a note starts a column, give it space at the top to render border //If a note starts a column, give it space at the top to render border
@@ -371,7 +371,7 @@ body {
} }
p, ul{ p, ul{
font-size : 0.352cm; font-size : 0.352cm;
line-height : 1.263em; line-height : 1.3em;
} }
ul{ ul{
margin-bottom : 0.5em; margin-bottom : 0.5em;
@@ -425,7 +425,7 @@ body {
p{ p{
display : block; display : block;
padding-bottom : 0px; padding-bottom : 0px;
line-height : 1.47em; line-height : 1.5em;
} }
p + p { p + p {
padding-top : .8em; padding-top : .8em;
@@ -457,7 +457,7 @@ body {
p, p + p { p, p + p {
margin : unset; margin : unset;
text-indent : unset; text-indent : unset;
line-height : 0.941em; line-height : 1em;
} }
h5 { h5 {
font-size : 1.3em; font-size : 1.3em;