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

Compare commits

...

405 Commits

Author SHA1 Message Date
Trevor Buckner
b72357096a Rebuild package-lock 2025-04-15 14:58:16 +00:00
Trevor Buckner
99fb8faf96 Merge pull request #4044 from dbolack-ab/marked-justifiedParagraphs
Migrate the justified paragraphs extension to an NPM
2025-04-14 17:34:51 -04:00
Trevor Buckner
519da0a5c0 Remove incorrect extension calls 2025-04-14 16:44:25 -04:00
Trevor Buckner
814a70b704 Merge branch 'master' into marked-justifiedParagraphs 2025-04-14 16:37:11 -04:00
Trevor Buckner
ff72f6cbd1 Merge pull request #4046 from dbolack-ab/marked-nonbreaking-spaces
Migrate nonbreaking spaces to npm module.
2025-04-14 14:11:54 -04:00
Trevor Buckner
511f33c44d Merge branch 'master' into marked-nonbreaking-spaces 2025-04-14 13:58:14 -04:00
Trevor Buckner
b455165fd3 Merge pull request #4144 from naturalcrit/dependabot/npm_and_yarn/babel/runtime-7.27.0
Bump @babel/runtime from 7.25.9 to 7.27.0
2025-04-11 22:47:50 -04:00
Trevor Buckner
7be03ab738 Merge branch 'master' into dependabot/npm_and_yarn/babel/runtime-7.27.0 2025-04-11 22:22:13 -04:00
Víctor Losada Hernández
b287163ef7 Merge pull request #3421 from G-Ambatte/addLockRoutes-#3326
Add lock routes
2025-04-11 12:12:32 +02:00
G.Ambatte
1429674013 Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2025-04-11 00:26:24 +12:00
G.Ambatte
5576a76731 Close malformed string 2025-04-11 00:26:19 +12:00
Víctor Losada Hernández
9c1a0fd798 Merge branch 'master' into addLockRoutes-#3326 2025-04-10 13:46:15 +02:00
dependabot[bot]
70afa96bb0 Bump @babel/runtime from 7.25.9 to 7.27.0
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.25.9 to 7.27.0.
- [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.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 07:56:27 +00:00
Víctor Losada Hernández
808f4dd9a0 Merge pull request #3870 from dbolack-ab/localSnippetEditor
Snippet editor tab
2025-04-10 09:55:18 +02:00
G.Ambatte
8e74ba07fe Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2025-04-10 19:15:38 +12:00
G.Ambatte
0c33df1cd6 Add tests for lock API functions 2025-04-10 19:15:34 +12:00
G.Ambatte
73bb6acc14 Update locked brew test for new structure 2025-04-10 10:21:35 +12:00
G.Ambatte
654c44ebc9 Change Locked Brew Error Page to use standardized author list
Co-authored-by: Víctor Losada Hernández <5ecleric.naturalcrit@gmail.com>
2025-04-10 08:39:35 +12:00
David Bolack
a6703ef731 Clear up confusing commment and consolidate style tag. 2025-04-09 13:30:03 -05:00
G.Ambatte
04defb97b0 Tweak styling for Overwrite checkbox 2025-04-09 20:10:48 +12:00
G.Ambatte
da4f6c9307 Load lock details to the Lock Brew form 2025-04-09 17:54:51 +12:00
David Bolack
c3b0311a4b Merge branch 'marked-nonbreaking-spaces' of github.com:dbolack-ab/homebrewery into marked-nonbreaking-spaces 2025-04-08 21:50:04 -05:00
David Bolack
196f290320 Merge branch 'master' into marked-nonbreaking-spaces 2025-04-08 21:49:07 -05:00
David Bolack
b1fec69d8f Merge branch 'master' into marked-justifiedParagraphs 2025-04-08 21:47:32 -05:00
David Bolack
bd9d9d4ab6 Merge branch 'master' into localSnippetEditor 2025-04-08 20:35:11 -05:00
David Bolack
c6cd6e9864 A little bit of cleanup and structure flattening
Fixes failed tests.
2025-04-08 20:29:32 -05:00
G.Ambatte
5e7e314baa Update fields returned for Lock and Review Tables 2025-04-09 11:38:57 +12:00
G.Ambatte
f2b995660a Merge branch 'master' into addLockRoutes-#3326 2025-04-09 11:36:40 +12:00
G.Ambatte
95f44f4460 Revert unnecessary change in app.js 2025-04-09 11:04:57 +12:00
G.Ambatte
bd68b9c0cb Remove unnecessary variable 2025-04-09 10:48:02 +12:00
G.Ambatte
b19d05fbf7 Remove commented out console.logs 2025-04-09 10:41:58 +12:00
G.Ambatte
dc724492ef Prevent BrewUtils from loading when it is not the current Admin tab 2025-04-09 10:26:10 +12:00
G.Ambatte
e3de7b9f01 Tweak lock styling 2025-04-09 10:17:35 +12:00
G.Ambatte
be2f1786b5 Add authors to locked brew error message 2025-04-09 09:59:34 +12:00
Trevor Buckner
99a3131724 Merge pull request #4132 from naturalcrit/dependabot/npm_and_yarn/express-5.1.0
Bump express from 4.21.2 to 5.1.0
2025-04-08 17:39:00 -04:00
dependabot[bot]
0dbbc469e1 Bump express from 4.21.2 to 5.1.0
Bumps [express](https://github.com/expressjs/express) from 4.21.2 to 5.1.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.2...v5.1.0)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 21:12:15 +00:00
Trevor Buckner
d5a80cc89a Merge pull request #4123 from naturalcrit/dependabot/npm_and_yarn/body-parser-2.2.0
Bump body-parser from 1.20.3 to 2.2.0
2025-04-08 17:11:01 -04:00
dependabot[bot]
0be5c6c576 Bump body-parser from 1.20.3 to 2.2.0
Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.3 to 2.2.0.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.3...v2.2.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 20:57:29 +00:00
Trevor Buckner
d7b478e830 Merge pull request #4141 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recommended-16.0.0
Bump stylelint-config-recommended from 15.0.0 to 16.0.0
2025-04-08 16:56:14 -04:00
Trevor Buckner
3ce76f450c Merge branch 'master' into dependabot/npm_and_yarn/stylelint-config-recommended-16.0.0 2025-04-08 14:48:22 -04:00
Trevor Buckner
ad04c68596 Merge pull request #4136 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-react-7.37.5
Bump eslint-plugin-react from 7.37.4 to 7.37.5
2025-04-08 14:48:03 -04:00
Trevor Buckner
0d8bf5f0aa Merge branch 'master' into dependabot/npm_and_yarn/eslint-plugin-react-7.37.5 2025-04-08 14:43:11 -04:00
dependabot[bot]
075fdb194e Bump stylelint-config-recommended from 15.0.0 to 16.0.0
Bumps [stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended) from 15.0.0 to 16.0.0.
- [Release notes](https://github.com/stylelint/stylelint-config-recommended/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-recommended/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-recommended/compare/15.0.0...16.0.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recommended
  dependency-version: 16.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 18:41:53 +00:00
Trevor Buckner
6ab1b7705a Merge pull request #4138 from naturalcrit/dependabot/npm_and_yarn/react-router-7.5.0
Bump react-router from 7.4.0 to 7.5.0
2025-04-08 14:40:31 -04:00
Trevor Buckner
9151b8c575 Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.0 2025-04-08 14:04:16 -04:00
Trevor Buckner
d5186a03e9 Merge pull request #4140 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.18.0
Bump stylelint from 16.17.0 to 16.18.0
2025-04-08 14:04:00 -04:00
dependabot[bot]
b461ac0a68 Bump react-router from 7.4.0 to 7.5.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.4.0 to 7.5.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.5.0/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:55:07 +00:00
Trevor Buckner
477c9d1555 Merge branch 'master' into dependabot/npm_and_yarn/stylelint-16.18.0 2025-04-08 13:54:07 -04:00
Trevor Buckner
ea365e18f4 Merge pull request #4139 from naturalcrit/dependabot/npm_and_yarn/eslint-9.24.0
Bump eslint from 9.23.0 to 9.24.0
2025-04-08 13:53:45 -04:00
dependabot[bot]
512eedfc39 Bump stylelint from 16.17.0 to 16.18.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.17.0 to 16.18.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.17.0...16.18.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.18.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:51:04 +00:00
dependabot[bot]
518bc7030d Bump eslint from 9.23.0 to 9.24.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.23.0 to 9.24.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.23.0...v9.24.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.24.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:51:04 +00:00
Trevor Buckner
ae8dc61423 Merge pull request #4135 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.13.2
Bump mongoose from 8.13.0 to 8.13.2
2025-04-08 13:49:41 -04:00
Trevor Buckner
b89532caa1 Merge branch 'master' into dependabot/npm_and_yarn/eslint-plugin-react-7.37.5 2025-04-08 13:48:31 -04:00
dependabot[bot]
9a57b407a5 Bump mongoose from 8.13.0 to 8.13.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.13.0 to 8.13.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.13.0...8.13.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:46:52 +00:00
Trevor Buckner
776de3618a Merge pull request #4143 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.8
Bump marked from 15.0.0 to 15.0.8
2025-04-08 13:45:35 -04:00
Trevor Buckner
b4d575c383 Merge branch 'master' into dependabot/npm_and_yarn/eslint-plugin-react-7.37.5 2025-04-08 13:39:15 -04:00
Trevor Buckner
dc4382d067 Merge branch 'master' into dependabot/npm_and_yarn/marked-15.0.8 2025-04-08 13:38:56 -04:00
Trevor Buckner
08e00aa38b Merge pull request #4104 from naturalcrit/fix-editor-overflow
CodeEditor overflow issue fix
2025-04-08 13:30:08 -04:00
dependabot[bot]
08946ce5d4 Bump marked from 15.0.0 to 15.0.8
Bumps [marked](https://github.com/markedjs/marked) from 15.0.0 to 15.0.8.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v15.0.0...v15.0.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:22:50 +00:00
dependabot[bot]
75212511d2 Bump eslint-plugin-react from 7.37.4 to 7.37.5
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.4 to 7.37.5.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.4...v7.37.5)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-version: 7.37.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:22:37 +00:00
Trevor Buckner
79845f2d63 Merge branch 'master' into fix-editor-overflow 2025-04-08 13:22:26 -04:00
Trevor Buckner
c982ff546c Merge pull request #4088 from G-Ambatte/autoPageNumberBrewVariable
Add pageNumber brew variable
2025-04-08 13:21:14 -04:00
David Bolack
9f56d100aa change tab 2025-04-08 00:32:11 -05:00
David Bolack
d0c3765f8f Move Snippets store to metadata block.
Note this still stores the snippets as a string for the passed about brew object.
2025-04-07 23:22:47 -05:00
G.Ambatte
1ded1cad5a Change accessType check 2025-04-08 09:28:54 +12:00
G.Ambatte
ec6258a2a5 Add overwrite option to API function 2025-04-07 22:02:53 +12:00
G.Ambatte
f8566392f6 Add overwrite option for updating locks 2025-04-07 22:02:28 +12:00
G.Ambatte
7a1042fedd Stylelint fixes 2025-04-07 22:02:08 +12:00
G.Ambatte
61efc2d152 Tweak styling 2025-04-07 21:12:37 +12:00
G.Ambatte
f74c2049a7 Rename Edit and Share page message fields 2025-04-07 20:54:44 +12:00
G.Ambatte
7451dda632 Add locked brews table 2025-04-07 20:54:21 +12:00
G.Ambatte
ab9b151b8a Add API route to return all locked brews 2025-04-07 20:52:35 +12:00
G.Ambatte
26aa302714 Remove debugging test route 2025-04-07 20:04:45 +12:00
G.Ambatte
e2f2b2962f Revert request middleware change as it is no longer necessary 2025-04-07 17:34:31 +12:00
G.Ambatte
a218b87215 Shift remaining lock API functions to use throw 2025-04-07 16:32:33 +12:00
G.Ambatte
ef6f022ea3 Tweak lock notification styling 2025-04-07 15:59:54 +12:00
G.Ambatte
a594d45611 Remove unnecessary default option 2025-04-07 15:59:34 +12:00
G.Ambatte
4c4a023f34 Fix lock notification message 2025-04-07 15:59:06 +12:00
G.Ambatte
1e35e1096f Reduce data retrieved for brews with requested reviews 2025-04-07 14:39:33 +12:00
G.Ambatte
bd145f17da Tweak styling 2025-04-07 14:38:37 +12:00
G.Ambatte
99c342f19b Use throw in Lock API calls 2025-04-07 14:38:25 +12:00
G.Ambatte
0bca3393d4 Add LOCK header in comment 2025-04-07 08:39:33 +12:00
G.Ambatte
41bd27b573 Refactor /api/lock/review/remove 2025-04-07 08:37:35 +12:00
G.Ambatte
30430cb8cb Refactor /admin/lock/review/request 2025-04-07 08:36:33 +12:00
G.Ambatte
3cf98617f5 Refactor /api/lock/reviews 2025-04-07 08:33:39 +12:00
G.Ambatte
fa4b2ae0e3 Refactor /api/lock 2025-04-07 08:27:32 +12:00
G.Ambatte
e2b38829f2 Refactor /api/lock/count 2025-04-07 08:25:42 +12:00
G.Ambatte
0a4ac7a35a Adjust location of package.json 2025-04-06 19:48:32 +12:00
G.Ambatte
cb060ae8b1 Fix state of "Request Review" button on page load 2025-04-06 19:26:39 +12:00
G.Ambatte
98edd2740f Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2025-04-06 19:09:14 +12:00
G.Ambatte
82e711a344 Shift request calls to import 2025-04-06 19:09:11 +12:00
G.Ambatte
2166d55878 Merge branch 'master' into autoPageNumberBrewVariable 2025-04-06 17:27:17 +12:00
David Bolack
8e8f520eaa Fix Highlighting issue with new brew sample snippets 2025-03-31 20:50:54 -05:00
Víctor Losada Hernández
4eeaa7c650 default text for snippet tab 2025-03-31 17:11:25 +02:00
Víctor Losada Hernández
f5fc106d01 pixel frame fix 2025-03-31 00:52:41 +02:00
Víctor Losada Hernández
de1773361a final css fix 2025-03-31 00:40:00 +02:00
Víctor Losada Hernández
b9b45632b0 fix package-lock 2025-03-31 00:19:43 +02:00
Víctor Losada Hernández
2ce7c6c2be css breakpoint changed 2025-03-31 00:14:03 +02:00
Víctor Losada Hernández
4137d0dd82 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into localSnippetEditor 2025-03-31 00:12:58 +02:00
G.Ambatte
29bd8b45c3 Merge branch 'master' into addLockRoutes-#3326 2025-03-30 15:28:36 +13:00
David Bolack
9d1601f424 Seems to be working - no idea why... 2025-03-29 19:14:10 -05:00
David Bolack
7525e087ff Regression Fix WIP 2025-03-29 18:47:03 -05:00
Víctor Losada Hernández
be4991a419 Merge pull request #4124 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.17.0
Bump stylelint from 16.16.0 to 16.17.0
2025-03-27 10:25:37 +01:00
G.Ambatte
ac89f428b2 Post merge fixes 2025-03-27 18:25:56 +13:00
G.Ambatte
7765cb31bf Merge branch 'master' into addLockRoutes-#3326 2025-03-27 17:43:21 +13:00
dependabot[bot]
8729407da6 Bump stylelint from 16.16.0 to 16.17.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.16.0 to 16.17.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.16.0...16.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-27 03:39:34 +00:00
Trevor Buckner
eac87b65d8 Update tests and custom link renderer
v15 changes where html gets escaped and escapes plain text more consistently. Needed to update tests to match.
2025-03-26 19:26:20 -04:00
G.Ambatte
848c68689d Add NaN custom pageNumber test 2025-03-27 12:09:58 +13:00
G.Ambatte
65001c44e6 Add tests for variable page numbering 2025-03-27 12:03:22 +13:00
G.Ambatte
6ec37d3fa4 Rename HB_PageNumber to HB_pageNumber 2025-03-27 12:03:00 +13:00
G.Ambatte
9f68d60703 Merge branch 'master' into autoPageNumberBrewVariable 2025-03-27 11:48:20 +13:00
Trevor Buckner
fc8654bff5 Merge pull request #4107 from dbolack-ab/issue_4105
"Workaround" for bad \page mustache blocks
2025-03-26 18:05:14 -04:00
Trevor Buckner
ebbf7cf3a2 Merge branch 'master' into issue_4105 2025-03-26 18:03:59 -04:00
Trevor Buckner
225fcef291 up Marked to v14.1.4 2025-03-26 18:03:22 -04:00
Trevor Buckner
b460acad0d Merge pull request #4091 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.10
Bump @babel/core from 7.26.9 to 7.26.10
2025-03-26 11:49:24 -04:00
dependabot[bot]
1c1808378b Bump @babel/core from 7.26.9 to 7.26.10
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.9 to 7.26.10.
- [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.26.10/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:47:08 +00:00
Trevor Buckner
2d25e08040 Merge pull request #4090 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.10
Bump @babel/plugin-transform-runtime from 7.26.9 to 7.26.10
2025-03-26 11:45:52 -04:00
dependabot[bot]
8fc1919d7c Bump @babel/plugin-transform-runtime from 7.26.9 to 7.26.10
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.26.9 to 7.26.10.
- [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.26.10/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:40:50 +00:00
Trevor Buckner
790c17ad53 Merge pull request #4118 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.13.0
Bump mongoose from 8.12.1 to 8.13.0
2025-03-26 11:39:35 -04:00
dependabot[bot]
d315e4f008 Bump mongoose from 8.12.1 to 8.13.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.12.1 to 8.13.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.12.1...8.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:28:21 +00:00
Trevor Buckner
2bfc41ce30 Merge pull request #4114 from naturalcrit/dependabot/npm_and_yarn/eslint-9.23.0
Bump eslint from 9.22.0 to 9.23.0
2025-03-26 11:27:01 -04:00
dependabot[bot]
cdacaac049 Bump eslint from 9.22.0 to 9.23.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.22.0 to 9.23.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.22.0...v9.23.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:26:53 +00:00
Trevor Buckner
da88fd0b3f Merge pull request #4092 from naturalcrit/dependabot/npm_and_yarn/superagent-10.2.0
Bump superagent from 10.1.1 to 10.2.0
2025-03-26 11:25:28 -04:00
dependabot[bot]
0095e4582b Bump superagent from 10.1.1 to 10.2.0
Bumps [superagent](https://github.com/ladjs/superagent) from 10.1.1 to 10.2.0.
- [Release notes](https://github.com/ladjs/superagent/releases)
- [Changelog](https://github.com/ladjs/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/ladjs/superagent/compare/v10.1.1...v10.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:18:49 +00:00
Trevor Buckner
5cf0945fa7 Merge pull request #4108 from naturalcrit/dependabot/npm_and_yarn/react-router-7.4.0
Bump react-router from 7.3.0 to 7.4.0
2025-03-26 11:17:24 -04:00
dependabot[bot]
8e10e9dea9 Bump react-router from 7.3.0 to 7.4.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.4.0/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:13:56 +00:00
Trevor Buckner
07f6439093 Merge pull request #4103 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.5
Bump nanoid from 5.1.3 to 5.1.5
2025-03-26 11:12:32 -04:00
Trevor Buckner
8c979b8545 Merge branch 'master' into dependabot/npm_and_yarn/nanoid-5.1.5 2025-03-26 11:10:11 -04:00
Trevor Buckner
4f3929c658 Merge pull request #4111 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-11.0.0
Bump @googleapis/drive from 8.16.0 to 11.0.0
2025-03-26 11:09:52 -04:00
dependabot[bot]
c8cf9e3002 Bump @googleapis/drive from 8.16.0 to 11.0.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 8.16.0 to 11.0.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/11.0.0/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/drive-v8.16.0...11.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:06:30 +00:00
dependabot[bot]
7bc323c92c Bump nanoid from 5.1.3 to 5.1.5
Bumps [nanoid](https://github.com/ai/nanoid) from 5.1.3 to 5.1.5.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.3...5.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:06:16 +00:00
Trevor Buckner
4d141fa6a3 Merge pull request #4110 from naturalcrit/dependabot/npm_and_yarn/supertest-7.1.0
Bump supertest from 7.0.0 to 7.1.0
2025-03-26 11:04:54 -04:00
Trevor Buckner
25d1db5584 Merge branch 'master' into dependabot/npm_and_yarn/supertest-7.1.0 2025-03-26 11:00:12 -04:00
David Bolack
565d58bb31 Add clearing for snippets 2025-03-24 21:06:55 -05:00
David Bolack
2f95cc5f45 Merge branch 'master' into localSnippetEditor 2025-03-24 15:10:25 -05:00
David Bolack
a62588a4c9 Fix fouled up regex that only worked by accident 2025-03-24 14:58:14 -05:00
Víctor Losada Hernández
4afef9d3b3 Merge branch 'master' into issue_4105 2025-03-24 13:14:31 +01:00
Víctor Losada Hernández
a887b87350 Merge pull request #3664 from dbolack-ab/SnippetsReorg
Relocate general purpose snippets still in 5ePHB theme to Blank
2025-03-24 13:14:01 +01:00
Víctor Losada Hernández
3672285e92 additional linting pass through themes 2025-03-24 13:09:56 +01:00
Víctor Losada Hernández
ea4dd5defd Merge branch 'master' of https://github.com/naturalcrit/homebrewery into SnippetsReorg 2025-03-24 13:07:16 +01:00
David Bolack
712ee111d4 Move Dropcap settings back to PHB 2025-03-23 20:48:30 -05:00
David Bolack
0fc7571c35 Somehow this change did not get commited or pushed.
This swaps the if block for an optional chaining
2025-03-23 20:41:14 -05:00
David Bolack
84cdf6a14e Merge branch 'master' into issue_4105 2025-03-23 20:34:34 -05:00
Víctor Losada Hernández
1f77656a1c Merge branch 'master' into marked-nonbreaking-spaces 2025-03-22 23:44:58 +01:00
Víctor Losada Hernández
bbd95ffe2a Merge pull request #4014 from dbolack-ab/issue_3659
Fix space issue in image tags
2025-03-22 23:40:23 +01:00
Víctor Losada Hernández
e3a7e1f403 Merge branch 'master' into issue_3659 2025-03-22 16:39:49 +01:00
Víctor Losada Hernández
746c71f44b Merge pull request #4113 from G-Ambatte/fixLocalInstallCORS
Fix CORS regex test for local installs
2025-03-22 16:31:00 +01:00
G.Ambatte
cb61891450 Change local regex testing 2025-03-22 12:31:38 +13:00
dependabot[bot]
481219402c Bump supertest from 7.0.0 to 7.1.0
Bumps [supertest](https://github.com/ladjs/supertest) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/ladjs/supertest/releases)
- [Commits](https://github.com/ladjs/supertest/compare/v7.0.0...v7.1.0)

---
updated-dependencies:
- dependency-name: supertest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 03:29:48 +00:00
David Bolack
48285e6738 Add validation to ensure \page mustaches have return valid contents from the lexar 2025-03-19 17:45:47 -05:00
Víctor Losada Hernández
d2507fe99f remove empty lines 2025-03-19 13:29:16 +01:00
Víctor Losada Hernández
07ff9a114e lint 2025-03-19 13:26:58 +01:00
Víctor Losada Hernández
962d98543e initial fix 2025-03-19 10:56:16 +01:00
Víctor Losada Hernández
7f17887e0e Merge pull request #4102 from naturalcrit/linting
Linting the entire project
2025-03-18 20:21:51 +01:00
Víctor Losada Hernández
8e37806791 jsx files 2025-03-18 19:47:49 +01:00
Víctor Losada Hernández
f076e05f49 js files 2025-03-18 19:46:11 +01:00
Víctor Losada Hernández
163e3927b5 style lint 2025-03-18 19:38:58 +01:00
Víctor Losada Hernández
0234de12bb Merge pull request #4097 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.16.0
Bump stylelint from 16.15.0 to 16.16.0
2025-03-18 13:22:49 +01:00
dependabot[bot]
21be329e77 Bump stylelint from 16.15.0 to 16.16.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.15.0 to 16.16.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.15.0...16.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-18 11:45:04 +00:00
Víctor Losada Hernández
314c5cef7e Merge pull request #4100 from naturalcrit/update-to-3.18.1
Up to v3.18.1
2025-03-18 12:43:42 +01:00
Víctor Losada Hernández
ec36365697 add letter size snippet 2025-03-18 12:39:31 +01:00
Víctor Losada Hernández
f47a32067e add changelog 2025-03-18 12:39:24 +01:00
Víctor Losada Hernández
fef571b1d6 version bump + npm audit fix 2025-03-18 12:13:17 +01:00
Víctor Losada Hernández
80b33e3fed Merge pull request #4099 from G-Ambatte/revertColonChange
Fix minor v3.18.0 issues
2025-03-18 12:07:03 +01:00
Víctor Losada Hernández
8b67118303 Merge branch 'master' into revertColonChange 2025-03-18 09:40:23 +01:00
Víctor Losada Hernández
d5969a6573 Merge pull request #4095 from naturalcrit/allow-all-origins-when-local
[LOCAL]: Fix cors issue in local network
2025-03-18 09:38:15 +01:00
Víctor Losada Hernández
547682a59a Merge branch 'master' into allow-all-origins-when-local 2025-03-18 09:31:10 +01:00
G.Ambatte
b2903137eb Fix hard-breaks tests 2025-03-18 11:08:15 +13:00
G.Ambatte
7329c69cfd Update tests 2025-03-18 11:02:09 +13:00
G.Ambatte
83a095923e Add columnSplit to end of each page for older Chrome versions 2025-03-18 10:51:09 +13:00
G.Ambatte
a44056a64b Restore blank styling 2025-03-18 10:02:29 +13:00
G.Ambatte
a705e3b9d8 Change : from br to div class='blank' 2025-03-18 09:54:27 +13:00
David Bolack
d55a6cfd88 Merge branch 'issue_3659' of github.com:dbolack-ab/homebrewery into issue_3659 2025-03-14 19:52:10 -05:00
David Bolack
86ff2ab96b Add tests for regression 2025-03-14 19:51:35 -05:00
David Bolack
a960299612 Merge branch 'master' into issue_3659 2025-03-14 19:50:32 -05:00
David Bolack
99efe7f06b Add default value for document name in snippets menu 2025-03-14 19:26:37 -05:00
David Bolack
b605346c7d Fix regeression in snippets 2025-03-14 18:36:18 -05:00
David Bolack
ab6c1ae402 Merge branch 'master' into localSnippetEditor 2025-03-14 17:44:32 -05:00
Víctor Losada Hernández
94bcc8e997 update to include all possible local adresses 2025-03-13 22:59:23 +01:00
Víctor Losada Hernández
72c2857237 initial fix 2025-03-13 22:32:12 +01:00
G.Ambatte
20f0d16a58 Fix missed var name update 2025-03-14 08:18:30 +13:00
G.Ambatte
9a26626412 Change automatic var name to HB_PageNumber 2025-03-14 08:12:30 +13:00
G.Ambatte
f4afc91df7 Rename toWordsCamel to toWordsCaps 2025-03-14 08:08:30 +13:00
G.Ambatte
baafb6d2f9 Tweak camelcase function 2025-03-13 14:54:45 +13:00
G.Ambatte
8b9e084b17 Add variable page numbering snippet 2025-03-13 14:16:41 +13:00
G.Ambatte
44a01f27fe Add tests for custom math functions 2025-03-13 13:58:06 +13:00
G.Ambatte
543d18f9d9 Add written number functions 2025-03-13 12:22:08 +13:00
G.Ambatte
7371f57ded Add written number package 2025-03-13 12:21:53 +13:00
G.Ambatte
ee543b7090 Add int to char functions 2025-03-13 11:59:12 +13:00
G.Ambatte
b67eb59461 Add Roman numerals function 2025-03-13 10:50:18 +13:00
G.Ambatte
e787a68859 Add roman numeral package 2025-03-13 10:49:59 +13:00
G.Ambatte
edc4f8ec63 Change to increment previous 2025-03-12 13:28:43 +13:00
G.Ambatte
aec958249a Add automatic pageNumber variable to globalVarsList 2025-03-12 10:21:22 +13:00
Trevor Buckner
f083391efd Merge pull request #4080 from naturalcrit/fix-vault-pagination
Vault fixes
2025-03-10 19:38:06 -04:00
Trevor Buckner
2c63c01723 Update Changelog 2025-03-10 19:37:32 -04:00
Trevor Buckner
85af5bbd27 Merge branch 'master' into fix-vault-pagination 2025-03-10 19:34:37 -04:00
Trevor Buckner
17f26b803c Merge pull request #4086 from naturalcrit/non-content_spans_dont_impact_flow
Style .frontCover and .insideCover to position : absolute
2025-03-10 19:32:27 -04:00
Trevor Buckner
8093380e0c .frontCover and .insideCover to position : absolute
.partCover and .backCover are already position : absolute.
2025-03-10 19:15:16 -04:00
Trevor Buckner
07f0cef67c Up marked-extended-tables to v2.0.1 2025-03-10 17:05:45 -04:00
Trevor Buckner
4241aa535b Merge pull request #4083 from G-Ambatte/killDomPurify
Remove unused DOMPurify package
2025-03-10 15:03:13 -04:00
Trevor Buckner
4c85f3ec4b Merge branch 'master' into killDomPurify 2025-03-10 15:02:21 -04:00
Trevor Buckner
57f273a276 Merge pull request #4084 from naturalcrit/v3.18.0
v3.18.0
2025-03-10 14:55:58 -04:00
Trevor Buckner
e159e57222 Changelog up to v3.18.0 2025-03-10 14:54:30 -04:00
G.Ambatte
d4991164e9 Merge branch 'master' into killDomPurify 2025-03-11 07:40:34 +13:00
Trevor Buckner
baa1ed2b53 Merge pull request #4082 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.3
Bump nanoid from 5.1.2 to 5.1.3
2025-03-10 14:38:17 -04:00
G.Ambatte
f1e291e313 Remove unused DOMPurify package 2025-03-11 07:32:58 +13:00
dependabot[bot]
814f3a6c20 Bump nanoid from 5.1.2 to 5.1.3
Bumps [nanoid](https://github.com/ai/nanoid) from 5.1.2 to 5.1.3.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.2...5.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 18:03:14 +00:00
Trevor Buckner
43dc1bed7d Merge pull request #4081 from naturalcrit/dependabot/npm_and_yarn/eslint-9.22.0
Bump eslint from 9.21.0 to 9.22.0
2025-03-10 14:01:55 -04:00
dependabot[bot]
313492a344 Bump eslint from 9.21.0 to 9.22.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.21.0 to 9.22.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.21.0...v9.22.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 03:19:07 +00:00
Víctor Losada Hernández
4cd5c13841 lint styles 2025-03-08 19:38:42 +01:00
Víctor Losada Hernández
c7a19857dd remove visual glitch when performing aa search 2025-03-08 19:35:12 +01:00
Víctor Losada Hernández
b07317b0f7 display pagination on top as well 2025-03-08 19:32:26 +01:00
Víctor Losada Hernández
c0eef7530e added transition to smooth change 2025-03-08 17:27:34 +01:00
Víctor Losada Hernández
55618a10b9 fix container query 2025-03-08 17:16:23 +01:00
Víctor Losada Hernández
5f48b30449 lint styles 2025-03-08 13:46:11 +01:00
Víctor Losada Hernández
e523886345 lint server api 2025-03-08 13:42:49 +01:00
Víctor Losada Hernández
4918dc5239 manual lint 2025-03-08 13:42:29 +01:00
Víctor Losada Hernández
a0de6295c7 fix space in help text 2025-03-08 13:39:06 +01:00
Víctor Losada Hernández
3db778a665 fix 2 column issue 2025-03-08 13:30:06 +01:00
Víctor Losada Hernández
a7eef65694 fix pagination not correctly updating 2025-03-08 13:16:41 +01:00
Trevor Buckner
8d1464a2c4 Merge pull request #4078 from naturalcrit/dependabot/npm_and_yarn/react-router-7.3.0
Bump react-router from 7.2.0 to 7.3.0
2025-03-06 23:39:16 -05:00
dependabot[bot]
552cf30863 Bump react-router from 7.2.0 to 7.3.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.3.0/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-07 03:48:38 +00:00
Trevor Buckner
20baa9984f Cleanup 2025-03-06 11:13:39 -05:00
Trevor Buckner
4daa8042a2 Merge pull request #4005 from dbolack-ab/issue_3206
Workaround for unclosed <pre> blocks before rendering.
2025-03-05 20:13:45 -05:00
Trevor Buckner
51e79c2c5f Remove extra column break entirely
Not needed with change to column-fill auto

See https://github.com/naturalcrit/homebrewery/pull/3013
2025-03-05 20:13:35 -05:00
Trevor Buckner
88e8140b60 Merge branch 'master' into pr/4005 2025-03-05 19:59:36 -05:00
Trevor Buckner
252698b135 Merge pull request #4077 from naturalcrit/Update-Marked.js-to-v14.0.0
Update to Marked v14
2025-03-05 15:50:08 -05:00
Trevor Buckner
21f1704626 Update to Marked v14 2025-03-05 15:40:14 -05:00
David Bolack
7f128b0dae Merge branch 'master' into issue_3659 2025-03-05 12:16:47 -06:00
David Bolack
869d69b986 Merge branch 'marked-justifiedParagraphs' of github.com:dbolack-ab/homebrewery into marked-justifiedParagraphs 2025-03-05 12:12:31 -06:00
David Bolack
c04cc94570 Merge branch 'master' into marked-justifiedParagraphs 2025-03-05 12:09:56 -06:00
David Bolack
46093ba6ba Update package versions 2025-03-05 11:23:29 -06:00
David Bolack
19556d9f36 Merge branch 'master' into issue_3206 2025-03-05 11:09:03 -06:00
Trevor Buckner
0d4d97c5c5 Merge pull request #4070 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.15.0
Bump stylelint from 16.14.1 to 16.15.0
2025-03-05 12:00:37 -05:00
David Bolack
55f333a9e5 Changed tactic per suggestion
Unsure if the test is absolutely necessary.
2025-03-05 10:52:43 -06:00
dependabot[bot]
2361cdeadc Bump stylelint from 16.14.1 to 16.15.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.14.1 to 16.15.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.14.1...16.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:47:30 +00:00
Trevor Buckner
aeae704173 Merge pull request #4071 from naturalcrit/dependabot/npm_and_yarn/core-js-3.41.0
Bump core-js from 3.40.0 to 3.41.0
2025-03-05 11:46:08 -05:00
David Bolack
c420410904 Merge branch 'master' into issue_3206 2025-03-05 10:37:58 -06:00
dependabot[bot]
0daf8c5c83 Bump core-js from 3.40.0 to 3.41.0
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.40.0 to 3.41.0.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.41.0/packages/core-js)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:37:35 +00:00
Trevor Buckner
924d014c69 Merge pull request #4074 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.12.1
Bump mongoose from 8.11.0 to 8.12.1
2025-03-05 11:36:18 -05:00
dependabot[bot]
8992cf8251 Bump mongoose from 8.11.0 to 8.12.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.11.0 to 8.12.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.11.0...8.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:26:08 +00:00
Trevor Buckner
7c6aa0ffec Merge pull request #4075 from naturalcrit/dependabot/npm_and_yarn/marked-emoji-2.0.0
Bump marked-emoji from 1.4.3 to 2.0.0
2025-03-05 11:24:42 -05:00
Trevor Buckner
ebe64c508f Merge branch 'master' into dependabot/npm_and_yarn/marked-emoji-2.0.0 2025-03-05 11:20:08 -05:00
Trevor Buckner
f3514cfea6 Merge pull request #4076 from MollyMaclachlan/master
Fix formatting inconsistencies in 5ePHB monster stat block
2025-03-05 10:52:29 -05:00
Trevor Buckner
8ed25fb7cf Merge branch 'master' into master 2025-03-05 10:52:21 -05:00
Trevor Buckner
762cd58d52 Update usage of Marked-extended-tables options 2025-03-05 10:49:46 -05:00
Murdo B. Maclachlan
477f706eb9 Fix formatting inconsistencies in 5ePHB monster stat block
Closes #4073. Fixes minor formatting inconsistencies in the monster stat block between the 5ePHB theme and actual 5e manuals.
2025-03-05 13:01:31 +00:00
dependabot[bot]
edcf9979a7 Bump marked-emoji from 1.4.3 to 2.0.0
Bumps [marked-emoji](https://github.com/UziTech/marked-emoji) from 1.4.3 to 2.0.0.
- [Release notes](https://github.com/UziTech/marked-emoji/releases)
- [Changelog](https://github.com/UziTech/marked-emoji/blob/main/release.config.cjs)
- [Commits](https://github.com/UziTech/marked-emoji/compare/v1.4.3...v2.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 03:48:15 +00:00
Trevor Buckner
ef2beec590 Merge pull request #4068 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-8.16.0
Bump @googleapis/drive from 8.14.0 to 8.16.0
2025-02-28 16:01:32 -05:00
dependabot[bot]
c10559ba5f Bump @googleapis/drive from 8.14.0 to 8.16.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 8.14.0 to 8.16.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/main/release-please-config.json)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/drive-v8.14.0...drive-v8.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-28 19:59:46 +00:00
Trevor Buckner
69c633dabe Merge pull request #4067 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.11.0
Bump mongoose from 8.10.2 to 8.11.0
2025-02-28 14:58:32 -05:00
dependabot[bot]
8bdcdcd510 Bump mongoose from 8.10.2 to 8.11.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.10.2 to 8.11.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.10.2...8.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-28 13:01:06 +00:00
Trevor Buckner
ce03f598b2 Merge pull request #4066 from naturalcrit/dependabot/npm_and_yarn/marked-extended-tables-2.0.0
Bump marked-extended-tables from 1.1.0 to 2.0.0
2025-02-28 07:59:47 -05:00
dependabot[bot]
addbf19682 Bump marked-extended-tables from 1.1.0 to 2.0.0
Bumps [marked-extended-tables](https://github.com/calculuschild/marked-extended-tables) from 1.1.0 to 2.0.0.
- [Release notes](https://github.com/calculuschild/marked-extended-tables/releases)
- [Changelog](https://github.com/calculuschild/marked-extended-tables/blob/main/release.config.cjs)
- [Commits](https://github.com/calculuschild/marked-extended-tables/compare/v1.1.0...v2.0.0)

---
updated-dependencies:
- dependency-name: marked-extended-tables
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-27 03:57:55 +00:00
Trevor Buckner
479aae4b2f Merge pull request #4065 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.10.2
Bump mongoose from 8.10.1 to 8.10.2
2025-02-26 10:46:36 -05:00
Trevor Buckner
4b6652c470 Merge pull request #4052 from G-Ambatte/fixSaveBug
Fix bug in save logic
2025-02-26 10:45:53 -05:00
dependabot[bot]
e9d1209ce8 Bump mongoose from 8.10.1 to 8.10.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.10.1 to 8.10.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.10.1...8.10.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-26 03:33:15 +00:00
G.Ambatte
7c62e49767 Merge branch 'master' into fixSaveBug 2025-02-25 22:39:18 +13:00
G.Ambatte
9b0da36365 Rework isPending & hasChanges 2025-02-25 19:56:23 +13:00
Trevor Buckner
1391a9053d Merge pull request #4062 from naturalcrit/dependabot/npm_and_yarn/globals-16.0.0
Bump globals from 15.15.0 to 16.0.0
2025-02-24 14:21:59 -05:00
dependabot[bot]
fee88d1d47 Bump globals from 15.15.0 to 16.0.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.15.0 to 16.0.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.15.0...v16.0.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 18:39:19 +00:00
Trevor Buckner
a47dc51bd1 Merge pull request #4064 from naturalcrit/dependabot/npm_and_yarn/eslint-9.21.0
Bump eslint from 9.20.1 to 9.21.0
2025-02-24 13:37:52 -05:00
dependabot[bot]
cfb9e1afa2 Bump eslint from 9.20.1 to 9.21.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.20.1 to 9.21.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.20.1...v9.21.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 18:34:06 +00:00
Trevor Buckner
540a0a7a36 Merge pull request #4063 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.2
Bump nanoid from 5.1.0 to 5.1.2
2025-02-24 13:32:41 -05:00
dependabot[bot]
b7e422ac06 Bump nanoid from 5.1.0 to 5.1.2
Bumps [nanoid](https://github.com/ai/nanoid) from 5.1.0 to 5.1.2.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.0...5.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 03:57:12 +00:00
David Bolack
06e3fd6248 Merge branch 'master' into marked-nonbreaking-spaces 2025-02-21 14:06:53 -06:00
Trevor Buckner
f3315d654e tabs to spaces 2025-02-21 14:37:33 -05:00
Víctor Losada Hernández
df5eeb5c97 Merge pull request #4023 from 5e-Cleric/fix-snippet-styling
Small adjustments to the PHB theme snippets
2025-02-20 21:18:24 +01:00
Víctor Losada Hernández
e2de225625 Merge branch 'fix-snippet-styling' of https://github.com/5e-Cleric/homebrewery into fix-snippet-styling 2025-02-20 20:44:32 +01:00
Víctor Losada Hernández
5b7d5bee24 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-snippet-styling 2025-02-20 20:44:18 +01:00
Trevor Buckner
18eb3ec643 Merge branch 'master' into fix-snippet-styling 2025-02-20 13:35:05 -05:00
G.Ambatte
1af66cf571 Add default case for SAVE button text 2025-02-15 00:13:23 +13:00
G.Ambatte
2cb8b5d014 Set brew state to exactly match savedBrew 2025-02-15 00:12:55 +13:00
G.Ambatte
34a0b4eb05 Base savedBrew on current brew state, apply only updated properties from API call 2025-02-15 00:12:20 +13:00
David Bolack
0c2f0ac31e Remove tests performed in nonbreaking space module 2025-02-12 12:52:01 -06:00
David Bolack
777f51c661 First pass testing
If completes, remove most tests.
2025-02-12 12:32:28 -06:00
David Bolack
3cfdb7eeb0 Temporarily restore old tests. 2025-02-12 12:26:02 -06:00
David Bolack
1f9495099f WIP 2025-02-12 12:22:02 -06:00
David Bolack
80564dd8db Remove relocated tests from executiomn 2025-02-11 15:01:20 -06:00
David Bolack
cf4c1f7009 Merge branch 'master' into SnippetsReorg 2025-02-11 14:46:03 -06:00
David Bolack
3ffdb34312 Tweaks in response to CC comments 2025-02-11 14:45:39 -06:00
David Bolack
dc8d0e9483 Restore .monster 2025-02-11 14:37:25 -06:00
David Bolack
38bd3b0fc5 Migrate the justified paragraphs extension to an NPM 2025-02-11 14:34:01 -06:00
Trevor Buckner
2e12980180 Merge branch 'master' into fix-snippet-styling 2025-02-11 11:23:05 -05:00
David Bolack
d061b902d5 Make all typefaces available via blank - for now. 2025-02-07 19:26:56 -06:00
David Bolack
0a86990bdf Merge branch 'master' into SnippetsReorg 2025-02-07 19:02:28 -06:00
David Bolack
f2f894381e Merge branch 'master' into issue_3206 2025-02-04 21:10:25 -06:00
David Bolack
7c293f51cb Merge branch 'master' into issue_3659 2025-02-04 21:09:55 -06:00
David Bolack
67b31c476c Merge branch 'master' into localSnippetEditor 2025-02-04 21:07:02 -06:00
Víctor Losada Hernández
8eef810f3f backcover logo width 2025-02-01 20:30:54 +01:00
Víctor Losada Hernández
a04df0fdfc small adjustements 2025-02-01 19:27:55 +01:00
David Bolack
497f8bde83 Merge branch 'master' into issue_3659 2025-01-31 16:27:28 -06:00
David Bolack
8711265506 Merge branch 'issue_3659' of github.com:dbolack-ab/homebrewery into issue_3659 2025-01-31 16:25:33 -06:00
David Bolack
b1ff68c3b1 Update code to use helper function to ensure unifrom normalization of Variable labels. 2025-01-31 16:23:32 -06:00
David Bolack
564f5d71b2 WIP 2025-01-31 16:12:00 -06:00
David Bolack
158122ed55 Merge branch 'master' into issue_3659 2025-01-31 16:05:00 -06:00
David Bolack
f1eb6e1ce4 Alter varCallInline content to alig with preceeding varDefBlock in inline definitions.
This was failing due to both labels having the extraneous spaces cleaned up but the variable label portion of the varCallInline did not, preventing them from being matched during variable resolution.
2025-01-28 21:31:43 -06:00
David Bolack
004729b2a4 Fix editor regression. 2025-01-28 19:37:02 -06:00
David Bolack
c27d9978fe Merge branch 'master' into localSnippetEditor 2025-01-28 19:30:06 -06:00
David Bolack
f608cb2d65 Merge branch 'issue_3206' of github.com:dbolack-ab/homebrewery into issue_3206 2025-01-28 19:26:37 -06:00
David Bolack
28a1610573 Merge branch 'master' into issue_3206 2025-01-28 19:26:10 -06:00
Trevor Buckner
df00160bc4 Merge branch 'master' into pr/4005 2025-01-27 23:29:28 -05:00
David Bolack
342ac76982 Merge branch 'master' into localSnippetEditor 2025-01-24 14:30:12 -06:00
David Bolack
60b6dbb388 Workaround for unclosed <pre> blocks before rendering.
Unsure if this is a fix you really need but it resolves the issue posted.
2025-01-24 13:55:48 -06:00
David Bolack
662f039daa Merge conflict clearing 2025-01-12 14:13:27 -06:00
David Bolack
20c46bd27f Merge branch 'master' into localSnippetEditor 2025-01-12 14:04:23 -06:00
David Bolack
0e6380a8bd Merge branch 'master' into localSnippetEditor 2024-12-28 16:09:48 -06:00
David Bolack
ed099aa061 Disable the BrewSnippets menu if empty. 2024-12-20 21:47:05 -06:00
David Bolack
5f54777663 Merge branch 'master' into localSnippetEditor 2024-12-20 15:18:25 -06:00
David Bolack
909affcf99 Merge branch 'localSnippetEditor' of github.com:dbolack-ab/homebrewery into localSnippetEditor 2024-12-10 23:10:31 -06:00
David Bolack
86856605b9 Add editor highlighting 2024-12-10 23:08:51 -06:00
David Bolack
dae297e0f5 Partially implement disabled BrewSnippets 2024-12-10 21:52:38 -06:00
David Bolack
6e5f071f22 Move Properties icon to the end of the snippets bar. 2024-12-10 21:21:41 -06:00
David Bolack
12c155b46f Merge branch 'master' into localSnippetEditor 2024-12-10 21:17:51 -06:00
Trevor Buckner
f51c51f041 Merge branch 'master' into localSnippetEditor 2024-12-10 11:11:09 -05:00
David Bolack
43222b7651 Fix test 2024-12-04 21:41:04 -06:00
David Bolack
70f86c6ebd Merge branch 'master' into localSnippetEditor 2024-12-04 21:31:10 -06:00
David Bolack
74b4cb2afd Revert local error in package.json 2024-11-25 13:59:24 -06:00
David Bolack
fa96836b63 Merge branch 'master' into localSnippetEditor 2024-11-25 13:55:54 -06:00
David Bolack
e763ae1631 t shouldn't have been saved... 2024-11-24 21:57:59 -06:00
David Bolack
008b31e530 Correct failing test. 2024-11-24 18:58:50 -06:00
David Bolack
b6e445c445 Convert storage of snippets in Brew to yaml by request. 2024-11-24 18:13:32 -06:00
David Bolack
c5935ec262 Add Snippets to /new 2024-11-23 01:57:07 -06:00
David Bolack
5f67494f77 Merge branch 'master' into localSnippetEditor 2024-11-23 01:39:38 -06:00
David Bolack
e98b614f05 Merge branch 'localSnippetEditor' of github.com:dbolack-ab/homebrewery into localSnippetEditor 2024-11-14 06:56:14 -06:00
David Bolack
d541a70da5 Remove unneeded dedent 2024-11-14 06:54:36 -06:00
G.Ambatte
cc9586aa64 Merge branch 'master' into localSnippetEditor 2024-11-14 16:41:26 +13:00
David Bolack
f7561b7824 Insert a CR 2024-11-13 20:53:04 -06:00
David Bolack
83abdc2ee6 Merge branch 'master' into localSnippetEditor 2024-11-12 18:42:50 -06:00
David Bolack
e0400c0425 Snippets should go after existing tab sections 2024-11-12 18:41:31 -06:00
David Bolack
687b7e04d9 Merge branch 'master' into localSnippetEditor 2024-11-11 21:21:57 -06:00
David Bolack
f43a155e6e Fix Test 2024-11-03 12:52:54 -06:00
David Bolack
f4e9516233 Remove testing helper 2024-11-03 12:41:04 -06:00
David Bolack
7f7f3557b3 Mostly working. Needs tweakages. Presentable 2024-11-03 12:30:14 -06:00
David Bolack
b9b3d284cf WOrking snippet editor - menu population regression 2024-11-03 11:14:31 -06:00
David Bolack
4f240bf110 WIP 2024-11-02 22:30:18 -05:00
David Bolack
7cd82ffc4e WOrking snippets insertion from local. 2024-11-02 17:37:43 -05:00
David Bolack
4448410c3e Partial implementation 2024-11-01 14:02:27 -05:00
Trevor Buckner
220f4fad24 Merge branch 'master' into addLockRoutes-#3326 2024-09-24 13:37:53 -04:00
G.Ambatte
05c1d31550 Merge branch 'master' into addLockRoutes-#3326 2024-09-18 15:21:20 +12:00
Trevor Buckner
c8bacabf24 Merge branch 'master' into addLockRoutes-#3326 2024-09-16 16:19:34 -04:00
G.Ambatte
bfab34f8c6 Merge branch 'master' into addLockRoutes-#3326 2024-09-06 08:07:33 +12:00
David Bolack
a3c01305df Revert "Move Page styles ( cover Page, etc ) to Blank from 5ePHB"
This reverts commit ad1dfc8e2b.
2024-08-25 19:15:57 -05:00
David Bolack
ad1dfc8e2b Move Page styles ( cover Page, etc ) to Blank from 5ePHB 2024-08-23 16:52:35 -05:00
David Bolack
2c573bfef5 Failed to save one file's changes. 2024-08-23 14:18:58 -05:00
David Bolack
fd5ff2c61a Relocate more general purposes snippets from 5ePHB to Blank
Should include all supporting style content.
2024-08-23 14:04:04 -05:00
G.Ambatte
1d03b200a5 Merge branch 'master' into addLockRoutes-#3326 2024-08-14 08:39:55 +12:00
G.Ambatte
b068749380 Move copy ID to clipboard to separate button 2024-07-25 22:11:14 +12:00
G.Ambatte
f82f893014 Merge branch 'master' into addLockRoutes-#3326 2024-07-23 10:28:59 +12:00
G.Ambatte
7094d43ee5 Shift API calls to /api from /admin 2024-06-29 15:29:02 +12:00
G.Ambatte
51296a9100 Merge branch 'master' into addLockRoutes-#3326 2024-06-29 10:34:55 +12:00
G.Ambatte
0803362a50 More styling tweaks 2024-06-12 22:29:47 +12:00
G.Ambatte
7e80787679 Tweak styling 2024-06-12 22:18:12 +12:00
G.Ambatte
fc99328459 Tweak styling, remove obsolete locked property 2024-06-12 21:48:55 +12:00
G.Ambatte
c594fc58b3 Spread lock object in /lock response 2024-06-12 21:47:52 +12:00
G.Ambatte
24cf78bc03 Add lock result 2024-06-12 20:44:55 +12:00
G.Ambatte
17aa564c57 Add styling for result tables 2024-06-08 21:05:44 +12:00
G.Ambatte
7d37602d43 Remove debugging line 2024-06-08 20:01:53 +12:00
G.Ambatte
44f2e38387 Shift unlock and removeReview functions to use PUT 2024-06-08 20:00:10 +12:00
G.Ambatte
7830c7e2eb Add divs for styling of brewsAwaitingReview 2024-06-08 19:59:01 +12:00
G.Ambatte
deef5998c5 Update LockBrew text and styling 2024-06-08 18:55:14 +12:00
G.Ambatte
01ae858a14 Update LockLookup placeholder value 2024-06-08 17:08:05 +12:00
G.Ambatte
634b099ade Add styling to LockTable 2024-06-08 16:48:16 +12:00
G.Ambatte
240342007b Fix lock count function 2024-06-08 16:36:20 +12:00
G.Ambatte
bc7731b819 Fix review requested aggregation pipeline 2024-06-08 16:08:18 +12:00
G.Ambatte
00fd1e415c Change styling for inactive REQUEST REVIEW button 2024-06-08 15:53:28 +12:00
G.Ambatte
f81d16309c Add functionality to REQUEST REVIEW button 2024-06-08 15:53:01 +12:00
G.Ambatte
fe09b89fcb Shift API call for REQUEST REVIEW to PUT 2024-06-08 15:31:46 +12:00
G.Ambatte
6314672109 Pass the entire lock object to LockNotification 2024-06-08 14:59:08 +12:00
G.Ambatte
d995169b3c Merge branch 'master' into addLockRoutes-#3326 2024-06-08 12:06:29 +12:00
G.Ambatte
2c6e00702c Merge branch 'master' into addLockRoutes-#3326 2024-06-06 23:06:59 +12:00
G.Ambatte
6d9e564d1c Merge branch 'master' into addLockRoutes-#3326 2024-05-30 00:02:37 +12:00
G.Ambatte
d751addf9d Change lock static error message 2024-05-16 17:18:50 +12:00
G.Ambatte
1af7adc09b Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2024-05-16 17:06:19 +12:00
G.Ambatte
7b287fb0f4 Fix test 2024-05-16 17:04:37 +12:00
G.Ambatte
489c65af3e Merge branch 'master' into addLockRoutes-#3326 2024-05-16 17:02:27 +12:00
G.Ambatte
4e11a0c737 Fix error message 2024-05-16 17:02:00 +12:00
G.Ambatte
96b955f2fe Change lock check to stub.lock from stub.lock.locked 2024-05-16 17:00:26 +12:00
G.Ambatte
f5014f29c3 Linter fix 2024-05-15 23:13:53 +12:00
G.Ambatte
b237f29636 Clean up API functions 2024-05-15 19:58:29 +12:00
G.Ambatte
c0c674d862 Comment out debug logging 2024-05-15 19:52:08 +12:00
G.Ambatte
7c4721932d Remove unnecessary function from HB.model 2024-05-15 19:50:27 +12:00
G.Ambatte
b093be52a2 Update esLint rules 2024-05-15 16:57:28 +12:00
G.Ambatte
65f1c19721 Add functional Lock Brew component 2024-05-15 16:14:09 +12:00
G.Ambatte
2502c0e87c Add missing return status messages 2024-05-15 16:08:26 +12:00
G.Ambatte
2a91d3ddbd Additional styling 2024-05-15 16:07:28 +12:00
G.Ambatte
e7dc757293 Initial UI functionality for most Lock functions 2024-05-11 23:58:37 +12:00
G.Ambatte
1fe2a26e83 Tabify Admin page 2024-05-10 12:38:31 +12:00
G.Ambatte
9b7471d6d2 Merge branch 'master' into addLockRoutes-#3326 2024-05-09 22:57:42 +12:00
G.Ambatte
b4ce621630 Request review no longer Admin only 2024-05-09 07:37:37 +12:00
G.Ambatte
bf88f63482 Merge branch 'master' into addLockRoutes-#3326 2024-05-08 23:30:31 +12:00
G.Ambatte
20d48d7dc2 Add review removal route 2024-04-25 15:59:11 +12:00
G.Ambatte
4b1d6ebd7c Rename review request route 2024-04-25 15:58:43 +12:00
G.Ambatte
e9db7d1bb9 Add return on success to lock route 2024-04-25 15:37:50 +12:00
G.Ambatte
1c2ae8392c Increase max lines in Linter 2024-04-25 15:32:26 +12:00
G.Ambatte
e8b9b3d583 Add lock route 2024-04-25 15:27:40 +12:00
G.Ambatte
71b84e1aba Log message when review already requested 2024-04-25 14:33:29 +12:00
G.Ambatte
99f5aad942 Stop review request if it has already been logged 2024-04-25 14:32:04 +12:00
G.Ambatte
8feae7efb6 Function request review route 2024-04-22 00:32:32 +12:00
G.Ambatte
874cbe1da4 Don't forget to update the schema! 2024-04-22 00:26:34 +12:00
G.Ambatte
14c7a11528 Fix error message 2024-04-21 22:51:53 +12:00
G.Ambatte
b4a7dc0cbd Update lock review route 2024-04-21 22:48:11 +12:00
G.Ambatte
8c5f2ff61c Add route to get locked documents with review requested 2024-04-21 22:17:10 +12:00
G.Ambatte
770025da04 Add lock count route, update pipelines 2024-04-21 14:42:20 +12:00
G.Ambatte
eb719e34a8 Add aggregate query to HB model 2024-04-21 14:35:51 +12:00
93 changed files with 4448 additions and 3037 deletions

View File

@@ -10,7 +10,7 @@ orbs:
jobs:
build:
docker:
- image: cimg/node:20.17.0
- image: cimg/node:20.18.0
- image: mongo:4.4
working_directory: ~/homebrewery

View File

@@ -144,3 +144,4 @@ your contribution to the project, please join our [gitter chat][gitter-url].
[github-mark-duplicate-url]: https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/about-duplicate-issues-and-pull-requests
[github-pr-docs-url]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
[gitter-url]: https://gitter.im/naturalcrit/Lobby

View File

@@ -88,6 +88,70 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Tuesday 03/18/2025 - v3.18.1
{{taskList
##### G-Ambatte
* [x] Revert colon rendering from br elements to blank divs
##### 5e-Cleric
* [x] Allow for local connections within a same network when running a local version
Fixes issue [#4094](https://github.com/naturalcrit/homebrewery/issues/4094)
* [x] Add US Letter size page snippet
Fixes issue [#3893](https://github.com/naturalcrit/homebrewery/issues/3893)
}}
### Monday 03/10/2025 - v3.18.0
{{taskList
##### dbolack
* [x] Add ability to paste in any Share ID/URL into a brew's {{openSans :fas_circle_info: **Properties** :fas_arrow_right: **THEMES**}} selection, as long as that brew has been tagged as `meta:theme`. You can now share your custom brew themes without needing to make a personal copy.
* [x] Begin migration of custom Markdown extensions into their own NPM packages, for easier adoption by other users or projects
* [x] Fix external HTML appearing in open codeblocks
Fixes issue [#3206](https://github.com/naturalcrit/homebrewery/issues/3206)
* [x] Fix tables not rendering when directly after text
##### G-Ambatte
* [x] Cleanup of "cover pages" in the {{openSans :fas_rectangle_list: **NAVIGATION**}} list
* [x] Fix autosave triggering when no changes are present
Fixes issue [#4051](https://github.com/naturalcrit/homebrewery/issues/4051)
* [x] Remove empty table rows resulting from rowspan
Fixes issue [#1729](https://github.com/naturalcrit/homebrewery/issues/1729)
##### 5e-Cleric
* [x] Style fixes for covers art and logos on A4 size pages
* [x] Fix crash when trying to open brews that don't exist
* [x] Tweaks and style update styling on {{openSans **VAULT** :fas_dungeon:}} page.
Fixes issue [#4079](https://github.com/naturalcrit/homebrewery/issues/4079)
##### Calculuschild
* [x] `` now produces `<br>` instead of a `<div>`
* [x] Fix typos in tables freezing the editor
Fixes issue [#4059](https://github.com/naturalcrit/homebrewery/issues/4059)
##### MollyMaclachlan (New Contributor!)
* [x] Fixed typos in the Monster Stat Block snippet
Fixes issue [#4073](https://github.com/naturalcrit/homebrewery/issues/4073)
##### All
* [x] Update dependencies and scripts
* [x] Refactor components and backend tools
}}
\column
### Thursday 01/30/2025 - v3.17.0
{{taskList
@@ -169,7 +233,7 @@ Fixes issue [#3824](https://github.com/naturalcrit/homebrewery/issues/3824)
* [x] Refactor components and fix various errors
}}
\column
\page
### Wednesday 11/27/2024 - v3.16.1
@@ -217,8 +281,6 @@ Fixes issue [#3744](https://github.com/naturalcrit/homebrewery/issues/3744)
* [x] Multiple code refactors, cleanups, and security fixes
}}
\page
### Saturday 10/12/2024 - v3.16.0
{{taskList

View File

@@ -3,14 +3,15 @@ import React, { useEffect, useState } from 'react';
const BrewUtils = require('./brewUtils/brewUtils.jsx');
const NotificationUtils = require('./notificationUtils/notificationUtils.jsx');
import AuthorUtils from './authorUtils/authorUtils.jsx';
import LockTools from './lockTools/lockTools.jsx';
const tabGroups = ['brew', 'notifications', 'authors'];
const tabGroups = ['brew', 'notifications', 'authors', 'locks'];
const Admin = ()=>{
const [currentTab, setCurrentTab] = useState('brew');
const [currentTab, setCurrentTab] = useState('');
useEffect(()=>{
setCurrentTab(localStorage.getItem('hbAdminTab'));
setCurrentTab(localStorage.getItem('hbAdminTab') || 'brew');
}, []);
useEffect(()=>{
@@ -40,6 +41,7 @@ const Admin = ()=>{
{currentTab === 'brew' && <BrewUtils />}
{currentTab === 'notifications' && <NotificationUtils />}
{currentTab === 'authors' && <AuthorUtils />}
{currentTab === 'locks' && <LockTools />}
</main>
</div>
);

View File

@@ -20,7 +20,7 @@
}
button {
width: 50px;
width : 50px;
i { margin-right : 10px; }
}

View File

@@ -0,0 +1,342 @@
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
require('./lockTools.less');
const React = require('react');
const createClass = require('create-react-class');
import request from '../../homebrew/utils/request-middleware.js';
const LockTools = createClass({
displayName : 'LockTools',
getInitialState : function() {
return {
fetching : false,
reviewCount : 0
};
},
componentDidMount : function() {
this.updateReviewCount();
},
updateReviewCount : async function() {
const newCount = await request.get('/api/lock/count')
.then((res)=>{return res.body?.count || 'Unknown';});
if(newCount != this.state.reviewCount){
this.setState({
reviewCount : newCount
});
}
},
updateLockData : function(lock){
this.setState({
lock : lock
});
},
render : function() {
return <div className='lockTools'>
<h2>Lock Count</h2>
<p>Number of brews currently locked: {this.state.reviewCount}</p>
<button onClick={this.updateReviewCount}>REFRESH</button>
<hr />
<LockTable title='Locked Brews' text='Total Locked Brews' resultName='lockedDocuments' fetchURL='/api/locks' propertyNames={['shareId', 'title']} loadBrew={this.updateLockData} ></LockTable>
<hr />
<LockTable title='Brews Awaiting Review' text='Total Reviews Waiting' resultName='reviewDocuments' fetchURL='/api/lock/reviews' propertyNames={['shareId', 'title']} loadBrew={this.updateLockData} ></LockTable>
<hr />
<LockBrew key={this.state.lock?.key || 0} lock={this.state.lock}></LockBrew>
<hr />
<div style={{ columns: 2 }}>
<LockLookup title='Unlock Brew' fetchURL='/api/unlock' updateFn={this.updateReviewCount}></LockLookup>
<LockLookup title='Clear Review Request' fetchURL='/api/lock/review/remove'></LockLookup>
</div>
<hr />
</div>;
}
});
const LockBrew = createClass({
displayName : 'LockBrew',
getInitialState : function() {
// Default values
return {
brewId : this.props.lock?.shareId || '',
code : this.props.lock?.code || 455,
editMessage : this.props.lock?.editMessage || '',
shareMessage : this.props.lock?.shareMessage || 'This Brew has been locked.',
result : {},
overwrite : false,
};
},
handleChange : function(e, varName) {
const output = {};
output[varName] = e.target.value;
this.setState(output);
},
submit : function(e){
e.preventDefault();
if(!this.state.editMessage) return;
const newLock = {
overwrite : this.state.overwrite,
code : parseInt(this.state.code) || 100,
editMessage : this.state.editMessage,
shareMessage : this.state.shareMessage,
applied : new Date
};
request.post(`/api/lock/${this.state.brewId}`)
.send(newLock)
.set('Content-Type', 'application/json')
.then((response)=>{
this.setState({ result: response.body });
})
.catch((err)=>{
this.setState({ result: err.response.body });
});
},
renderInput : function (name) {
return <input type='text' name={name} value={this.state[name]} onChange={(e)=>this.handleChange(e, name)} autoComplete='off' required/>;
},
renderResult : function(){
return <>
<h3>Result:</h3>
<table>
<tbody>
{Object.keys(this.state.result).map((key, idx)=>{
return <tr key={`${idx}-row`}>
<td key={`${idx}-key`}>{key}</td>
<td key={`${idx}-value`}>{this.state.result[key].toString()}
</td>
</tr>;
})}
</tbody>
</table>
</>;
},
render : function() {
return <div className='lockBrew'>
<div className='lockForm'>
<h2>Lock Brew</h2>
<form onSubmit={this.submit}>
<label>
ID:
{this.renderInput('brewId')}
</label>
<br />
<label>
Error Code:
{this.renderInput('code')}
</label>
<br />
<label>
Private Message:
{this.renderInput('editMessage')}
</label>
<br />
<label>
Public Message:
{this.renderInput('shareMessage')}
</label>
<br />
<label className='checkbox'>
Overwrite
<input name='overwrite' className='checkbox' type='checkbox' value={this.state.overwrite} onClick={()=>{return this.setState((prevState)=>{return { overwrite: !prevState.overwrite };});}} />
</label>
<label>
<input type='submit' />
</label>
</form>
{this.state.result && this.renderResult()}
</div>
<div className='lockSuggestions'>
<h2>Suggestions</h2>
<div className='lockCodes'>
<h3>Codes</h3>
<ul>
<li>455 - Generic Lock</li>
<li>456 - Copyright issues</li>
<li>457 - Confidential Information Leakage</li>
<li>458 - Sensitive Personal Information</li>
<li>459 - Defamation or Libel</li>
<li>460 - Hate Speech or Discrimination</li>
<li>461 - Illegal Activities</li>
<li>462 - Malware or Phishing</li>
<li>463 - Plagiarism</li>
<li>465 - Misrepresentation</li>
<li>466 - Inappropriate Content</li>
</ul>
</div>
<div className='lockMessages'>
<h3>Messages</h3>
<ul>
<li><b>Private Message:</b> This is the private message that is ONLY displayed to the authors of the locked brew. This message MUST specify exactly what actions must be taken in order to have the brew unlocked.</li>
<li><b>Public Message:</b> This is the public message that is displayed to the EVERYONE that attempts to view the locked brew.</li>
</ul>
</div>
</div>
</div>;
}
});
const LockTable = createClass({
displayName : 'LockTable',
getDefaultProps : function() {
return {
title : '',
text : '',
fetchURL : '/api/locks',
resultName : '',
propertyNames : ['shareId'],
loadBrew : ()=>{}
};
},
getInitialState : function() {
return {
result : '',
error : '',
searching : false
};
},
lockKey : React.createRef(0),
clickFn : function (){
this.setState({ searching: true, error: null });
request.get(this.props.fetchURL)
.then((res)=>this.setState({ result: res.body }))
.catch((err)=>this.setState({ result: err.response.body }))
.finally(()=>{
this.setState({ searching: false });
});
},
updateBrewLockData : function (lockData){
this.lockKey.current++;
const brewData = {
key : this.lockKey.current,
shareId : lockData.shareId,
code : lockData.lock.code,
editMessage : lockData.lock.editMessage,
shareMessage : lockData.lock.shareMessage
};
this.props.loadBrew(brewData);
},
render : function () {
return <>
<div className='brewsAwaitingReview'>
<div className='brewBlock'>
<h2>{this.props.title}</h2>
<button onClick={this.clickFn}>
REFRESH
<i className={`fas ${!this.state.searching ? 'fa-search' : 'fa-spin fa-spinner'}`} />
</button>
</div>
{this.state.result[this.props.resultName] &&
<>
<p>{this.props.text}: {this.state.result[this.props.resultName].length}</p>
<table className='lockTable'>
<thead>
<tr>
{this.props.propertyNames.map((name, idx)=>{
return <th key={idx}>{name}</th>;
})}
<th>clip</th>
<th>load</th>
</tr>
</thead>
<tbody>
{this.state.result[this.props.resultName].map((result, resultIdx)=>{
return <tr className='row' key={`${resultIdx}-row`}>
{this.props.propertyNames.map((name, nameIdx)=>{
return <td key={`${resultIdx}-${nameIdx}`}>
{result[name].toString()}
</td>;
})}
<td className='icon' title='Copy ID to Clipboard' onClick={()=>{navigator.clipboard.writeText(result.shareId.toString());}}><i className='fa-regular fa-clipboard'></i></td>
<td className='icon' title='View Lock details' onClick={()=>{this.updateBrewLockData(result);}}><i className='fa-regular fa-circle-down'></i></td>
</tr>;
})}
</tbody>
</table>
</>
}
</div>
</>;
}
});
const LockLookup = createClass({
displayName : 'LockLookup',
getDefaultProps : function() {
return {
fetchURL : '/api/lookup'
};
},
getInitialState : function() {
return {
query : '',
result : '',
error : '',
searching : false
};
},
handleChange(e){
this.setState({ query: e.target.value });
},
clickFn(){
this.setState({ searching: true, error: null });
request.put(`${this.props.fetchURL}/${this.state.query}`)
.then((res)=>this.setState({ result: res.body }))
.catch((err)=>this.setState({ result: err.response.body }))
.finally(()=>{
this.setState({ searching: false });
});
},
renderResult : function(){
return <div className='lockLookup'>
<h3>Result:</h3>
<table>
<tbody>
{Object.keys(this.state.result).map((key, idx)=>{
return <tr key={`${idx}-row`}>
<td key={`${idx}-key`}>{key}</td>
<td key={`${idx}-value`}>{this.state.result[key].toString()}
</td>
</tr>;
})}
</tbody>
</table>
</div>;
},
render : function() {
return <div className='brewLookup'>
<h2>{this.props.title}</h2>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='share id' />
<button onClick={this.clickFn}>
<i className={`fas ${!this.state.searching ? 'fa-search' : 'fa-spin fa-spinner'}`} />
</button>
{this.state.error
&& <div className='error'>{this.state.error.toString()}</div>
}
{this.state.result && this.renderResult()}
</div>;
}
});
module.exports = LockTools;

View File

@@ -0,0 +1,66 @@
.lockTools {
.lockBrew {
columns : 2;
.lockForm {
break-inside : avoid;
label {
display : inline-block;
width : 100%;
line-height : 2.25em;
text-align : right;
input {
float : right;
width : 65%;
margin-left : 10px;
}
&.checkbox {
line-height: 1.5em;
input {
width : 1.5em;
height : 1.5em;
}
}
}
}
.lockSuggestions {
line-height : 1.2em;
break-inside : avoid;
columns : 2;
h2 { column-span : all; }
h3 { margin-top : 0px; }
b { font-weight : 600; }
.lockCodes { break-inside : avoid; }
}
}
.lockTable {
cursor : default;
break-inside : avoid;
.row:hover {
color : #000000;
background-color : #CCCCCC;
}
.icon {
cursor : pointer;
&:hover { text-shadow : 0px 0px 6px black; }
}
}
th, td {
padding : 4px 10px;
text-align : center;
}
table, td { border : 1px solid #333333; }
.brewLookup {
min-height : 175px;
break-inside : avoid;
h2 { margin-top : 0px; }
}
button i { padding-left : 5px; }
}

View File

@@ -18,22 +18,20 @@
margin-bottom : unset;
font-family : monospace;
&[type="date"] {
width:14ch;
}
&[type='date'] { width : 14ch; }
}
textarea {
width : 50ch;
min-height : 7em;
max-height : 20em;
resize : vertical;
padding : 10px;
resize : vertical;
}
}
button {
width: 200px;
width : 200px;
i { margin-right : 10px; }
}

View File

@@ -1,6 +1,6 @@
.notificationLookup {
width : 450px;
height : fit-content;
height : fit-content;
.noNotification { margin-block : 20px; }
.notificationList {

View File

@@ -1,13 +1,11 @@
.anchored-box {
position:absolute;
@supports (inset-block-start: anchor(bottom)){
inset-block-start: anchor(bottom);
}
justify-self: anchor-center;
visibility: hidden;
&.active {
visibility: visible;
position : absolute;
visibility : hidden;
justify-self : anchor-center;
@supports (inset-block-start: anchor(bottom)) {
inset-block-start : anchor(bottom);
}
&.active { visibility : visible; }
}

View File

@@ -27,7 +27,7 @@
position : relative;
padding : 5px;
margin : 0 3px;
font-family : "Open Sans";
font-family : 'Open Sans';
font-size : 11px;
cursor : default;
&:hover {

View File

@@ -39,7 +39,7 @@ const BrewPage = (props)=>{
...props
};
const pageRef = useRef(null);
const cleanText = safeHTML(props.contents);
const cleanText = safeHTML(`${props.contents}\n<div class="columnSplit"></div>\n`);
useEffect(()=>{
if(!pageRef.current) return;
@@ -186,17 +186,16 @@ const BrewRenderer = (props)=>{
} else {
if(pageText.startsWith('\\page')) {
const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
const injectedTags = firstLineTokens.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
if(injectedTags) {
styles = { ...styles, ...injectedTags.styles };
styles = _.mapKeys(styles, (v, k) => k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
classes = [classes, injectedTags.classes].join(' ').trim();
attributes = injectedTags.attributes;
}
pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line
}
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)
const html = Markdown.render(pageText, index);
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />;

View File

@@ -1,43 +1,39 @@
@import (multiple, less) 'shared/naturalcrit/styles/reset.less';
.brewRenderer {
height : 100vh;
padding-top : 60px;
overflow-y : scroll;
will-change : transform;
padding-top : 60px;
height : 100vh;
&:has(.facing, .flow) {
padding : 60px 30px;
}
&.deployment {
background-color: darkred;
}
&:has(.facing, .flow) { padding : 60px 30px; }
&.deployment { background-color : darkred; }
:where(.pages) {
&.facing {
display: grid;
grid-template-columns: repeat(2, auto);
grid-template-rows: repeat(3, auto);
gap: 10px 10px;
justify-content: safe center;
display : grid;
grid-template-rows : repeat(3, auto);
grid-template-columns : repeat(2, auto);
gap : 10px 10px;
justify-content : safe center;
&.recto .page:first-child {
// sets first page on 'right' ('recto') of the preview, as if for a Cover page.
// todo: add a checkbox to toggle this setting
grid-column-start: 2;
grid-column-start : 2;
}
& :where(.page) {
margin-left: unset !important;
margin-right: unset !important;
margin-right : unset !important;
margin-left : unset !important;
}
}
&.flow {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: safe center;
display : flex;
flex-wrap : wrap;
gap : 10px;
justify-content : safe center;
& :where(.page) {
flex: 0 0 auto;
margin-left: unset !important;
margin-right: unset !important;
flex : 0 0 auto;
margin-right : unset !important;
margin-left : unset !important;
}
}
@@ -50,9 +46,7 @@
margin-left : auto;
box-shadow : 1px 4px 14px #000000;
}
*[id] {
scroll-margin-top:100px;
}
*[id] { scroll-margin-top : 100px; }
}
&::-webkit-scrollbar {
width : 20px;
@@ -79,11 +73,9 @@
overflow-y : unset;
.pages {
margin : 0px;
zoom: 100% !important;
zoom : 100% !important;
& > .page { box-shadow : unset; }
}
}
.headerNav {
visibility: hidden;
}
.headerNav { visibility : hidden; }
}

View File

@@ -25,7 +25,7 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
'.toc' : ()=>{ return 'Table of Contents'; },
};
const getHeaderContent = el => el.querySelector('h1')?.textContent;
const getHeaderContent = (el)=>el.querySelector('h1')?.textContent;
const topLevelPageSelector = Object.keys(topLevelPages).join(',');
@@ -52,25 +52,23 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
depth : 7, // All unmatched elements with IDs are set to the maximum depth (7)
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
link : el.id
}
};
if(el.classList.contains('page')) {
let text = `Page ${el.id.slice(1)}`; // Get the page # by trimming off the 'p' from the ID
const pageType = Object.keys(topLevelPages).find(pageType => el.querySelector(pageType));
if (pageType)
text += ` - ${topLevelPages[pageType](el, pageType)}` // If a Top Level Page, add extra label
const pageType = Object.keys(topLevelPages).find((pageType)=>el.querySelector(pageType));
if(pageType)
text += ` - ${topLevelPages[pageType](el, pageType)}`; // If a Top Level Page, add extra label
navEntry.depth = 0; // Pages are always at the least indented level
navEntry.text = text;
navEntry.className = 'pageLink';
}
else if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
} else if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
navEntry.depth = el.localName[1]; // Depth is set by the header level
}
navList.push(navEntry);
});
return _.map(navList, (navItem, index)=>
<HeaderNavItem {...navItem} key={index} />
return _.map(navList, (navItem, index)=><HeaderNavItem {...navItem} key={index} />
);
};

View File

@@ -1,39 +1,31 @@
.headerNav {
position: fixed;
top: 32px;
left: 0px;
padding: 5px 10px;
background-color: #ccc;
border-radius: 5px;
max-height: calc(100vh - 32px);
max-width: 40vw;
overflow-y: auto;
&.active {
padding-bottom: 10px;
.navIcon {
padding-bottom: 10px;
}
}
.navIcon {
cursor: pointer;
position : fixed;
top : 32px;
left : 0px;
max-width : 40vw;
max-height : calc(100vh - 32px);
padding : 5px 10px;
overflow-y : auto;
background-color : #CCCCCC;
border-radius : 5px;
&.active {
padding-bottom : 10px;
.navIcon { padding-bottom : 10px; }
}
.navIcon { cursor : pointer; }
li {
list-style-type: none;
list-style-type : none;
a {
display: inline-block;
width: 100%;
font-family: 'Open Sans';
font-size: 12px;
padding: 2px;
color: inherit;
text-decoration: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
&.pageLink {
font-weight: 900;
}
display : inline-block;
width : 100%;
padding : 2px;
font-family : 'Open Sans';
font-size : 12px;
color : inherit;
text-decoration : none;
cursor : pointer;
&:hover { text-decoration : underline; }
&.pageLink { font-weight : 900; }
@depths: 0,1,2,3,4,5,6,7;

View File

@@ -85,4 +85,9 @@
display : inline-block;
width : 100%;
}
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}

View File

@@ -156,7 +156,7 @@
min-width : 46px;
height : 100%;
&:hover { background-color : #444444; }
&:focus { border : 1px solid #D3D3D3;outline : none;}
&:focus {outline : none; border : 1px solid #D3D3D3;}
&:disabled {
color : #777777;
background-color : unset !important;
@@ -182,8 +182,8 @@
position : absolute;
left : 0;
z-index : 5;
display : flex;
width : 32px;
min-width : unset;
height : 100%;
display : flex;
}

View File

@@ -13,6 +13,7 @@ const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m;
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
const SNIPPETBAR_HEIGHT = 25;
const DEFAULT_STYLE_TEXT = dedent`
/*=======--- Example CSS styling ---=======*/
@@ -22,6 +23,13 @@ const DEFAULT_STYLE_TEXT = dedent`
color: black;
}`;
const DEFAULT_SNIPPET_TEXT = dedent`
\snippet example snippet
The text between \`\snippet title\` lines will become a snippet of name \`title\` as this example provides.
This snippet is accessible in the brew tab, and will be inherited if the brew is used as a theme.
`;
let isJumping = false;
const Editor = createClass({
@@ -36,6 +44,7 @@ const Editor = createClass({
onTextChange : ()=>{},
onStyleChange : ()=>{},
onMetaChange : ()=>{},
onSnipChange : ()=>{},
reportError : ()=>{},
onCursorPageChange : ()=>{},
@@ -52,7 +61,7 @@ const Editor = createClass({
getInitialState : function() {
return {
editorTheme : this.props.editorTheme,
view : 'text' //'text', 'style', 'meta'
view : 'text' //'text', 'style', 'meta', 'snippet'
};
},
@@ -62,12 +71,11 @@ const Editor = createClass({
isText : function() {return this.state.view == 'text';},
isStyle : function() {return this.state.view == 'style';},
isMeta : function() {return this.state.view == 'meta';},
isSnip : function() {return this.state.view == 'snippet';},
componentDidMount : function() {
this.updateEditorSize();
this.highlightCustomMarkdown();
window.addEventListener('resize', this.updateEditorSize);
document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys);
document.addEventListener('keydown', this.handleControlKeys);
@@ -82,10 +90,6 @@ const Editor = createClass({
}
},
componentWillUnmount : function() {
window.removeEventListener('resize', this.updateEditorSize);
},
componentDidUpdate : function(prevProps, prevState, snapshot) {
this.highlightCustomMarkdown();
@@ -118,14 +122,6 @@ const Editor = createClass({
}
},
updateEditorSize : function() {
if(this.codeEditor.current) {
let paneHeight = this.editor.current.parentNode.clientHeight;
paneHeight -= SNIPPETBAR_HEIGHT;
this.codeEditor.current.codeMirror.setSize(null, paneHeight);
}
},
updateCurrentCursorPage : function(cursor) {
const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1);
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
@@ -146,17 +142,17 @@ const Editor = createClass({
handleViewChange : function(newView){
this.props.setMoveArrows(newView === 'text');
this.setState({
view : newView
}, ()=>{
this.codeEditor.current?.codeMirror.focus();
this.updateEditorSize();
}); //TODO: not sure if updateeditorsize needed
});
},
highlightCustomMarkdown : function(){
if(!this.codeEditor.current) return;
if(this.state.view === 'text') {
if((this.state.view === 'text') ||(this.state.view === 'snippet')) {
const codeMirror = this.codeEditor.current.codeMirror;
codeMirror.operation(()=>{ // Batch CodeMirror styling
@@ -175,12 +171,18 @@ const Editor = createClass({
for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear();
let userSnippetCount = 1; // start snippet count from snippet 1
let editorPageCount = 1; // start page count from page 1
_.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{
const whichSource = this.state.view === 'text' ? this.props.brew.text : this.props.brew.snippets;
_.forEach(whichSource?.split('\n'), (line, lineNumber)=>{
const tabHighlight = this.state.view === 'text' ? 'pageLine' : 'snippetLine';
const textOrSnip = this.state.view === 'text';
//reset custom line styles
codeMirror.removeLineClass(lineNumber, 'background', 'pageLine');
codeMirror.removeLineClass(lineNumber, 'background', 'snippetLine');
codeMirror.removeLineClass(lineNumber, 'text');
codeMirror.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash');
@@ -191,22 +193,24 @@ const Editor = createClass({
// Styling for \page breaks
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
(this.props.renderer == 'V3' && line.match(PAGEBREAK_REGEX_V3))) {
(this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) {
if(lineNumber > 0) // Since \page is optional on first line of document,
if((lineNumber > 0) && (textOrSnip)) // Since \page is optional on first line of document,
editorPageCount += 1; // don't use it to increment page count; stay at 1
else if(this.state.view !== 'text') userSnippetCount += 1;
// add back the original class 'background' but also add the new class '.pageline'
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
codeMirror.addLineClass(lineNumber, 'background', tabHighlight);
const pageCountElement = Object.assign(document.createElement('span'), {
className : 'editor-page-count',
textContent : editorPageCount
textContent : textOrSnip ? editorPageCount : userSnippetCount
});
codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement);
};
// New Codemirror styling for V3 renderer
if(this.props.renderer == 'V3') {
if(this.props.renderer === 'V3') {
if(line.match(/^\\column$/)){
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
}
@@ -462,6 +466,21 @@ const Editor = createClass({
userThemes={this.props.userThemes}/>
</>;
}
if(this.isSnip()){
if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; }
return <>
<CodeEditor key='codeEditor'
ref={this.codeEditor}
language='gfm'
view={this.state.view}
value={this.props.brew.snippets}
onChange={this.props.onSnipChange}
enableFolding={true}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent} />
</>;
}
},
redo : function(){
@@ -502,7 +521,7 @@ const Editor = createClass({
historySize={this.historySize()}
currentEditorTheme={this.state.editorTheme}
updateEditorTheme={this.updateEditorTheme}
snippetBundle={this.props.snippetBundle}
themeBundle={this.props.themeBundle}
cursorPos={this.codeEditor.current?.getCursorPosition() || {}}
updateBrew={this.props.updateBrew}
/>

View File

@@ -1,12 +1,13 @@
@import 'themes/codeMirror/customEditorStyles.less';
.editor {
position : relative;
width : 100%;
container: editor / inline-size;
position : relative;
width : 100%;
height : 100%;
container : editor / inline-size;
.codeEditor {
height : 100%;
.pageLine {
.CodeMirror { height : 100%; }
.pageLine, .snippetLine {
background : #33333328;
border-top : #333399 solid 1px;
}
@@ -14,6 +15,10 @@
float : right;
color : grey;
}
.editor-snippet-count {
float : right;
color : grey;
}
.columnSplit {
font-style : italic;
color : grey;
@@ -45,26 +50,26 @@
color : green;
}
.emoji:not(.cm-comment) {
margin-left : 2px;
color : #360034;
background : #ffc8ff;
border-radius : 6px;
font-weight : bold;
padding-bottom : 1px;
margin-left : 2px;
font-weight : bold;
color : #360034;
outline : solid 2px #FF96FC;
outline-offset : -2px;
outline : solid 2px #ff96fc;
background : #FFC8FF;
border-radius : 6px;
}
.superscript:not(.cm-comment) {
font-weight : bold;
color : goldenrod;
vertical-align : super;
font-size : 0.9em;
font-weight : bold;
vertical-align : super;
color : goldenrod;
}
.subscript:not(.cm-comment) {
font-weight : bold;
color : rgb(123, 123, 15);
vertical-align : sub;
font-size : 0.9em;
font-weight : bold;
vertical-align : sub;
color : rgb(123, 123, 15);
}
.dl-highlight {
&.dl-colon-highlight {
@@ -104,3 +109,7 @@
}
}
@container editor (width < 553px) {
.editor .codeEditor .CodeMirror { height : calc(100% - 51px);}
}

View File

@@ -4,7 +4,6 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
import request from '../../utils/request-middleware.js';
const Nav = require('naturalcrit/nav/nav.jsx');
const Combobox = require('client/components/combobox.jsx');
const TagInput = require('../tagInput/tagInput.jsx');
@@ -48,7 +47,7 @@ const MetadataEditor = createClass({
getInitialState : function(){
return {
showThumbnail : true
showThumbnail : true
};
},
@@ -68,7 +67,7 @@ const MetadataEditor = createClass({
const inputRules = validations[name] ?? [];
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
const debouncedReportValidity = _.debounce((target, errMessage) => {
const debouncedReportValidity = _.debounce((target, errMessage)=>{
callIfExists(target, 'setCustomValidity', errMessage);
callIfExists(target, 'reportValidity');
}, 300); // 300ms debounce delay, adjust as needed
@@ -87,7 +86,7 @@ const MetadataEditor = createClass({
return `- ${err}`;
}).join('\n');
debouncedReportValidity(e.target, errMessage);
return false;
}
@@ -110,6 +109,7 @@ const MetadataEditor = createClass({
}
this.props.onChange(this.props.metadata, 'renderer');
},
handlePublish : function(val){
this.props.onChange({
...this.props.metadata,

View File

@@ -1,8 +1,8 @@
@import 'naturalcrit/styles/colors.less';
.userThemeName {
padding-left: 10px;
padding-right: 10px;
padding-right : 10px;
padding-left : 10px;
}
.metadataEditor {
@@ -12,20 +12,20 @@
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
padding : 25px;
overflow-y : auto;
font-size : 13px;
background-color : #999999;
font-size : 13px;
h1 {
margin: 0 0 40px;
font-weight: bold;
text-transform: uppercase;
margin : 0 0 40px;
font-weight : bold;
text-transform : uppercase;
}
h2 {
margin : 20px 0;
font-weight : bold;
border-bottom: 2px solid gray;
color: #555;
margin : 20px 0;
font-weight : bold;
color : #555555;
border-bottom : 2px solid gray;
}
& > div { margin-bottom : 10px; }
@@ -54,10 +54,10 @@
min-width : 200px;
& > label {
width : 80px;
font-size : 0.9em;
font-weight : 800;
line-height : 1.8em;
text-transform : uppercase;
font-size: .9em;
}
& > .value {
flex : 1 1 auto;
@@ -74,7 +74,7 @@
border : 1px solid gray;
&:focus { outline : 1px solid #444444; }
}
&.thumbnail, &.themes{
&.thumbnail, &.themes {
label { line-height : 2.0em; }
.value {
overflow : hidden;
@@ -90,14 +90,14 @@
}
}
&.themes{
&.themes {
.value {
overflow : visible;
text-overflow : auto;
}
button {
padding-left: 5px;
padding-right: 5px;
padding-right : 5px;
padding-left : 5px;
}
}
@@ -136,8 +136,8 @@
margin-right : 15px;
font-size : 0.9em;
font-weight : 800;
white-space : nowrap;
vertical-align : middle;
white-space : nowrap;
cursor : pointer;
user-select : none;
}
@@ -164,9 +164,7 @@
.colorButton(@red);
}
}
.authors.field .value {
line-height : 1.5em;
}
.authors.field .value { line-height : 1.5em; }
.themes.field {
& .dropdown-container {
@@ -174,9 +172,7 @@
z-index : 100;
background-color : white;
}
& .dropdown-options {
overflow-y : visible;
}
& .dropdown-options { overflow-y : visible; }
.disabled {
font-style : italic;
color : dimgray;

View File

@@ -28,18 +28,18 @@ module.exports = {
return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null;
}
],
theme: [
(value) => {
theme : [
(value)=>{
const URL = global.config.baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); //Escape any regex characters
const shareIDPattern = '[a-zA-Z0-9-_]{12}';
const shareURLRegex = new RegExp(`^${URL}\\/share\\/${shareIDPattern}$`);
const shareIDRegex = new RegExp(`^${shareIDPattern}$`);
if (value?.length === 0) return null;
if (shareURLRegex.test(value)) return null;
if (shareIDRegex.test(value)) return null;
if(value?.length === 0) return null;
if(shareURLRegex.test(value)) return null;
if(shareIDRegex.test(value)) return null;
return 'Must be a valid Share URL or a 12-character ID.';
}
}
]
};

View File

@@ -6,6 +6,7 @@ const _ = require('lodash');
const cx = require('classnames');
import { loadHistory } from '../../utils/versionHistory.js';
import { brewSnippetsToJSON } from '../../../../shared/helpers.js';
//Import all themes
const ThemeSnippets = {};
@@ -40,7 +41,7 @@ const Snippetbar = createClass({
unfoldCode : ()=>{},
updateEditorTheme : ()=>{},
cursorPos : {},
snippetBundle : [],
themeBundle : [],
updateBrew : ()=>{}
};
},
@@ -64,7 +65,10 @@ const Snippetbar = createClass({
},
componentDidUpdate : async function(prevProps, prevState) {
if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) {
if(prevProps.renderer != this.props.renderer ||
prevProps.theme != this.props.theme ||
prevProps.themeBundle != this.props.themeBundle ||
prevProps.brew.snippets != this.props.brew.snippets) {
this.setState({
snippets : this.compileSnippets()
});
@@ -97,7 +101,7 @@ const Snippetbar = createClass({
if(key == 'snippets') {
const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme
return result.filter((snip)=>snip.gen || snip.subsnippets);
}
};
},
compileSnippets : function() {
@@ -105,15 +109,21 @@ const Snippetbar = createClass({
let oldSnippets = _.keyBy(compiledSnippets, 'groupName');
for (let snippets of this.props.snippetBundle) {
if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name
snippets = ThemeSnippets[snippets];
if(this.props.themeBundle.snippets) {
for (let snippets of this.props.themeBundle.snippets) {
if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name
snippets = ThemeSnippets[snippets];
const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName');
compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer));
const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName');
compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer));
oldSnippets = _.keyBy(compiledSnippets, 'groupName');
oldSnippets = _.keyBy(compiledSnippets, 'groupName');
}
}
const userSnippetsasJSON = brewSnippetsToJSON(this.props.brew.title || 'New Document', this.props.brew.snippets, this.props.themeBundle.snippets);
compiledSnippets.push(userSnippetsasJSON);
return compiledSnippets;
},
@@ -207,59 +217,60 @@ const Snippetbar = createClass({
renderEditorButtons : function(){
if(!this.props.showEditButtons) return;
return (
<div className='editors'>
{this.props.view !== 'meta' && <><div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() }
<div className='editors'>
{this.props.view !== 'meta' && <><div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() }
</div>
<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>
<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>
<div className='codeTools'>
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</div></>}
<div className='codeTools'>
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</div></>}
<div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' />
<div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' />
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('snippet', { selected: this.props.view === 'snippet' })}
onClick={()=>this.props.onViewChange('snippet')}>
<i className='fas fa-th-list' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</div>
</div>
)
</div>
);
},
render : function(){
@@ -272,11 +283,6 @@ const Snippetbar = createClass({
module.exports = Snippetbar;
const SnippetGroup = createClass({
displayName : 'SnippetGroup',
getDefaultProps : function() {
@@ -310,7 +316,8 @@ const SnippetGroup = createClass({
},
render : function(){
return <div className='snippetGroup snippetBarButton'>
const snippetGroup = `snippetGroup snippetBarButton ${this.props.snippets.length === 0 ? 'disabledSnippets' : ''}`;
return <div className={snippetGroup}>
<div className='text'>
<i className={this.props.icon} />
<span className='groupName'>{this.props.groupName}</span>

View File

@@ -14,15 +14,15 @@
.snippets {
display : flex;
justify-content : flex-start;
min-width : 327.58px;
min-width : 432.18px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
}
.editors {
display : flex;
justify-content : flex-end;
min-width : 225px;
min-width : 250px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
&:only-child { margin-left : auto;min-width:unset;}
&:only-child {min-width : unset; margin-left : auto;}
>div {
display : flex;
@@ -39,9 +39,7 @@
text-align : center;
cursor : pointer;
&.editorTool:not(.active) {
cursor:not-allowed;
}
&.editorTool:not(.active) { cursor : not-allowed; }
&:hover,&.selected { background-color : #999999; }
&.text {
@@ -53,6 +51,9 @@
&.meta {
.tooltipLeft('Properties');
}
&.snip {
.tooltipLeft('Snippets');
}
&.undo {
.tooltipLeft('Undo');
font-size : 0.75em;
@@ -151,9 +152,9 @@
position : absolute;
top : 100%;
z-index : 1000;
visibility : hidden;
padding : 0px;
margin-left : -5px;
visibility : hidden;
background-color : #DDDDDD;
.snippet {
position : relative;
@@ -228,8 +229,15 @@
}
}
}
.disabledSnippets {
color: grey;
cursor: not-allowed;
&:hover { background-color: #DDDDDD;}
}
}
@container editor (width < 553px) {
@container editor (width < 683px) {
.snippetBar {
.editors {
flex : 1;

View File

@@ -3,43 +3,43 @@ const React = require('react');
const { useState, useEffect } = React;
const _ = require('lodash');
const TagInput = ({ unique = true, values = [], ...props }) => {
const TagInput = ({ unique = true, values = [], ...props })=>{
const [tempInputText, setTempInputText] = useState('');
const [tagList, setTagList] = useState(values.map((value) => ({ value, editing: false })));
const [tagList, setTagList] = useState(values.map((value)=>({ value, editing: false })));
useEffect(()=>{
handleChange(tagList.map((context)=>context.value))
}, [tagList])
handleChange(tagList.map((context)=>context.value));
}, [tagList]);
const handleChange = (value)=>{
props.onChange({
target : { value }
})
});
};
const handleInputKeyDown = ({ evt, value, index, options = {} }) => {
if (_.includes(['Enter', ','], evt.key)) {
const handleInputKeyDown = ({ evt, value, index, options = {} })=>{
if(_.includes(['Enter', ','], evt.key)) {
evt.preventDefault();
submitTag(evt.target.value, value, index);
if (options.clear) {
if(options.clear) {
setTempInputText('');
}
}
};
const submitTag = (newValue, originalValue, index) => {
setTagList((prevContext) => {
const submitTag = (newValue, originalValue, index)=>{
setTagList((prevContext)=>{
// remove existing tag
if(newValue === null){
return [...prevContext].filter((context, i)=>i !== index);
}
// add new tag
if(originalValue === null){
return [...prevContext, { value: newValue, editing: false }]
return [...prevContext, { value: newValue, editing: false }];
}
// update existing tag
return prevContext.map((context, i) => {
if (i === index) {
return prevContext.map((context, i)=>{
if(i === index) {
return { ...context, value: newValue, editing: false };
}
return context;
@@ -47,10 +47,10 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
});
};
const editTag = (index) => {
setTagList((prevContext) => {
return prevContext.map((context, i) => {
if (i === index) {
const editTag = (index)=>{
setTagList((prevContext)=>{
return prevContext.map((context, i)=>{
if(i === index) {
return { ...context, editing: true };
}
return { ...context, editing: false };
@@ -58,25 +58,25 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
});
};
const renderReadTag = (context, index) => {
const renderReadTag = (context, index)=>{
return (
<li key={index}
data-value={context.value}
className='tag'
onClick={() => editTag(index)}>
onClick={()=>editTag(index)}>
{context.value}
<button onClick={(evt)=>{evt.stopPropagation(); submitTag(null, context.value, index)}}><i className='fa fa-times fa-fw'/></button>
<button onClick={(evt)=>{evt.stopPropagation(); submitTag(null, context.value, index);}}><i className='fa fa-times fa-fw'/></button>
</li>
);
};
const renderWriteTag = (context, index) => {
const renderWriteTag = (context, index)=>{
return (
<input type='text'
key={index}
defaultValue={context.value}
onKeyDown={(evt) => handleInputKeyDown({evt, value: context.value, index: index})}
autoFocus
defaultValue={context.value}
onKeyDown={(evt)=>handleInputKeyDown({ evt, value: context.value, index: index })}
autoFocus
/>
);
};
@@ -86,7 +86,7 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
<label>{props.label}</label>
<div className='value'>
<ul className='list'>
{tagList.map((context, index) => { return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })}
{tagList.map((context, index)=>{ return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })}
</ul>
<input
@@ -94,8 +94,8 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
className='value'
placeholder={props.placeholder}
value={tempInputText}
onChange={(e) => setTempInputText(e.target.value)}
onKeyDown={(evt) => handleInputKeyDown({ evt, value: null, options: { clear: true } })}
onChange={(e)=>setTempInputText(e.target.value)}
onKeyDown={(evt)=>handleInputKeyDown({ evt, value: null, options: { clear: true } })}
/>
</div>
</div>

View File

@@ -1,36 +1,32 @@
@import 'naturalcrit/styles/core.less';
.homebrew{
.homebrew {
height : 100%;
.sitePage{
.sitePage {
display : flex;
height : 100%;
background-color : @steel;
flex-direction : column;
height : 100%;
overflow-y : hidden;
.content{
background-color : @steel;
.content {
position : relative;
height : calc(~"100% - 29px"); //Navbar height
flex : auto;
height : calc(~'100% - 29px'); //Navbar height
overflow-y : hidden;
}
&.listPage .content {
overflow-y : scroll;
&::-webkit-scrollbar {
width: 20px;
&:horizontal{
height: 20px;
width:auto;
width : 20px;
&:horizontal {
width : auto;
height : 20px;
}
&-thumb {
background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px);
&:horizontal{
background: linear-gradient(0deg, #d3c1af 15px, #00000000 15px);
}
}
&-corner {
visibility: hidden;
background : linear-gradient(90deg, #D3C1AF 15px, #00000000 15px);
&:horizontal { background : linear-gradient(0deg, #D3C1AF 15px, #00000000 15px); }
}
&-corner { visibility : hidden; }
}
}
}

View File

@@ -1,78 +1,70 @@
.navItem.error {
position : relative;
background-color : @red;
position : relative;
background-color : @red;
}
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 1000;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
.lowercase {
text-transform : none;
.errorContainer {
position : absolute;
top : 100%;
left : 50%;
z-index : 1000;
width : 140px;
padding : 3px;
font-size : 10px;
font-weight : 800;
color : white;
text-align : center;
text-transform : uppercase;
background-color : #333333;
border : 3px solid #444444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
animation-name : glideDown;
animation-duration : 0.4s;
.lowercase { text-transform : none; }
a { color : @teal; }
&::before {
position : absolute;
top : -23px;
left : 53px;
width : 0px;
height : 0px;
content : '';
border-top : 10px solid transparent;
border-right : 10px solid transparent;
border-bottom : 10px solid #444444;
border-left : 10px solid transparent;
}
&::after {
position : absolute;
top : -19px;
left : 53px;
width : 0px;
height : 0px;
content : '';
border-top : 10px solid transparent;
border-right : 10px solid transparent;
border-bottom : 10px solid #333333;
border-left : 10px solid transparent;
}
.deny {
display : inline-block;
width : 48%;
padding : 5px;
margin : 1px;
background-color : #333333;
border-left : 1px solid #666666;
.animate(background-color);
&:hover { background-color : red; }
}
.confirm {
display : inline-block;
width : 48%;
padding : 5px;
margin : 1px;
color : white;
background-color : #333333;
.animate(background-color);
&:hover { background-color : teal; }
}
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
}

View File

@@ -24,11 +24,11 @@
}
.homebrew nav {
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
background-color : #333333;
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
.navSection {
display : flex;
@@ -82,8 +82,8 @@
font-weight : 800;
line-height : 13px;
color : white;
text-decoration : none;
text-transform : uppercase;
text-decoration : none;
cursor : pointer;
background-color : #333333;
i {
@@ -106,11 +106,11 @@
display : block;
width : 100%;
overflow : hidden;
text-overflow : ellipsis;
font-size : 12px;
font-weight : 800;
color : white;
text-align : center;
text-overflow : ellipsis;
text-transform : initial;
white-space : nowrap;
background-color : transparent;
@@ -170,16 +170,16 @@
h4 {
box-sizing : border-box;
display : block;
flex-basis : 20%;
flex-grow : 1;
flex-basis : 20%;
min-width : 76px;
padding : 5px 0;
color : #BBBBBB;
text-align : center;
}
p {
flex-basis : 80%;
flex-grow : 1;
flex-basis : 80%;
padding : 5px 0;
font-family : 'Open Sans', sans-serif;
font-size : 10px;
@@ -215,10 +215,10 @@
z-index : 10000;
box-sizing : border-box;
display : block;
visibility : hidden;
width : 100%;
padding : 13px 5px;
text-align : center;
visibility : hidden;
background-color : #333333;
}
}

View File

@@ -30,11 +30,11 @@ const BrewItem = ({
}
request.delete(`/api/${brew.googleId ?? ''}${brew.editId}`).send().end((err, res)=>{
if (err) reportError(err); else window.location.reload();
});
if(err) reportError(err); else window.location.reload();
});
}, [brew, reportError]);
const updateFilter = useCallback((type, term)=> updateListFilter(type, term), [updateListFilter]);
const updateFilter = useCallback((type, term)=>updateListFilter(type, term), [updateListFilter]);
const renderDeleteBrewLink = ()=>{
if(!brew.editId) return null;

View File

@@ -1,148 +1,129 @@
.brewItem{
.brewItem {
position : relative;
box-sizing : border-box;
display : inline-block;
vertical-align : top;
box-sizing : border-box;
box-sizing : border-box;
overflow : hidden;
width : 48%;
min-height : 105px;
margin-right : 15px;
margin-bottom : 15px;
padding : 5px 15px 2px 6px;
padding-right : 15px;
border : 1px solid #c9ad6a;
margin-right : 15px;
margin-bottom : 15px;
overflow : hidden;
vertical-align : top;
background-color : #CAB2802E;
border : 1px solid #C9AD6A;
border-radius : 5px;
box-shadow : 0px 4px 5px 0px #333333;
break-inside : avoid;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
box-shadow : 0px 4px 5px 0px #333;
background-color : #cab2802e;
.thumbnail {
position: absolute;
width: 150px;
height: 100%;
top: 0;
right: 0;
z-index: -1;
background-size: contain;
background-repeat: no-repeat;
background-position: right top;
mask-image: linear-gradient(80deg, #0000 20%, #050 40%);
-webkit-mask-image: linear-gradient(80deg, #0000 20%, #050 40%);
opacity: 50%;
.thumbnail {
position : absolute;
top : 0;
right : 0;
z-index : -1;
width : 150px;
height : 100%;
background-repeat : no-repeat;
background-position : right top;
background-size : contain;
opacity : 50%;
-webkit-mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%);
mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%);
}
.text {
min-height : 54px;
h4{
h4 {
margin-bottom : 5px;
font-size : 2.2em;
}
}
.info{
position: initial;
bottom: 2px;
font-family : ScalySansRemake;
.info {
position : initial;
bottom : 2px;
font-family : "ScalySansRemake";
font-size : 1.2em;
&>span{
& > span {
margin-right : 12px;
line-height : 1.5em;
a {
color:inherit;
}
a { color : inherit; }
}
}
.brewTags span {
background-color: #c8ac6e3b;
margin: 2px;
padding: 2px;
border: 1px solid #c8ac6e;
border-radius: 4px;
white-space: nowrap;
display: inline-block;
font-weight: bold;
border-color: currentColor;
cursor : pointer;
&:before {
font-family: 'Font Awesome 5 Free';
font-size: 12px;
margin-right: 3px;
display : inline-block;
padding : 2px;
margin : 2px;
font-weight : bold;
white-space : nowrap;
cursor : pointer;
background-color : #C8AC6E3B;
border : 1px solid #C8AC6E;
border-color : currentColor;
border-radius : 4px;
&::before {
margin-right : 3px;
font-family : 'Font Awesome 5 Free';
font-size : 12px;
}
&.type {
background-color: #0080003b;
color: #008000;
&:before{
content: '\f0ad';
}
color : #008000;
background-color : #0080003B;
&::before { content : '\f0ad'; }
}
&.group {
background-color: #5050503b;
color: #000000;
&:before{
content: '\f500';
}
color : #000000;
background-color : #5050503B;
&::before { content : '\f500'; }
}
&.meta {
background-color: #0000803b;
color: #000080;
&:before{
content: '\f05a';
}
color : #000080;
background-color : #0000803B;
&::before { content : '\f05a'; }
}
&.system {
background-color: #8000003b;
color: #800000;
&:before{
content: '\f518';
}
color : #800000;
background-color : #8000003B;
&::before { content : '\f518'; }
}
}
&:hover{
.links{
opacity : 1;
}
&:hover {
.links { opacity : 1; }
}
&:nth-child(2n + 1){
margin-right : 0px;
}
.links{
&:nth-child(2n + 1) { margin-right : 0px; }
.links {
.animate(opacity);
position : absolute;
top : 0px;
right : 0px;
height : 100%;
width : 2em;
opacity : 0;
background-color : fade(black, 60%);
height : 100%;
text-align : center;
a{
background-color : fade(black, 60%);
opacity : 0;
a {
.animate(opacity);
display : block;
margin : 8px 0px;
opacity : 0.6;
font-size : 1.3em;
color : white;
text-decoration : unset;
&:hover{
opacity : 1;
}
i{
cursor : pointer;
}
opacity : 0.6;
&:hover { opacity : 1; }
i { cursor : pointer; }
}
}
.googleDriveIcon {
height : 18px;
height : 18px;
padding : 0px;
margin : -5px;
}
.homebreweryIcon {
mix-blend-mode : darken;
height : 24px;
position : relative;
top : 5px;
left : -5px;
height : 24px;
mix-blend-mode : darken;
}
}

View File

@@ -1,5 +1,5 @@
.noColumns(){
.noColumns() {
column-count : auto;
column-fill : auto;
column-gap : normal;
@@ -13,177 +13,151 @@
height : auto;
min-height : 279.4mm;
margin : 20px auto;
contain : unset;
contain : unset;
}
.listPage{
.content{
.listPage {
.content {
z-index : 1;
.page{
.page {
.noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer
&::after{
display : none;
}
.noBrews{
&::after { display : none; }
.noBrews {
margin : 10px 0px;
font-size : 1.3em;
font-style : italic;
}
.brewCollection {
h1:hover{
cursor: pointer;
}
h1:hover { cursor : pointer; }
.active::before, .inactive::before {
font-family: 'Font Awesome 5 Free';
font-weight: 900;
font-size: 0.6cm;
padding-right: 0.5em;
}
.active {
color: var(--HB_Color_HeaderText);
}
.active::before {
content: '\f107';
}
.inactive {
color: #707070;
}
.inactive::before {
content: '\f105';
padding-right : 0.5em;
font-family : 'Font Awesome 5 Free';
font-size : 0.6cm;
font-weight : 900;
}
.active { color : var(--HB_Color_HeaderText); }
.active::before { content : '\f107'; }
.inactive { color : #707070; }
.inactive::before { content : '\f105'; }
}
}
}
.sort-container {
font-family : 'Open Sans', sans-serif;
position : sticky;
top : 0;
left : 0;
width : 100%;
height : 30px;
background-color : #555;
border-top : 1px solid #666;
border-bottom : 1px solid #666;
color : white;
text-align : center;
z-index : 1;
display : flex;
justify-content : center;
align-items : baseline;
column-gap : 15px;
row-gap : 5px;
flex-wrap : wrap;
h6{
text-transform : uppercase;
position : sticky;
top : 0;
left : 0;
z-index : 1;
display : flex;
flex-wrap : wrap;
row-gap : 5px;
column-gap : 15px;
align-items : baseline;
justify-content : center;
width : 100%;
height : 30px;
font-family : 'Open Sans', sans-serif;
color : white;
text-align : center;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
h6 {
font-family : 'Open Sans', sans-serif;
font-size : 11px;
font-weight : bold;
text-transform : uppercase;
}
.sort-option {
display: flex;
align-items: center;
padding: 0 8px;
color: #ccc;
height: 100%;
display : flex;
align-items : center;
height : 100%;
padding : 0 8px;
color : #CCCCCC;
&:hover{
background-color : #444;
}
&:hover { background-color : #444444; }
&.active {
font-weight: bold;
color: #ddd;
background-color: #333;
font-weight : bold;
color : #DDDDDD;
background-color : #333333;
button {
color: white;
font-weight: 800;
height: 100%;
& + .sortDir {
padding-left: 5px;
button {
height : 100%;
font-weight : 800;
color : white;
& + .sortDir { padding-left : 5px; }
}
}
}
}
.filter-option {
margin-left: 20px;
background-color : transparent !important;
margin-left : 20px;
font-size : 11px;
i{
padding-right : 5px;
}
background-color : transparent !important;
i { padding-right : 5px; }
}
button {
padding : 0;
font-family : 'Open Sans', sans-serif;
font-size : 11px;
font-weight : normal;
color : #CCCCCC;
text-transform : uppercase;
background-color : transparent;
}
button{
background-color : transparent;
font-family : 'Open Sans', sans-serif;
text-transform : uppercase;
font-weight : normal;
font-size : 11px;
color : #ccc;
padding : 0;
}
}
.tags-container {
height : 30px;
background-color : #555;
border-top : 1px solid #666;
border-bottom : 1px solid #666;
color : white;
display : flex;
justify-content : center;
align-items : center;
column-gap : 15px;
row-gap : 5px;
flex-wrap : wrap;
row-gap : 5px;
column-gap : 15px;
align-items : center;
justify-content : center;
height : 30px;
color : white;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
span {
padding : 3px;
font-family : 'Open Sans', sans-serif;
font-size : 11px;
font-weight : bold;
color : #DFDFDF;
cursor : pointer;
border : 1px solid;
border-radius : 3px;
padding : 3px;
cursor : pointer;
color: #dfdfdf;
&:before {
font-family: 'Font Awesome 5 Free';
font-size: 12px;
margin-right: 3px;
&::before {
margin-right : 3px;
font-family : 'Font Awesome 5 Free';
font-size : 12px;
}
&:after {
content: '\f00d';
font-family: 'Font Awesome 5 Free';
font-size: 12px;
margin-left: 3px;
&::after {
margin-left : 3px;
font-family : 'Font Awesome 5 Free';
font-size : 12px;
content : '\f00d';
}
&.type {
background-color: #008000;
border-color: #00a000;
&:before{
content: '\f0ad';
}
background-color : #008000;
border-color : #00A000;
&::before { content : '\f0ad'; }
}
&.group {
background-color: #505050;
border-color: #000000;
&:before{
content: '\f500';
}
background-color : #505050;
border-color : #000000;
&::before { content : '\f500'; }
}
&.meta {
background-color: #000080;
border-color: #0000a0;
&:before{
content: '\f05a';
}
background-color : #000080;
border-color : #0000A0;
&::before { content : '\f05a'; }
}
&.system {
background-color: #800000;
border-color: #a00000;
&:before{
content: '\f518';
}
background-color : #800000;
border-color : #A00000;
&::before { content : '\f518'; }
}
}
}

View File

@@ -1,7 +1,7 @@
.homebrew {
.uiPage.sitePage {
.content {
width : ~"min(90vw, 1000px)";
width : ~'min(90vw, 1000px)';
padding : 2% 4%;
margin-top : 25px;
margin-right : auto;
@@ -17,19 +17,19 @@
border : 2px solid black;
border-radius : 5px;
button {
width : 125px;
margin-right : 5px;
color : black;
background-color : transparent;
border : 1px solid black;
border-radius : 5px;
width : 125px;
color : black;
margin-right : 5px;
&.active {
background-color: #0007;
color: white;
&:before {
content: '\f00c';
font-family: 'FONT AWESOME 5 FREE';
margin-right: 5px;
color : white;
background-color : #00000077;
&::before {
margin-right : 5px;
font-family : 'FONT AWESOME 5 FREE';
content : '\f00c';
}
}
}
@@ -59,6 +59,11 @@
padding-left : 1.25em;
list-style : square;
}
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
}
}

View File

@@ -102,6 +102,14 @@ const EditPage = createClass({
window.onbeforeunload = function(){};
document.removeEventListener('keydown', this.handleControlKeys);
},
componentDidUpdate : function(){
const hasChange = this.hasChanges();
if(this.state.isPending != hasChange){
this.setState({
isPending : hasChange
});
}
},
handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return;
@@ -138,6 +146,17 @@ const EditPage = createClass({
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();});
},
handleSnipChange : function(snippet){
//If there are errors, run the validator on every change to give quick feedback
let htmlErrors = this.state.htmlErrors;
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
this.setState((prevState)=>({
brew : { ...prevState.brew, snippets: snippet },
isPending : true,
htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();});
@@ -145,8 +164,7 @@ const EditPage = createClass({
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : { ...prevState.brew, style: style },
isPending : true
brew : { ...prevState.brew, style: style }
}), ()=>{if(this.state.autoSave) this.trySave();});
},
@@ -158,8 +176,7 @@ const EditPage = createClass({
brew : {
...prevState.brew,
...metadata
},
isPending : true,
}
}), ()=>{if(this.state.autoSave) this.trySave();});
},
@@ -247,16 +264,17 @@ const EditPage = createClass({
});
if(!res) return;
this.savedBrew = res.body;
this.savedBrew = {
...this.state.brew,
googleId : res.body.googleId ? res.body.googleId : null,
editId : res.body.editId,
shareId : res.body.shareId,
version : res.body.version
};
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
this.setState((prevState)=>({
brew : { ...prevState.brew,
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
editId : this.savedBrew.editId,
shareId : this.savedBrew.shareId,
version : this.savedBrew.version
},
this.setState(()=>({
brew : this.savedBrew,
isPending : false,
isSaving : false,
unsavedTime : new Date()
@@ -311,7 +329,14 @@ const EditPage = createClass({
},
renderSaveButton : function(){
if(this.state.autoSaveWarning && this.hasChanges()){
// #1 - Currently saving, show SAVING
if(this.state.isSaving){
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
}
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
if(this.state.isPending && this.state.autoSaveWarning){
this.setAutosaveWarning();
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
@@ -324,18 +349,17 @@ const EditPage = createClass({
</Nav.item>;
}
if(this.state.isSaving){
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
// #3 - Unsaved changes exist, click to save, show SAVE NOW
// Use trySave(true) instead of save() to use debounced save function
if(this.state.isPending){
return <Nav.item className='save' onClick={()=>this.trySave(true)} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
}
if(this.state.isPending && this.hasChanges()){
return <Nav.item className='save' onClick={this.save} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
}
if(!this.state.isPending && !this.state.isSaving && this.state.autoSave){
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(this.state.autoSave){
return <Nav.item className='save saved'>auto-saved.</Nav.item>;
}
if(!this.state.isPending && !this.state.isSaving){
return <Nav.item className='save saved'>saved.</Nav.item>;
}
// DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved.</Nav.item>;
},
handleAutoSave : function(){
@@ -431,7 +455,7 @@ const EditPage = createClass({
<Meta name='robots' content='noindex, nofollow' />
{this.renderNavbar()}
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} reviewRequested={this.props.brew.lock.reviewRequested} />}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
@@ -439,12 +463,12 @@ const EditPage = createClass({
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onSnipChange={this.handleSnipChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
themeBundle={this.state.themeBundle}
snippetBundle={this.state.themeBundle.snippets}
updateBrew={this.updateBrew}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}

View File

@@ -1,29 +1,25 @@
@keyframes glideDown {
0% {transform : translate(-50% + 3px, 0px);
opacity : 0;}
100% {transform : translate(-50% + 3px, 10px);
opacity : 1;}
0% {
opacity : 0;transform : translate(-50% + 3px, 0px);}
100% {
opacity : 1;transform : translate(-50% + 3px, 10px);}
}
.editPage{
.navItem.save{
.editPage {
.navItem.save {
position : relative;
width : 106px;
text-align : center;
position : relative;
&.saved{
&.saved {
color : #666666;
cursor : initial;
color : #666;
}
}
.googleDriveStorage {
position : relative;
}
.googleDriveStorage img{
height : 18px;
.googleDriveStorage { position : relative; }
.googleDriveStorage img {
height : 18px;
padding : 0px;
margin : -5px;
&.inactive {
filter: grayscale(1);
}
&.inactive { filter : grayscale(1); }
}
}

View File

@@ -1,17 +1,30 @@
require('./lockNotification.less');
const React = require('react');
import './lockNotification.less';
import * as React from 'react';
import request from '../../../utils/request-middleware.js';
import Dialog from '../../../../components/dialog.jsx';
function LockNotification(props) {
props = {
shareId : 0,
disableLock : ()=>{},
message : '',
shareId : 0,
disableLock : ()=>{},
lock : {},
message : 'Unable to retrieve Lock Message',
reviewRequested : false,
...props
};
const removeLock = ()=>{
alert(`Not yet implemented - ID ${props.shareId}`);
const [reviewState, setReviewState] = React.useState(props.reviewRequested);
const removeLock = async ()=>{
await request.put(`/api/lock/review/request/${props.shareId}`)
.then(()=>{
setReviewState(true);
});
};
const renderReviewButton = function(){
if(reviewState){ return <button className='inactive'>REVIEW REQUESTED</button>; };
return <button onClick={removeLock}>REQUEST LOCK REMOVAL</button>;
};
return <Dialog className='lockNotification' blocking closeText='CONTINUE TO EDITOR' >
@@ -19,11 +32,11 @@ function LockNotification(props) {
<p>This brew been locked by the Administrators. It will not be accessible by any method other than the Editor until the lock is removed.</p>
<hr />
<h3>LOCK REASON</h3>
<p>{props.message || 'Unable to retrieve Lock Message'}</p>
<p>{props.message}</p>
<hr />
<p>Once you have resolved this issue, click REQUEST LOCK REMOVAL to notify the Administrators for review.</p>
<p>Click CONTINUE TO EDITOR to temporarily hide this notification; it will reappear the next time the page is reloaded.</p>
<button onClick={removeLock}>REQUEST LOCK REMOVAL</button>
{renderReviewButton()}
</Dialog>;
};

View File

@@ -11,10 +11,12 @@
&::backdrop { background-color : #000000AA; }
button {
padding : 2px 15px;
margin : 10px;
color : white;
background-color : #333333;
&.inactive,
&:hover { background-color : #777777; }
}

View File

@@ -167,7 +167,7 @@ const errorIndex = (props)=>{
**Requested access:** ${props.brew.accessType}
**Brew ID:** ${props.brew.brewId}`,
// Theme Not Valid
'10' : dedent`
## The selected theme is not tagged as a theme.
@@ -194,13 +194,47 @@ const errorIndex = (props)=>{
**Brew ID:** ${props.brew.brewId}
**Brew Title:** ${escape(props.brew.brewTitle)}`,
**Brew Title:** ${escape(props.brew.brewTitle)}
**Brew Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}`,
// ####### Admin page error #######
'52' : dedent`
## Access Denied
You need to provide correct administrator credentials to access this page.`,
// ####### Lock Errors
'60' : dedent`Lock Error: General`,
'61' : dedent`Lock Get Error: Unable to get lock count`,
'62' : dedent`Lock Set Error: Cannot lock`,
'63' : dedent`Lock Set Error: Brew not found`,
'64' : dedent`Lock Set Error: Already locked`,
'65' : dedent`Lock Remove Error: Cannot unlock`,
'66' : dedent`Lock Remove Error: Brew not found`,
'67' : dedent`Lock Remove Error: Not locked`,
'68' : dedent`Lock Get Review Error: Cannot get review requests`,
'69' : dedent`Lock Set Review Error: Cannot set review request`,
'70' : dedent`Lock Set Review Error: Brew not found`,
'71' : dedent`Lock Set Review Error: Review already requested`,
'72' : dedent`Lock Remove Review Error: Cannot clear review request`,
'73' : dedent`Lock Remove Review Error: Brew not found`,
// ####### Other Errors
'90' : dedent` An unexpected error occurred while looking for these brews.
Try again in a few minutes.`,

View File

@@ -100,32 +100,32 @@ const HomePage = createClass({
return <div className='homePage sitePage'>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{this.renderNavbar()}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer}
showEditButtons={false}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
/>
</SplitPane>
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer}
showEditButtons={false}
themeBundle={this.state.themeBundle}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
/>
</SplitPane>
</div>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
Save current <i className='fas fa-save' />

View File

@@ -1,50 +1,40 @@
.homePage{
.homePage {
position : relative;
a.floatingNewButton{
a.floatingNewButton {
.animate(background-color);
position : absolute;
display : block;
right : 70px;
bottom : 50px;
z-index : 100;
z-index : 5001;
display : block;
padding : 1em;
background-color : @orange;
font-size : 1.5em;
color : white;
text-decoration : none;
background-color : @orange;
box-shadow : 3px 3px 15px black;
&:hover{
background-color : darken(@orange, 20%);
}
&:hover { background-color : darken(@orange, 20%); }
}
.floatingSaveButton{
.floatingSaveButton {
.animateAll();
position : absolute;
display : block;
right : 200px;
bottom : 70px;
z-index : 100;
z-index : 5000;
display : block;
padding : 0.8em;
cursor : pointer;
background-color : @blue;
font-size : 0.8em;
color : white;
text-decoration : none;
cursor : pointer;
background-color : @blue;
box-shadow : 3px 3px 15px black;
&:hover{
background-color : darken(@blue, 20%);
}
&.show{
right : 350px;
}
&:hover { background-color : darken(@blue, 20%); }
&.show { right : 350px; }
}
.navItem.save{
background-color: @orange;
&:hover{
background-color: @green;
}
.navItem.save {
background-color : @orange;
&:hover { background-color : @green; }
}
}

View File

@@ -141,6 +141,18 @@ const NewPage = createClass({
localStorage.setItem(STYLEKEY, style);
},
handleSnipChange : function(snippet){
//If there are errors, run the validator on every change to give quick feedback
let htmlErrors = this.state.htmlErrors;
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
this.setState((prevState)=>({
brew : { ...prevState.brew, snippets: snippet },
isPending : true,
htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();});
},
handleMetaChange : function(metadata, field=undefined){
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
fetchThemeBundle(this, metadata.renderer, metadata.theme);
@@ -223,39 +235,39 @@ const NewPage = createClass({
render : function(){
return <div className='newPage sitePage'>
{this.renderNavbar()}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
themeBundle={this.state.themeBundle}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
onSnipChange={this.handleSnipChange}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
themeBundle={this.state.themeBundle}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
</div>
</div>;
}

View File

@@ -1,8 +1,6 @@
.newPage{
.navItem.save{
background-color: @orange;
&:hover{
background-color: @green;
}
.newPage {
.navItem.save {
background-color : @orange;
&:hover { background-color : @green; }
}
}

View File

@@ -1,9 +1,7 @@
.sharePage{
.sharePage {
nav .navSection.titleSection {
flex-grow: 1;
justify-content: center;
}
.content{
overflow-y : hidden;
flex-grow : 1;
justify-content : center;
}
.content { overflow-y : hidden; }
}

View File

@@ -99,14 +99,14 @@ const VaultPage = (props)=>{
setSearching(true);
setError(null);
const title = titleRef.current.value || '';
const author = authorRef.current.value || '';
const count = countRef.current.value || 10;
const v3 = v3Ref.current.checked != false;
const legacy = legacyRef.current.checked != false;
const title = titleRef.current.value || '';
const author = authorRef.current.value || '';
const count = countRef.current.value || 10;
const v3 = v3Ref.current.checked != false;
const legacy = legacyRef.current.checked != false;
const sortOption = sort || 'title';
const dirOption = dir || 'asc';
const pageProp = page || 1;
const dirOption = dir || 'asc';
const pageProp = page || 1;
setSort(sortOption);
setdir(dirOption);
@@ -247,7 +247,7 @@ const VaultPage = (props)=>{
</li>
<li>
Some common words like "a", "after", "through", "itself", "here", etc.,
are ignored in searches. The full list can be found &nbsp;
are ignored in searches. The full list can be found&nbsp;
<a href='https://github.com/mongodb/mongo/blob/0e3b3ca8480ddddf5d0105d11a94bd4698335312/src/mongo/db/fts/stop_words_english.txt'>
here
</a>
@@ -286,9 +286,9 @@ const VaultPage = (props)=>{
};
const renderPaginationControls = ()=>{
if(!totalBrews) return null;
if(!totalBrews || totalBrews < 10) return null;
const countInt = parseInt(props.query.count || 20);
const countInt = parseInt(brewCollection.length || 20);
const totalPages = Math.ceil(totalBrews / countInt);
let startPage, endPage;
@@ -355,7 +355,7 @@ const VaultPage = (props)=>{
};
const renderFoundBrews = ()=>{
if(searching) {
if(searching && !brewCollection) {
return (
<div className='foundBrews searching'>
<h3 className='searchAnim'>Searching</h3>
@@ -395,6 +395,7 @@ const VaultPage = (props)=>{
{`Brews found: `}
<span>{totalBrews}</span>
</span>
{brewCollection.length > 10 && renderPaginationControls()}
{brewCollection.map((brew, index)=>{
return (
<BrewItem
@@ -415,14 +416,14 @@ const VaultPage = (props)=>{
<link href='/themes/V3/Blank/style.css' rel='stylesheet' />
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet' />
{renderNavItems()}
<div className="content">
<SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'>
{renderSortBar()}
{renderFoundBrews()}
</div>
</SplitPane>
<div className='content'>
<SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'>
{renderSortBar()}
{renderFoundBrews()}
</div>
</SplitPane>
</div>
</div>
);

View File

@@ -5,7 +5,7 @@
*:not(input) { user-select : none; }
.content .dataGroup {
:where(.content .dataGroup) {
width : 100%;
height : 100%;
background : white;
@@ -169,9 +169,10 @@
width : 100%;
height : 100%;
max-height : 100%;
padding : 50px 50px 70px 50px;
padding : 70px 50px;
overflow-y : scroll;
background-color : #2C3E50;
container-type : inline-size;
h3 { font-size : 25px; }
@@ -236,6 +237,7 @@
margin-right : 40px;
color : black;
isolation : isolate;
transition : width 0.5s;
&::after {
position : absolute;
@@ -269,8 +271,8 @@
.links { z-index : 2; }
hr {
margin : 0px;
visibility : hidden;
margin : 0px;
}
.thumbnail { z-index : -1; }
@@ -278,30 +280,37 @@
.paginationControls {
position : absolute;
top : 35px;
left : 50%;
display : grid;
grid-template-areas : 'previousPage currentPage nextPage';
grid-template-columns : 50px 1fr 50px;
gap : 20px;
place-items : center;
width : auto;
font-size : 15px;
translate : -50%;
&:last-child { top : unset; }
.pages {
display : flex;
grid-area : currentPage;
gap : 1em;
justify-content : space-evenly;
width : 100%;
height : 100%;
padding : 5px 8px;
text-align : center;
.pageNumber {
margin-inline : 1vw;
place-content : center;
width : fit-content;
min-width : 2em;
font-family : 'Open Sans';
font-weight : 900;
color : white;
text-underline-position : under;
text-wrap : nowrap;
text-underline-position : under;
cursor : pointer;
&.currentPage {
@@ -329,7 +338,6 @@
}
}
}
}
@keyframes trailingDots {
@@ -344,8 +352,7 @@
100% { content : ' ...'; }
}
// media query for when the page is smaller than 1079 px in width
@media screen and (max-width : 1079px) {
@container (width < 670px) {
.vaultPage {
.dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; }

View File

@@ -1,84 +1,34 @@
.fac {
display : inline-block;
background-color : currentColor;
mask-size : contain;
mask-repeat : no-repeat;
mask-position : center;
width : 1em;
aspect-ratio : 1;
background-color : currentColor;
mask-repeat : no-repeat;
mask-position : center;
mask-size : contain;
}
.position-top-left {
mask-image: url('../icons/position-top-left.svg');
}
.position-top-right {
mask-image: url('../icons/position-top-right.svg');
}
.position-bottom-left {
mask-image: url('../icons/position-bottom-left.svg');
}
.position-bottom-right {
mask-image: url('../icons/position-bottom-right.svg');
}
.position-top {
mask-image: url('../icons/position-top.svg');
}
.position-right {
mask-image: url('../icons/position-right.svg');
}
.position-bottom {
mask-image: url('../icons/position-bottom.svg');
}
.position-left {
mask-image: url('../icons/position-left.svg');
}
.mask-edge {
mask-image: url('../icons/mask-edge.svg');
}
.mask-corner {
mask-image: url('../icons/mask-corner.svg');
}
.mask-center {
mask-image: url('../icons/mask-center.svg');
}
.book-front-cover {
mask-image: url('../icons/book-front-cover.svg');
}
.book-back-cover {
mask-image: url('../icons/book-back-cover.svg');
}
.book-inside-cover {
mask-image: url('../icons/book-inside-cover.svg');
}
.book-part-cover {
mask-image: url('../icons/book-part-cover.svg');
}
.image-wrap-left {
mask-image: url('../icons/image-wrap-left.svg');
}
.image-wrap-right {
mask-image: url('../icons/image-wrap-right.svg');
}
.davek {
mask-image: url('../icons/Davek.svg');
}
.rellanic {
mask-image: url('../icons/Rellanic.svg');
}
.iokharic {
mask-image: url('../icons/Iokharic.svg');
}
.zoom-to-fit {
mask-image: url('../icons/zoom-to-fit.svg');
}
.fit-width {
mask-image: url('../icons/fit-width.svg');
}
.single-spread {
mask-image: url('../icons/single-spread.svg');
}
.facing-spread {
mask-image: url('../icons/facing-spread.svg');
}
.flow-spread {
mask-image: url('../icons/flow-spread.svg');
}
.position-top-left { mask-image : url('../icons/position-top-left.svg'); }
.position-top-right { mask-image : url('../icons/position-top-right.svg'); }
.position-bottom-left { mask-image : url('../icons/position-bottom-left.svg'); }
.position-bottom-right { mask-image : url('../icons/position-bottom-right.svg'); }
.position-top { mask-image : url('../icons/position-top.svg'); }
.position-right { mask-image : url('../icons/position-right.svg'); }
.position-bottom { mask-image : url('../icons/position-bottom.svg'); }
.position-left { mask-image : url('../icons/position-left.svg'); }
.mask-edge { mask-image : url('../icons/mask-edge.svg'); }
.mask-corner { mask-image : url('../icons/mask-corner.svg'); }
.mask-center { mask-image : url('../icons/mask-center.svg'); }
.book-front-cover { mask-image : url('../icons/book-front-cover.svg'); }
.book-back-cover { mask-image : url('../icons/book-back-cover.svg'); }
.book-inside-cover { mask-image : url('../icons/book-inside-cover.svg'); }
.book-part-cover { mask-image : url('../icons/book-part-cover.svg'); }
.image-wrap-left { mask-image : url('../icons/image-wrap-left.svg'); }
.image-wrap-right { mask-image : url('../icons/image-wrap-right.svg'); }
.davek { mask-image : url('../icons/Davek.svg'); }
.rellanic { mask-image : url('../icons/Rellanic.svg'); }
.iokharic { mask-image : url('../icons/Iokharic.svg'); }
.zoom-to-fit { mask-image : url('../icons/zoom-to-fit.svg'); }
.fit-width { mask-image : url('../icons/fit-width.svg'); }
.single-spread { mask-image : url('../icons/single-spread.svg'); }
.facing-spread { mask-image : url('../icons/facing-spread.svg'); }
.flow-spread { mask-image : url('../icons/flow-spread.svg'); }

1240
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.17.0",
"version": "3.18.1",
"type": "module",
"engines": {
"npm": "^10.2.x",
"npm": "^10.8.x",
"node": "^20.18.x"
},
"repository": {
@@ -84,22 +84,21 @@
]
},
"dependencies": {
"@babel/core": "^7.26.9",
"@babel/plugin-transform-runtime": "^7.26.9",
"@babel/core": "^7.26.10",
"@babel/plugin-transform-runtime": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^8.14.0",
"body-parser": "^1.20.2",
"@googleapis/drive": "^11.0.0",
"body-parser": "^2.2.0",
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.7",
"core-js": "^3.40.0",
"core-js": "^3.41.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"dompurify": "^3.2.4",
"expr-eval": "^2.0.2",
"express": "^4.21.2",
"express": "^5.1.0",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
"fs-extra": "11.3.0",
@@ -108,39 +107,43 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "13.0.3",
"marked-emoji": "^1.4.3",
"marked-extended-tables": "^1.1.0",
"marked": "15.0.8",
"marked-emoji": "^2.0.0",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.0.1",
"marked-alignment-paragraphs": "^1.0.0",
"marked-nonbreaking-spaces": "^1.0.1",
"marked-smartypants-lite": "^1.0.3",
"marked-subsuper-text": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.10.1",
"nanoid": "5.1.0",
"mongoose": "^8.13.2",
"nanoid": "5.1.5",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
"react-router": "^7.2.0",
"react-router": "^7.5.0",
"romans": "^3.0.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
"superagent": "^10.2.0",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git",
"written-number": "^0.11.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.2",
"babel-plugin-transform-import-meta": "^2.3.2",
"eslint": "^9.20.1",
"eslint": "^9.24.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.15.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.0.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.14.1",
"stylelint": "^16.18.0",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^15.0.0",
"supertest": "^7.0.0"
"stylelint-config-recommended": "^16.0.0",
"supertest": "^7.1.0"
}
}

View File

@@ -10,7 +10,7 @@ import babel from '@babel/core';
import babelConfig from '../babel.config.json' with { type : 'json' };
import less from 'less';
const isDev = !!process.argv.find((arg) => arg === '--dev');
const isDev = !!process.argv.find((arg)=>arg === '--dev');
const babelify = async (code)=>(await babel.transformAsync(code, babelConfig)).code;
@@ -53,7 +53,7 @@ fs.emptyDirSync('./build');
const themes = { Legacy: {}, V3: {} };
let themeFiles = fs.readdirSync('./themes/Legacy');
for (let dir of themeFiles) {
for (const dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`).toString());
themeData.path = dir;
themes.Legacy[dir] = (themeData);
@@ -70,7 +70,7 @@ fs.emptyDirSync('./build');
}
themeFiles = fs.readdirSync('./themes/V3');
for (let dir of themeFiles) {
for (const dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`).toString());
themeData.path = dir;
themes.V3[dir] = (themeData);
@@ -113,7 +113,7 @@ fs.emptyDirSync('./build');
const stream = fs.createWriteStream(editorThemeFile, { flags: 'a' });
stream.write('[\n"default"');
for (let themeFile of editorThemeFiles) {
for (const themeFile of editorThemeFiles) {
stream.write(`,\n"${themeFile.slice(0, -4)}"`);
}
stream.write('\n]\n');

View File

@@ -1,3 +1,4 @@
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
import { model as HomebrewModel } from './homebrew.model.js';
import { model as NotificationModel } from './notifications.model.js';
import express from 'express';
@@ -11,6 +12,7 @@ import { splitTextStyleAndMetadata } from '../shared/helpers.js';
const router = express.Router();
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3';
@@ -162,6 +164,180 @@ router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
}
});
// ####################### LOCKS
router.get('/api/lock/count', mw.adminOnly, asyncHandler(async (req, res)=>{
const countLocksQuery = {
lock : { $exists: true }
};
const count = await HomebrewModel.countDocuments(countLocksQuery)
.catch((error)=>{
throw { name: 'Lock Count Error', message: 'Unable to get lock count', status: 500, HBErrorCode: '61', error };
});
return res.json({ count });
}));
router.get('/api/locks', mw.adminOnly, asyncHandler(async (req, res)=>{
const countLocksPipeline = [
{
$match :
{
'lock' : { '$exists': 1 }
},
},
{
$project : {
shareId : 1,
editId : 1,
title : 1,
lock : 1
}
}
];
const lockedDocuments = await HomebrewModel.aggregate(countLocksPipeline)
.catch((error)=>{
throw { name: 'Can Not Get Locked Brews', message: 'Unable to get locked brew collection', status: 500, HBErrorCode: '68', error };
});
return res.json({
lockedDocuments
});
}));
router.post('/api/lock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
const lock = req.body;
lock.applied = new Date;
const filter = {
shareId : req.params.id
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to lock', shareId: req.params.id, status: 500, HBErrorCode: '63' };
if(brew.lock && !lock.overwrite) {
throw { name: 'Already Locked', message: 'Lock already exists on brew', shareId: req.params.id, title: brew.title, status: 500, HBErrorCode: '64' };
}
lock.overwrite = undefined;
brew.lock = lock;
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Lock Error', message: 'Unable to set lock', shareId: req.params.id, status: 500, HBErrorCode: '62', error };
});
return res.json({ name: 'LOCKED', message: `Lock applied to brew ID ${brew.shareId} - ${brew.title}`, ...lock });
}));
router.put('/api/unlock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
const filter = {
shareId : req.params.id
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to unlock', shareId: req.params.id, status: 500, HBErrorCode: '66' };
if(!brew.lock) throw { name: 'Not Locked', message: 'Cannot unlock as brew is not locked', shareId: req.params.id, status: 500, HBErrorCode: '67' };
brew.lock = undefined;
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Cannot Unlock', message: 'Unable to clear lock', shareId: req.params.id, status: 500, HBErrorCode: '65', error };
});
return res.json({ name: 'Unlocked', message: `Lock removed from brew ID ${req.params.id}` });
}));
router.get('/api/lock/reviews', mw.adminOnly, asyncHandler(async (req, res)=>{
const countReviewsPipeline = [
{
$match :
{
'lock.reviewRequested' : { '$exists': 1 }
},
},
{
$project : {
shareId : 1,
editId : 1,
title : 1,
lock : 1
}
}
];
const reviewDocuments = await HomebrewModel.aggregate(countReviewsPipeline)
.catch((error)=>{
throw { name: 'Can Not Get Reviews', message: 'Unable to get review collection', status: 500, HBErrorCode: '68', error };
});
return res.json({
reviewDocuments
});
}));
router.put('/api/lock/review/request/:id', asyncHandler(async (req, res)=>{
// === This route is NOT Admin only ===
// Any user can request a review of their document
const filter = {
shareId : req.params.id,
lock : { $exists: 1 }
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) { throw { name: 'Brew Not Found', message: `Cannot find a locked brew with ID ${req.params.id}`, code: 500, HBErrorCode: '70' }; };
if(brew.lock.reviewRequested){
throw { name: 'Review Already Requested', message: `Review already requested for brew ${brew.shareId} - ${brew.title}`, code: 500, HBErrorCode: '71' };
};
brew.lock.reviewRequested = new Date();
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Can Not Set Review Request', message: `Unable to set request for review on brew ID ${req.params.id}`, code: 500, HBErrorCode: '69', error };
});
return res.json({ name: 'Review Requested', message: `Review requested on brew ID ${brew.shareId} - ${brew.title}` });
}));
router.put('/api/lock/review/remove/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
const filter = {
shareId : req.params.id,
'lock.reviewRequested' : { $exists: 1 }
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) { throw { name: 'Can Not Clear Review Request', message: `Brew ID ${req.params.id} does not have a review pending!`, HBErrorCode: '73' }; };
brew.lock.reviewRequested = undefined;
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Can Not Clear Review Request', message: `Unable to remove request for review on brew ID ${req.params.id}`, HBErrorCode: '72', error };
});
return res.json({ name: 'Review Request Cleared', message: `Review request removed for brew ID ${brew.shareId} - ${brew.title}` });
}));
// ####################### NOTIFICATIONS
router.get('/admin/notification/all', async (req, res, next)=>{

View File

@@ -1,6 +1,8 @@
/*eslint max-lines: ["warn", {"max": 1000, "skipBlankLines": true, "skipComments": true}]*/
import supertest from 'supertest';
import HBApp from './app.js';
import {model as NotificationModel } from './notifications.model.js';
import { model as NotificationModel } from './notifications.model.js';
import { model as HomebrewModel } from './homebrew.model.js';
// Mimic https responses to avoid being redirected all the time
@@ -16,7 +18,7 @@ describe('Tests for admin api', ()=>{
const testNotifications = ['a', 'b'];
jest.spyOn(NotificationModel, 'find')
.mockImplementationOnce(() => {
.mockImplementationOnce(()=>{
return { exec: jest.fn().mockResolvedValue(testNotifications) };
});
@@ -59,7 +61,7 @@ describe('Tests for admin api', ()=>{
expect(response.body).toEqual(savedNotification);
});
it('should handle error adding a notification without dismissKey', async () => {
it('should handle error adding a notification without dismissKey', async ()=>{
const inputNotification = {
title : 'Test Notification',
text : 'This is a test notification',
@@ -75,7 +77,7 @@ describe('Tests for admin api', ()=>{
const response = await app
.post('/admin/notification/add')
.set('Authorization', 'Basic ' + Buffer.from('admin:password3').toString('base64'))
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.send(inputNotification);
expect(response.status).toBe(500);
@@ -86,14 +88,14 @@ describe('Tests for admin api', ()=>{
const dismissKey = 'testKey';
jest.spyOn(NotificationModel, 'findOneAndDelete')
.mockImplementationOnce((key) => {
.mockImplementationOnce((key)=>{
return { exec: jest.fn().mockResolvedValue(key) };
});
const response = await app
.delete(`/admin/notification/delete/${dismissKey}`)
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'});
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
expect(response.status).toBe(200);
expect(response.body).toEqual({ dismissKey: 'testKey' });
});
@@ -102,16 +104,602 @@ describe('Tests for admin api', ()=>{
const dismissKey = 'testKey';
jest.spyOn(NotificationModel, 'findOneAndDelete')
.mockImplementationOnce(() => {
.mockImplementationOnce(()=>{
return { exec: jest.fn().mockResolvedValue() };
});
const response = await app
.delete(`/admin/notification/delete/${dismissKey}`)
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'});
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
expect(response.status).toBe(500);
expect(response.body).toEqual({ message: 'Notification not found' });
});
});
describe('Locks', ()=>{
describe('Count', ()=>{
it('Count of all locked documents', async ()=>{
const testNumber = 16777216; // 8^8, because why not
jest.spyOn(HomebrewModel, 'countDocuments')
.mockImplementationOnce(()=>{
return Promise.resolve(testNumber);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/count');
expect(response.status).toBe(200);
expect(response.body).toEqual({ count: testNumber });
});
it('Handle error while fetching count of locked documents', async ()=>{
jest.spyOn(HomebrewModel, 'countDocuments')
.mockImplementationOnce(()=>{
return Promise.reject();
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/count');
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '61',
message : 'Unable to get lock count',
name : 'Lock Count Error',
originalUrl : '/api/lock/count',
status : 500,
});
});
});
describe('Lists', ()=>{
it('Get list of all locked documents', async ()=>{
const testLocks = ['a', 'b'];
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.resolve(testLocks);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/locks');
expect(response.status).toBe(200);
expect(response.body).toEqual({ lockedDocuments: testLocks });
});
it('Handle error while fetching list of all locked documents', async ()=>{
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.reject();
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/locks');
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '68',
message : 'Unable to get locked brew collection',
name : 'Can Not Get Locked Brews',
originalUrl : '/api/locks',
status : 500
});
});
it('Get list of all locked documents with pending review requests', async ()=>{
const testLocks = ['a', 'b'];
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.resolve(testLocks);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/reviews');
expect(response.status).toBe(200);
expect(response.body).toEqual({ reviewDocuments: testLocks });
});
it('Handle error while fetching list of all locked documents with pending review requests', async ()=>{
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.reject();
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/reviews');
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '68',
message : 'Unable to get review collection',
name : 'Can Not Get Reviews',
originalUrl : '/api/lock/reviews',
status : 500
});
});
});
describe('Lock', ()=>{
it('Lock a brew', async ()=>{
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); }
};
const testLock = {
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(200);
expect(response.body).toMatchObject({
applied : expect.any(String),
code : testLock.code,
editMessage : testLock.editMessage,
shareMessage : testLock.shareMessage,
name : 'LOCKED',
message : `Lock applied to brew ID ${testBrew.shareId} - ${testBrew.title}`
});
});
it('Overwrite lock on a locked brew', async ()=>{
const testLock = {
code : 999,
editMessage : 'newEdit',
shareMessage : 'newShare',
overwrite : true
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : {
code : 1,
editMessage : 'oldEdit',
shareMessage : 'oldShare',
}
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(200);
expect(response.body).toMatchObject({
applied : expect.any(String),
code : testLock.code,
editMessage : testLock.editMessage,
shareMessage : testLock.shareMessage,
name : 'LOCKED',
message : `Lock applied to brew ID ${testBrew.shareId} - ${testBrew.title}`
});
});
it('Error when locking a locked brew', async ()=>{
const testLock = {
code : 999,
editMessage : 'newEdit',
shareMessage : 'newShare'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : {
code : 1,
editMessage : 'oldEdit',
shareMessage : 'oldShare',
}
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '64',
message : 'Lock already exists on brew',
name : 'Already Locked',
originalUrl : `/api/lock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500,
title : 'title'
});
});
it('Handle save error while locking a brew', async ()=>{
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); }
};
const testLock = {
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '62',
message : 'Unable to set lock',
name : 'Lock Error',
originalUrl : `/api/lock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500
});
});
});
describe('Unlock', ()=>{
it('Unlock a brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/unlock/${testBrew.shareId}`);
expect(response.status).toBe(200);
expect(response.body).toEqual({
name : 'Unlocked',
message : `Lock removed from brew ID ${testBrew.shareId}`
});
});
it('Error when unlocking a brew with no lock', async ()=>{
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/unlock/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '67',
message : 'Cannot unlock as brew is not locked',
name : 'Not Locked',
originalUrl : `/api/unlock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500,
});
});
it('Handle error while unlocking a brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/unlock/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '65',
message : 'Unable to clear lock',
name : 'Cannot Unlock',
originalUrl : `/api/unlock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500
});
});
});
describe('Reviews', ()=>{
it('Add review request to a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`);
expect(response.status).toBe(200);
expect(response.body).toEqual({
message : `Review requested on brew ID ${testBrew.shareId} - ${testBrew.title}`,
name : 'Review Requested',
});
});
it('Error when cannot find a locked brew', async ()=>{
const testBrew = {
shareId : 'shareId'
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(false);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`)
.catch((err)=>{return err;});
expect(response.status).toBe(500);
expect(response.body).toEqual({
message : `Cannot find a locked brew with ID ${testBrew.shareId}`,
name : 'Brew Not Found',
HBErrorCode : '70',
code : 500,
originalUrl : `/api/lock/review/request/${testBrew.shareId}`
});
});
it('Error when review is already requested', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share',
reviewRequested : 'YES'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(false);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`)
.catch((err)=>{return err;});
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '70',
code : 500,
message : `Cannot find a locked brew with ID ${testBrew.shareId}`,
name : 'Brew Not Found',
originalUrl : `/api/lock/review/request/${testBrew.shareId}`
});
});
it('Handle error while adding review request to a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '69',
code : 500,
message : `Unable to set request for review on brew ID ${testBrew.shareId}`,
name : 'Can Not Set Review Request',
originalUrl : `/api/lock/review/request/${testBrew.shareId}`
});
});
it('Clear review request from a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share',
reviewRequested : 'YES'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/lock/review/remove/${testBrew.shareId}`);
expect(response.status).toBe(200);
expect(response.body).toEqual({
message : `Review request removed for brew ID ${testBrew.shareId} - ${testBrew.title}`,
name : 'Review Request Cleared'
});
});
it('Error when clearing review request from a brew with no review request', async ()=>{
const testBrew = {
shareId : 'shareId',
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(false);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/lock/review/remove/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '73',
message : `Brew ID ${testBrew.shareId} does not have a review pending!`,
name : 'Can Not Clear Review Request',
originalUrl : `/api/lock/review/remove/${testBrew.shareId}`
});
});
it('Handle error while clearing review request from a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share',
reviewRequested : 'YES'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/lock/review/remove/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '72',
message : `Unable to remove request for review on brew ID ${testBrew.shareId}`,
name : 'Can Not Clear Review Request',
originalUrl : `/api/lock/review/remove/${testBrew.shareId}`
});
});
});
});
});

View File

@@ -2,7 +2,7 @@
// Set working directory to project root
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import packageJSON from './../package.json' with { type: 'json' };
import packageJSON from './../package.json' with { type: 'json' };
const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(`${__dirname}/..`);
@@ -11,7 +11,6 @@ const version = packageJSON.version;
import _ from 'lodash';
import jwt from 'jwt-simple';
import express from 'express';
import yaml from 'js-yaml';
import config from './config.js';
import fs from 'fs-extra';
@@ -70,13 +69,11 @@ const corsOptions = {
'https://homebrewery-stage.herokuapp.com',
];
if(isLocalEnvironment) {
allowedOrigins.push('http://localhost:8000', 'http://localhost:8010');
}
const localNetworkRegex = /^http:\/\/(localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[0-1])\.\d+\.\d+):\d+$/;
const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app
if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin)) {
if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) {
callback(null, true);
} else {
console.log(origin, 'not allowed');
@@ -352,7 +349,7 @@ app.get('/user/:username', async (req, res, next)=>{
app.put('/api/user/rename', async (req, res)=>{
const { username, newUsername } = req.body;
const ownAccount = req.account && (req.account.username == newUsername);
if(!username || !newUsername)
return res.status(400).json({ error: 'Username and newUsername are required.' });
if(!ownAccount)

View File

@@ -27,12 +27,12 @@ if(!config.get('service_account')){
const defaultAuth = serviceAuth || config.get('google_api_key');
const retryConfig = {
retry: 3, // Number of retry attempts
retryDelay: 100, // Initial delay in milliseconds
retryDelayMultiplier: 2, // Multiplier for exponential backoff
maxRetryDelay: 32000, // Maximum delay in milliseconds
httpMethodsToRetry: ['PATCH'], // Only retry PATCH requests
statusCodesToRetry: [[429, 429]], // Only retry on 429 status code
retry : 3, // Number of retry attempts
retryDelay : 100, // Initial delay in milliseconds
retryDelayMultiplier : 2, // Multiplier for exponential backoff
maxRetryDelay : 32000, // Maximum delay in milliseconds
httpMethodsToRetry : ['PATCH'], // Only retry PATCH requests
statusCodesToRetry : [[429, 429]], // Only retry on 429 status code
};
const GoogleActions = {
@@ -177,8 +177,8 @@ const GoogleActions = {
mimeType : 'text/plain',
body : brew.text
},
headers: {
'X-Forwarded-For': userIp, // Set the X-Forwarded-For header
headers : {
'X-Forwarded-For' : userIp, // Set the X-Forwarded-For header
},
retryConfig
})

View File

@@ -8,9 +8,11 @@ import Markdown from '../shared/naturalcrit/markdown.js';
import yaml from 'js-yaml';
import asyncHandler from 'express-async-handler';
import { nanoid } from 'nanoid';
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
import { splitTextStyleAndMetadata,
brewSnippetsToJSON } from '../shared/helpers.js';
import checkClientVersion from './middleware/check-client-version.js';
const router = express.Router();
import { DEFAULT_BREW, DEFAULT_BREW_LOAD } from './brewDefaults.js';
@@ -92,7 +94,7 @@ const api = {
const accessMap = {
edit : { editId: id },
share : { shareId: id },
admin : { $or : [{ editId: id }, { shareId: id }] }
admin : { $or: [{ editId: id }, { shareId: id }] }
};
// Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine.
@@ -118,8 +120,8 @@ const api = {
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04' };
}
if(stub?.lock?.locked && accessType != 'edit') {
throw { HBErrorCode: '51', code: stub?.lock.code, message: stub?.lock.shareMessage, brewId: stub?.shareId, brewTitle: stub?.title };
if(stub?.lock && accessType === 'share') {
throw { HBErrorCode: '51', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title, brewAuthors: stub.authors };
}
// If there's a google id, get it if requesting the full brew or if no stub found yet
@@ -175,12 +177,15 @@ const api = {
`${text}`;
}
const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme']);
const snippetsArray = brewSnippetsToJSON('brew_snippets', brew.snippets, null, false).snippets;
metadata.snippets = snippetsArray.length > 0 ? snippetsArray : undefined;
text = `\`\`\`metadata\n` +
`${yaml.dump(metadata)}\n` +
`\`\`\`\n\n` +
`${text}`;
return text;
},
getGoodBrewTitle : (text)=>{
const tokens = Markdown.marked.lexer(text);
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
@@ -294,13 +299,13 @@ const api = {
currentTheme = req.brew;
splitTextStyleAndMetadata(currentTheme);
if(!currentTheme.tags.some(tag => tag === "meta:theme" || tag === "meta:Theme"))
if(!currentTheme.tags.some((tag)=>tag === 'meta:theme' || tag === 'meta:Theme'))
throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', status: 422, HBErrorCode: '10' };
themeName ??= currentTheme.title;
themeAuthor ??= currentTheme.authors?.[0];
// If there is anything in the snippets or style members, append them to the appropriate array
if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets));
if(currentTheme?.snippets) completeSnippets.push({ name: currentTheme.title, snippets: currentTheme.snippets });
if(currentTheme?.style) completeStyles.push(`/* From Brew: ${req.protocol}://${req.get('host')}/share/${req.params.id} */\n\n${currentTheme.style}`);
req.params.id = currentTheme.theme;

View File

@@ -302,7 +302,7 @@ describe('Tests for api', ()=>{
});
it('access is denied to a locked brew', async()=>{
const lockBrew = { title: 'test brew', shareId: '1', lock: { locked: true, code: 404, shareMessage: 'brew locked' } };
const lockBrew = { title: 'test brew', shareId: '1', lock: { code: 404, shareMessage: 'brew locked' } };
model.get = jest.fn(()=>toBrewPromise(lockBrew));
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
@@ -939,7 +939,7 @@ brew`);
});
describe('Get CSS', ()=>{
it('should return brew style content as CSS text', async ()=>{
const testBrew = { title: 'test brew', text: '```css\n\nI Have a style!\n````\n\n' };
const testBrew = { title: 'test brew', text: '```css\n\nI Have a style!\n```\n\n' };
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
@@ -1034,7 +1034,7 @@ brew`);
expect(testBrew.theme).toEqual('5ePHB');
expect(testBrew.lang).toEqual('en');
// Style
expect(testBrew.style).toEqual('style\nstyle\nstyle');
expect(testBrew.style).toEqual('style\nstyle\nstyle\n');
// Text
expect(testBrew.text).toEqual('text\n');
});

View File

@@ -27,7 +27,9 @@ const HomebrewSchema = mongoose.Schema({
updatedAt : { type: Date, default: Date.now },
lastViewed : { type: Date, default: Date.now },
views : { type: Number, default: 0 },
version : { type: Number, default: 1 }
version : { type: Number, default: 1 },
lock : { type: Object }
}, { versionKey: false });
HomebrewSchema.statics.increaseView = async function(query) {
@@ -63,7 +65,7 @@ HomebrewSchema.statics.getByUser = async function(username, allowAccess=false, f
const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
export {
export {
HomebrewSchema as schema,
Homebrew as model
};

View File

@@ -1,6 +1,6 @@
import express from 'express';
import asyncHandler from 'express-async-handler';
import {model as HomebrewModel } from './homebrew.model.js';
import { model as HomebrewModel } from './homebrew.model.js';
const router = express.Router();
@@ -29,7 +29,7 @@ const rendererConditions = (legacy, v3)=>{
return {}; // If all renderers selected, renderer field not needed in query for speed
};
const sortConditions = (sort, dir) => {
const sortConditions = (sort, dir)=>{
return { [sort]: dir === 'asc' ? 1 : -1 };
};

View File

@@ -2,24 +2,103 @@ import _ from 'lodash';
import yaml from 'js-yaml';
import request from '../client/homebrew/utils/request-middleware.js';
// Convert the templates from a brew to a Snippets Structure.
const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=null, full=true)=>{
const textSplit = /^(\\snippet +.+\n)/gm;
const mpAsSnippets = [];
// Snippets from Themes first.
if(themeBundleSnippets) {
for (let themes of themeBundleSnippets) {
if(typeof themes !== 'string') {
const userSnippets = [];
const snipSplit = themes.snippets.trim().split(textSplit).slice(1);
for (let snips = 0; snips < snipSplit.length; snips+=2) {
if(!snipSplit[snips].startsWith('\\snippet ')) break;
const snippetName = snipSplit[snips].split(/\\snippet +/)[1].split('\n')[0].trim();
if(snippetName.length != 0) {
userSnippets.push({
name : snippetName,
icon : '',
gen : snipSplit[snips + 1],
});
}
}
if(userSnippets.length > 0) {
mpAsSnippets.push({
name : themes.name,
icon : '',
gen : '',
subsnippets : userSnippets
});
}
}
}
}
// Local Snippets
if(userBrewSnippets) {
const userSnippets = [];
const snipSplit = userBrewSnippets.trim().split(textSplit).slice(1);
for (let snips = 0; snips < snipSplit.length; snips+=2) {
if(!snipSplit[snips].startsWith('\\snippet ')) break;
const snippetName = snipSplit[snips].split(/\\snippet +/)[1].split('\n')[0].trim();
if(snippetName.length != 0) {
const subSnip = {
name : snippetName,
gen : snipSplit[snips + 1],
};
// if(full) subSnip.icon = '';
userSnippets.push(subSnip);
}
}
if(userSnippets.length) {
mpAsSnippets.push({
name : menuTitle,
// icon : '',
subsnippets : userSnippets
});
}
}
const returnObj = {
snippets : mpAsSnippets
};
if(full) {
returnObj.groupName = 'Brew Snippets';
returnObj.icon = 'fas fa-th-list';
returnObj.view = 'text';
}
return returnObj;
};
const yamlSnippetsToText = (yamlObj)=>{
if(typeof yamlObj == 'string') return yamlObj;
let snippetsText = '';
for (let snippet of yamlObj) {
for (let subSnippet of snippet.subsnippets) {
snippetsText = `${snippetsText}\\snippet ${subSnippet.name}\n${subSnippet.gen || ''}\n`;
}
}
return snippetsText;
};
const splitTextStyleAndMetadata = (brew)=>{
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 index = brew.text.indexOf('\n```\n\n');
const metadataSection = brew.text.slice(11, index + 1);
const metadata = yaml.load(metadataSection);
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
brew.text = brew.text.slice(index + 5);
brew.snippets = yamlSnippetsToText(_.pick(metadata, ['snippets']).snippets || '');
brew.text = brew.text.slice(index + 6);
}
if(brew.text.startsWith('```css')) {
const index = brew.text.indexOf('```\n\n');
brew.style = brew.text.slice(7, index - 1);
brew.text = brew.text.slice(index + 5);
}
if(brew.text.startsWith('```snippets')) {
const index = brew.text.indexOf('```\n\n');
brew.snippets = brew.text.slice(11, index - 1);
brew.text = brew.text.slice(index + 5);
const index = brew.text.indexOf('\n```\n\n');
brew.style = brew.text.slice(7, index + 1);
brew.text = brew.text.slice(index + 6);
}
// Handle old brews that still have empty strings in the tags metadata
@@ -64,4 +143,5 @@ export {
splitTextStyleAndMetadata,
printCurrentBrew,
fetchThemeBundle,
brewSnippetsToJSON
};

View File

@@ -7,7 +7,11 @@ import MarkedExtendedTables from 'marked-extended-tables';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
import MarkedAlignedParagraphs from 'marked-alignment-paragraphs';
import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces';
import MarkedSubSuperText from 'marked-subsuper-text';
import { romanize } from 'romans';
import writtenNumber from 'written-number';
//Icon fonts included so they can appear in emoji autosuggest dropdown
import diceFont from '../../themes/fonts/iconFonts/diceFont.js';
@@ -59,9 +63,57 @@ mathParser.functions.signed = function (a) {
if(a >= 0) return `+${a}`;
return `${a}`;
};
// Add Roman numeral functions
mathParser.functions.toRomans = function (a) {
return romanize(a);
};
mathParser.functions.toRomansUpper = function (a) {
return romanize(a).toUpperCase();
};
mathParser.functions.toRomansLower = function (a) {
return romanize(a).toLowerCase();
};
// Add character functions
mathParser.functions.toChar = function (a) {
if(a <= 0) return a;
const genChars = function (i) {
return (i > 26 ? genChars(Math.floor((i - 1) / 26)) : '') + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[(i - 1) % 26];
};
return genChars(a);
};
mathParser.functions.toCharUpper = function (a) {
return mathParser.functions.toChar(a).toUpperCase();
};
mathParser.functions.toCharLower = function (a) {
return mathParser.functions.toChar(a).toLowerCase();
};
// Add word functions
mathParser.functions.toWords = function (a) {
return writtenNumber(a);
};
mathParser.functions.toWordsUpper = function (a) {
return mathParser.functions.toWords(a).toUpperCase();
};
mathParser.functions.toWordsLower = function (a) {
return mathParser.functions.toWords(a).toLowerCase();
};
mathParser.functions.toWordsCaps = function (a) {
const words = mathParser.functions.toWords(a).split(' ');
return words.map((word)=>{
return word.replace(/(?:^|\b|\s)(\w)/g, function(w, index) {
return index === 0 ? w.toLowerCase() : w.toUpperCase();
});
}).join(' ');
};
// Normalize variable names; trim edge spaces and shorten blocks of whitespace to 1 space
const normalizeVarNames = (label)=>{
return label.trim().replace(/\s+/g, ' ');
};
//Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (html) {
renderer.html = function (token) {
let html = token.text;
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
const openTag = html.substring(0, html.indexOf('>')+1);
html = html.substring(html.indexOf('>')+1);
@@ -72,18 +124,21 @@ renderer.html = function (html) {
};
// Don't wrap {{ Spans alone on a line, or {{ Divs in <p> tags
renderer.paragraph = function(text){
renderer.paragraph = function(token){
let match;
const text = this.parser.parseInline(token.tokens);
if(text.startsWith('<div') || text.startsWith('</div'))
return `${text}`;
else if(match = text.match(/(^|^.*?\n)<span class="inline-block(.*?<\/span>)$/)) {
else if(match = text.match(/(^|^.*?\n)<span class="inline-block(.*?<\/span>)$/))
return `${match[1].trim() ? `<p>${match[1]}</p>` : ''}<span class="inline-block${match[2]}`;
} else
else
return `<p>${text}</p>\n`;
};
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) {
renderer.link = function (token) {
let { href, title, tokens } = token;
const text = this.parser.parseInline(tokens);
let self = false;
if(href[0] == '#') {
self = true;
@@ -95,7 +150,7 @@ renderer.link = function (href, title, text) {
}
let out = `<a href="${escape(href)}"`;
if(title) {
out += ` title="${title}"`;
out += ` title="${escape(title)}"`;
}
if(self) {
out += ' target="_self"';
@@ -105,8 +160,8 @@ renderer.link = function (href, title, text) {
};
// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS
renderer.image = function (href, title, text) {
href = cleanUrl(href);
renderer.image = function (token) {
const { href, title, text } = token;
if(href === null)
return text;
@@ -334,42 +389,6 @@ const mustacheInjectBlock = {
}
};
const justifiedParagraphClasses = [];
justifiedParagraphClasses[2] = 'Left';
justifiedParagraphClasses[4] = 'Right';
justifiedParagraphClasses[6] = 'Center';
const justifiedParagraphs = {
name : 'justifiedParagraphs',
level : 'block',
start(src) {
return src.match(/\n(?:-:|:-|-:) {1}/m)?.index;
}, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /^(((:-))|((-:))|((:-:))) .+(\n(([^\n].*\n)*(\n|$))|$)/ygm;
const match = regex.exec(src);
if(match?.length) {
let whichJustify;
if(match[2]?.length) whichJustify = 2;
if(match[4]?.length) whichJustify = 4;
if(match[6]?.length) whichJustify = 6;
return {
type : 'justifiedParagraphs', // Should match "name" above
raw : match[0], // Text to consume from the source
length : match[whichJustify].length,
text : match[0].slice(match[whichJustify].length),
class : justifiedParagraphClasses[whichJustify],
tokens : this.lexer.inlineTokens(match[0].slice(match[whichJustify].length + 1))
};
}
},
renderer(token) {
return `<p align="${token.class}">${this.parser.parseInline(token.tokens)}</p>`;
}
};
const forcedParagraphBreaks = {
name : 'hardBreaks',
level : 'block',
@@ -387,28 +406,7 @@ const forcedParagraphBreaks = {
}
},
renderer(token) {
return `<br>\n`.repeat(token.length);
}
};
const nonbreakingSpaces = {
name : 'nonbreakingSpaces',
level : 'inline',
start(src) { return src.match(/:>+/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /:(>+)/ym;
const match = regex.exec(src);
if(match?.length) {
return {
type : 'nonbreakingSpaces', // Should match "name" above
raw : match[0], // Text to consume from the source
length : match[1].length,
text : ''
};
}
},
renderer(token) {
return `&nbsp;`.repeat(token.length).concat('');
return `<div class='blank'></div>\n`.repeat(token.length);
}
};
@@ -505,7 +503,7 @@ const replaceVar = function(input, hoist=false, allowUnresolved=false) {
const match = regex.exec(input);
const prefix = match[1];
const label = match[2];
const label = normalizeVarNames(match[2]); // Ensure the label name is normalized as it should be in the var stack.
//v=====--------------------< HANDLE MATH >-------------------=====v//
const mathRegex = /[a-z]+\(|[+\-*/^(),]/g;
@@ -660,8 +658,8 @@ function MarkedVariables() {
});
}
if(match[3]) { // Block Definition
const label = match[4] ? match[4].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const label = match[4] ? normalizeVarNames(match[4]) : null;
const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Normalize text content (except newlines for block-level content)
varsQueue.push(
{ type : 'varDefBlock',
@@ -670,7 +668,7 @@ function MarkedVariables() {
});
}
if(match[6]) { // Block Call
const label = match[7] ? match[7].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const label = match[7] ? normalizeVarNames(match[7]) : null;
varsQueue.push(
{ type : 'varCallBlock',
@@ -679,7 +677,7 @@ function MarkedVariables() {
});
}
if(match[8]) { // Inline Definition
const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const label = match[10] ? normalizeVarNames(match[10]) : null;
let content = match[11] || null;
// In case of nested (), find the correct matching end )
@@ -711,7 +709,7 @@ function MarkedVariables() {
});
}
if(match[12]) { // Inline Call
const label = match[13] ? match[13].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const label = match[13] ? normalizeVarNames(match[13]) : null;
varsQueue.push(
{ type : 'varCallInline',
@@ -767,12 +765,14 @@ const tableTerminators = [
];
Marked.use(MarkedVariables());
Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
nonbreakingSpaces, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
Marked.use(MarkedAlignedParagraphs());
Marked.use(MarkedSubSuperText());
Marked.use(MarkedNonbreakingSpaces());
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }),
Marked.use(MarkedExtendedTables({ interruptPatterns: tableTerminators }), MarkedGFMHeadingId({ globalSlugs: true }),
MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
function cleanUrl(href) {
@@ -837,12 +837,12 @@ const processStyleTags = (string)=>{
obj[key.trim()] = value.trim();
return obj;
}, {}) || null;
const styles = tags?.length ? tags.reduce((styleObj, style) => {
const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim();
return styleObj;
}, {}) : null;
const styles = tags?.length ? tags.reduce((styleObj, style)=>{
const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim();
return styleObj;
}, {}) : null;
return {
id : id,
@@ -858,8 +858,8 @@ const extractHTMLStyleTags = (htmlString)=>{
const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null;
const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null;
const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1]
?.split(';').reduce((styleObj, style) => {
if (style.trim() === '') return styleObj;
?.split(';').reduce((styleObj, style)=>{
if(style.trim() === '') return styleObj;
const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.trim();
@@ -869,7 +869,7 @@ const extractHTMLStyleTags = (htmlString)=>{
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{
const index = attr.indexOf('=');
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
const [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
obj[key.trim()] = value.replace(/"/g, '');
return obj;
}, {}) || null;
@@ -882,7 +882,7 @@ const extractHTMLStyleTags = (htmlString)=>{
};
};
const mergeHTMLTags = (originalTags, newTags) => {
const mergeHTMLTags = (originalTags, newTags)=>{
return {
id : newTags.id || originalTags.id || null,
classes : [originalTags.classes, newTags.classes].join(' ').trim() || null,
@@ -898,7 +898,13 @@ let globalPageNumber = 0;
const Markdown = {
marked : Marked,
render : (rawBrewText, pageNumber=0)=>{
globalVarsList[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order
const lastPageNumber = pageNumber > 0 ? globalVarsList[pageNumber - 1].HB_pageNumber.content : 0;
globalVarsList[pageNumber] = { //Reset global links for current page, to ensure values are parsed in order
'HB_pageNumber' : { //Add document variables for this page
content : !isNaN(Number(lastPageNumber)) ? Number(lastPageNumber) + 1 : lastPageNumber,
resolved : true
}
};
varsQueue = []; //Could move into MarkedVariables()
globalPageNumber = pageNumber;
if(pageNumber==0) {

View File

@@ -12,8 +12,8 @@ const Nav = {
displayName : 'Nav.base',
render : function(){
return <nav>
{this.props.children}
</nav>;
{this.props.children}
</nav>;
}
}),
logo : function(){

View File

@@ -29,8 +29,8 @@ const SplitPane = (props)=>{
const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x)));
//when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position
const handleResize = () =>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)));
const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)));
const handleUp =(e)=>{
e.preventDefault();
if(isDragging) {

View File

@@ -21,8 +21,8 @@
background-color : #BBBBBB;
.dots {
display : table-cell;
text-align : center;
vertical-align : middle;
text-align : center;
i {
display : block !important;
margin : 10px 0px;

View File

@@ -3,127 +3,127 @@
@defaultEasing : ease;
//Animates all properties on an element
.animateAll(@duration : @defaultDuration, @easing : @defaultEasing){
-webkit-transition: all @duration @easing;
-moz-transition: all @duration @easing;
-o-transition: all @duration @easing;
transition: all @duration @easing;
.animateAll(@duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transition : all @duration @easing;
-moz-transition : all @duration @easing;
-o-transition : all @duration @easing;
transition : all @duration @easing;
}
//Animates Specific property
.animate(@prop, @duration : @defaultDuration, @easing : @defaultEasing){
-webkit-transition: @prop @duration @easing;
-moz-transition: @prop @duration @easing;
-o-transition: @prop @duration @easing;
transition: @prop @duration @easing;
.animate(@prop, @duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transition : @prop @duration @easing;
-moz-transition : @prop @duration @easing;
-o-transition : @prop @duration @easing;
transition : @prop @duration @easing;
}
.animateMany(...){
.animateMany(...) {
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
-webkit-transition-property: @value;
-moz-transition-property: @value;
-o-transition-property: @value;
transition-property: @value;
-webkit-transition-property : @value;
-moz-transition-property : @value;
-o-transition-property : @value;
transition-property : @value;
.animateDuration();
.animateEasing();
}
.animateDuration(@duration : @defaultDuration){
-webkit-transition-duration: @duration;
-moz-transition-duration: @duration;
-o-transition-duration: @duration;
transition-duration: @duration;
.animateDuration(@duration : @defaultDuration) {
-webkit-transition-duration : @duration;
-moz-transition-duration : @duration;
-o-transition-duration : @duration;
transition-duration : @duration;
}
.animateEasing(@easing : @defaultEasing){
-webkit-transition-timing-function: @easing;
-moz-transition-timing-function: @easing;
-o-transition-timing-function: @easing;
transition-timing-function: @easing;
.animateEasing(@easing : @defaultEasing) {
-webkit-transition-timing-function : @easing;
-moz-transition-timing-function : @easing;
-o-transition-timing-function : @easing;
transition-timing-function : @easing;
}
.transition (@prop, @duration: @defaultDuration) {
-webkit-transition: @prop @duration, -webkit-transform @duration;
-moz-transition: @prop @duration, -moz-transform @duration;
-o-transition: @prop @duration, -o-transform @duration;
-ms-transition: @prop @duration, -ms-transform @duration;
transition: @prop @duration, transform @duration;
-webkit-transition : @prop @duration, -webkit-transform @duration;
-moz-transition : @prop @duration, -moz-transform @duration;
-o-transition : @prop @duration, -o-transform @duration;
-ms-transition : @prop @duration, -ms-transform @duration;
transition : @prop @duration, transform @duration;
}
.transform (@transform) {
-webkit-transform: @transform;
-moz-transform: @transform;
-o-transform: @transform;
-ms-transform: @transform;
transform: @transform;
-webkit-transform : @transform;
-moz-transform : @transform;
-o-transform : @transform;
-ms-transform : @transform;
transform : @transform;
}
.delay(@delay){
animation-delay:@delay;
-webkit-animation-delay:@delay;
transition-delay:@delay;
-webkit-transition-delay:@delay;
.delay(@delay) {
-webkit-transition-delay : @delay;
transition-delay : @delay;
-webkit-animation-delay : @delay;
animation-delay : @delay;
}
.keep(){
-webkit-animation-fill-mode:forwards;
-moz-animation-fill-mode:forwards;
-ms-animation-fill-mode:forwards;
-o-animation-fill-mode:forwards;
animation-fill-mode:forwards;
.keep() {
-webkit-animation-fill-mode : forwards;
-moz-animation-fill-mode : forwards;
-ms-animation-fill-mode : forwards;
-o-animation-fill-mode : forwards;
animation-fill-mode : forwards;
}
.sequentialDelay(@delayInc : 0.2s, @initialDelay : 0s){
&:nth-child(1){.delay(0*@delayInc + @initialDelay)}
&:nth-child(2){.delay(1*@delayInc + @initialDelay)}
&:nth-child(3){.delay(2*@delayInc + @initialDelay)}
&:nth-child(4){.delay(3*@delayInc + @initialDelay)}
&:nth-child(5){.delay(4*@delayInc + @initialDelay)}
&:nth-child(6){.delay(5*@delayInc + @initialDelay)}
&:nth-child(7){.delay(6*@delayInc + @initialDelay)}
&:nth-child(8){.delay(7*@delayInc + @initialDelay)}
&:nth-child(9){.delay(8*@delayInc + @initialDelay)}
&:nth-child(10){.delay(9*@delayInc + @initialDelay)}
&:nth-child(11){.delay(10*@delayInc + @initialDelay)}
&:nth-child(12){.delay(11*@delayInc + @initialDelay)}
&:nth-child(13){.delay(12*@delayInc + @initialDelay)}
&:nth-child(14){.delay(13*@delayInc + @initialDelay)}
&:nth-child(15){.delay(14*@delayInc + @initialDelay)}
&:nth-child(16){.delay(15*@delayInc + @initialDelay)}
&:nth-child(17){.delay(16*@delayInc + @initialDelay)}
&:nth-child(18){.delay(17*@delayInc + @initialDelay)}
&:nth-child(19){.delay(18*@delayInc + @initialDelay)}
&:nth-child(20){.delay(19*@delayInc + @initialDelay)}
.sequentialDelay(@delayInc : 0.2s, @initialDelay : 0s) {
&:nth-child(1) {.delay(0*@delayInc + @initialDelay); }
&:nth-child(2) {.delay(1*@delayInc + @initialDelay); }
&:nth-child(3) {.delay(2*@delayInc + @initialDelay); }
&:nth-child(4) {.delay(3*@delayInc + @initialDelay); }
&:nth-child(5) {.delay(4*@delayInc + @initialDelay); }
&:nth-child(6) {.delay(5*@delayInc + @initialDelay); }
&:nth-child(7) {.delay(6*@delayInc + @initialDelay); }
&:nth-child(8) {.delay(7*@delayInc + @initialDelay); }
&:nth-child(9) {.delay(8*@delayInc + @initialDelay); }
&:nth-child(10) {.delay(9*@delayInc + @initialDelay); }
&:nth-child(11) {.delay(10*@delayInc + @initialDelay); }
&:nth-child(12) {.delay(11*@delayInc + @initialDelay); }
&:nth-child(13) {.delay(12*@delayInc + @initialDelay); }
&:nth-child(14) {.delay(13*@delayInc + @initialDelay); }
&:nth-child(15) {.delay(14*@delayInc + @initialDelay); }
&:nth-child(16) {.delay(15*@delayInc + @initialDelay); }
&:nth-child(17) {.delay(16*@delayInc + @initialDelay); }
&:nth-child(18) {.delay(17*@delayInc + @initialDelay); }
&:nth-child(19) {.delay(18*@delayInc + @initialDelay); }
&:nth-child(20) {.delay(19*@delayInc + @initialDelay); }
}
.createFrames(@name, @from, @to){
.createFrames(@name, @from, @to) {
@frames: {
from { @from(); }
to { @to(); }
};
@-webkit-keyframes @name {@frames();}
@-moz-keyframes @name {@frames();}
@-ms-keyframes @name {@frames();}
@-o-keyframes @name {@frames();}
@keyframes @name {@frames();}
@-webkit-keyframes @name {@frames();}
@-moz-keyframes @name {@frames();}
@-ms-keyframes @name {@frames();}
@-o-keyframes @name {@frames();}
@keyframes @name {@frames();}
}
.createAnimation(@name, @duration : @defaultDuration, @easing : @defaultEasing){
-webkit-animation-name: @name;
-moz-animation-name: @name;
-ms-animation-name: @name;
animation-name: @name;
-webkit-animation-duration: @duration;
-moz-animation-duration: @duration;
-ms-animation-duration: @duration;
animation-duration: @duration;
-webkit-animation-timing-function: @easing;
-moz-animation-timing-function: @easing;
-ms-animation-timing-function: @easing;
animation-timing-function: @easing;
.createAnimation(@name, @duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-animation-name : @name;
-moz-animation-name : @name;
-ms-animation-name : @name;
animation-name : @name;
-webkit-animation-duration : @duration;
-moz-animation-duration : @duration;
-ms-animation-duration : @duration;
animation-duration : @duration;
-webkit-animation-timing-function : @easing;
-moz-animation-timing-function : @easing;
-ms-animation-timing-function : @easing;
animation-timing-function : @easing;
}
@@ -132,82 +132,82 @@
Standard Animations
****************************/
.fadeIn(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeIn(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeIn; @duration; @easing);
.createFrames(fadeIn,
{ opacity : 0; },
{ opacity : 0; },
{ opacity : 1; }
);
}
.fadeInDown(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeInDown(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInDown; @duration; @easing);
.createFrames(fadeInDown,
{ opacity : 0; .transform(translateY(20px));},
{ opacity : 0; .transform(translateY(20px));},
{ opacity : 1; .transform(translateY(0px));}
);
}
.fadeInTop(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeInTop(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInTop; @duration; @easing);
.createFrames(fadeInTop,
{ opacity : 0; .transform(translateY(-20px)); },
{ opacity : 0; .transform(translateY(-20px)); },
{ opacity : 1; .transform(translateY(0px));}
);
}
.fadeInLeft(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeInLeft(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInLeft; @duration; @easing);
.createFrames(fadeInLeft,
{ opacity: 0; .transform(translateX(-20px));},
{ opacity: 0; .transform(translateX(-20px));},
{ opacity: 1; .transform(translateX(0));}
);
}
.fadeInRight(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeInRight(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeInRight; @duration; @easing);
.createFrames(fadeInRight,
{ opacity: 0; .transform(translateX(20px));},
{ opacity: 0; .transform(translateX(20px));},
{ opacity: 1; .transform(translateX(0));}
);
}
.fadeOut(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeOut(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOut; @duration; @easing);
.createFrames(fadeOut,
{ opacity : 1; },
{ opacity : 1; },
{ opacity : 0; }
);
}
.fadeOutDown(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeOutDown(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutDown; @duration; @easing);
.createFrames(fadeOutDown,
{ opacity : 1; .transform(translateY(0)); visibility: visible;},
{ opacity : 1; .transform(translateY(0)); visibility: visible;},
{ opacity : 0; .transform(translateY(20px)); visibility: hidden;}
);
}
.fadeOutTop(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeOutTop(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutTop; @duration; @easing);
.createFrames(fadeOutTop,
{ opacity : 1; .transform(translateY(0)); },
{ opacity : 1; .transform(translateY(0)); },
{ opacity : 0; .transform(translateY(-20px)); }
);
}
.fadeOutLeft(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeOutLeft(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutLeft; @duration; @easing);
.createFrames(fadeOutLeft,
{ opacity : 1; .transform(translateX(0));},
{ opacity : 1; .transform(translateX(0));},
{ opacity : 0; .transform(translateX(-20px));}
);
}
.fadeOutRight(@duration : @defaultDuration, @easing : @defaultEasing){
.fadeOutRight(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(fadeOutRight; @duration; @easing);
.createFrames(fadeOutRight,
{ opacity : 1; .transform(translateX(0));},
{ opacity : 1; .transform(translateX(0));},
{ opacity : 0; .transform(translateX(20px));}
);
}
@@ -219,50 +219,50 @@
Fun Animations
****************************/
.spin(@duration : @defaultDuration, @easing : @defaultEasing){
.spin(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(spin, @duration, @easing);
.spinKeyFrames(){
.spinKeyFrames() {
from { .transform(rotate(0deg)); }
to { .transform(rotate(360deg)); }
to { .transform(rotate(360deg)); }
}
@-webkit-keyframes spin {.spinKeyFrames();}
@-moz-keyframes spin {.spinKeyFrames();}
@-ms-keyframes spin {.spinKeyFrames();}
@-o-keyframes spin {.spinKeyFrames();}
@keyframes spin {.spinKeyFrames();}
@-moz-keyframes spin {.spinKeyFrames();}
@-ms-keyframes spin {.spinKeyFrames();}
@-o-keyframes spin {.spinKeyFrames();}
@keyframes spin {.spinKeyFrames();}
}
.bounce(@duration : @defaultDuration, @easing : @defaultEasing){
.bounce(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(bounce, @duration, @easing);
.bounceKeyFrames(){
.bounceKeyFrames() {
0%, 20%, 50%, 80%, 100% { .transform(translateY(0));}
40% { .transform(translateY(-30px));}
60% { .transform(translateY(-15px));}
}
@-webkit-keyframes bounce {.bounceKeyFrames();}
@-moz-keyframes bounce {.bounceKeyFrames();}
@-ms-keyframes bounce {.bounceKeyFrames();}
@-o-keyframes bounce {.bounceKeyFrames();}
@keyframes bounce {.bounceKeyFrames();}
@-moz-keyframes bounce {.bounceKeyFrames();}
@-ms-keyframes bounce {.bounceKeyFrames();}
@-o-keyframes bounce {.bounceKeyFrames();}
@keyframes bounce {.bounceKeyFrames();}
}
.pulse(@duration : @defaultDuration, @easing : @defaultEasing){
.pulse(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(pulse, @duration, @easing);
.pulseKeyFrames(){
0% { .transform(scale(1));}
50% { .transform(scale(1.4));}
.pulseKeyFrames() {
0% { .transform(scale(1));}
50% { .transform(scale(1.4));}
100% { .transform(scale(1));}
}
@-webkit-keyframes pulse {.pulseKeyFrames();}
@-moz-keyframes pulse {.pulseKeyFrames();}
@-ms-keyframes pulse {.pulseKeyFrames();}
@-o-keyframes pulse {.pulseKeyFrames();}
@keyframes pulse {.pulseKeyFrames();}
@-moz-keyframes pulse {.pulseKeyFrames();}
@-ms-keyframes pulse {.pulseKeyFrames();}
@-o-keyframes pulse {.pulseKeyFrames();}
@keyframes pulse {.pulseKeyFrames();}
}
.rubberBand(@duration : @defaultDuration, @easing : @defaultEasing){
.rubberBand(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(rubberBand, @duration, @easing);
.rubberBandKeyFrames(){
.rubberBandKeyFrames() {
0% {.transform(scale(1));}
30% {.transform(scaleX(1.25) scaleY(0.75));}
40% {.transform(scaleX(0.75) scaleY(1.25));}
@@ -270,32 +270,32 @@
100% {.transform(scale(1));}
}
@-webkit-keyframes rubberBand {.rubberBandKeyFrames();}
@-moz-keyframes rubberBand {.rubberBandKeyFrames();}
@-ms-keyframes rubberBand {.rubberBandKeyFrames();}
@-o-keyframes rubberBand {.rubberBandKeyFrames();}
@keyframes rubberBand {.rubberBandKeyFrames();}
@-moz-keyframes rubberBand {.rubberBandKeyFrames();}
@-ms-keyframes rubberBand {.rubberBandKeyFrames();}
@-o-keyframes rubberBand {.rubberBandKeyFrames();}
@keyframes rubberBand {.rubberBandKeyFrames();}
}
.shake(@duration : @defaultDuration, @easing : @defaultEasing){
.shake(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(shake, @duration, @easing);
.shakeKeyFrames(){
.shakeKeyFrames() {
0%, 100% {.transform( translateX(0));}
10%, 30%, 50%, 70%, 90% {.transform( translateX(-10px));}
20%, 40%, 60%, 80% {.transform( translateX(10px));}
}
@-webkit-keyframes shake {.shakeKeyFrames();}
@-moz-keyframes shake {.shakeKeyFrames();}
@-ms-keyframes shake {.shakeKeyFrames();}
@-o-keyframes shake {.shakeKeyFrames();}
@keyframes shake {.shakeKeyFrames();}
@-moz-keyframes shake {.shakeKeyFrames();}
@-ms-keyframes shake {.shakeKeyFrames();}
@-o-keyframes shake {.shakeKeyFrames();}
@keyframes shake {.shakeKeyFrames();}
}
.swing(@duration : @defaultDuration, @easing : @defaultEasing){
-webkit-transform-origin: top center;
-ms-transform-origin: top center;
transform-origin: top center;
.swing(@duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transform-origin : top center;
-ms-transform-origin : top center;
transform-origin : top center;
.createAnimation(swing, @duration, @easing);
.swingKeyFrames(){
.swingKeyFrames() {
20% {.transform(rotate(15deg));}
40% {.transform(rotate(-10deg));}
60% {.transform(rotate(5deg));}
@@ -303,18 +303,18 @@
100% {.transform(rotate(0deg));}
}
@-webkit-keyframes swing {.swingKeyFrames();}
@-moz-keyframes swing {.swingKeyFrames();}
@-ms-keyframes swing {.swingKeyFrames();}
@-o-keyframes swing {.swingKeyFrames();}
@keyframes swing {.swingKeyFrames();}
@-moz-keyframes swing {.swingKeyFrames();}
@-ms-keyframes swing {.swingKeyFrames();}
@-o-keyframes swing {.swingKeyFrames();}
@keyframes swing {.swingKeyFrames();}
}
.twist(@duration : @defaultDuration, @easing : @defaultEasing){
-webkit-transform-origin: center center;
-ms-transform-origin: center center;
transform-origin: center center;
.twist(@duration : @defaultDuration, @easing : @defaultEasing) {
-webkit-transform-origin : center center;
-ms-transform-origin : center center;
transform-origin : center center;
.createAnimation(swing, @duration, @easing);
.swingKeyFrames(){
.swingKeyFrames() {
20% {.transform(rotate(15deg));}
40% {.transform(rotate(-10deg));}
60% {.transform(rotate(5deg));}
@@ -322,15 +322,15 @@
100% {.transform(rotate(0deg));}
}
@-webkit-keyframes swing {.swingKeyFrames();}
@-moz-keyframes swing {.swingKeyFrames();}
@-ms-keyframes swing {.swingKeyFrames();}
@-o-keyframes swing {.swingKeyFrames();}
@keyframes swing {.swingKeyFrames();}
@-moz-keyframes swing {.swingKeyFrames();}
@-ms-keyframes swing {.swingKeyFrames();}
@-o-keyframes swing {.swingKeyFrames();}
@keyframes swing {.swingKeyFrames();}
}
.wobble(@duration : @defaultDuration, @easing : @defaultEasing){
.wobble(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(wobble, @duration, @easing);
.wobbleKeyFrames(){
.wobbleKeyFrames() {
0% {.transform(translateX(0%));}
15% {.transform(translateX(-25%) rotate(-5deg));}
30% {.transform(translateX(20%) rotate(3deg));}
@@ -340,22 +340,22 @@
100% {.transform(translateX(0%));}
}
@-webkit-keyframes wobble {.wobbleKeyFrames();}
@-moz-keyframes wobble {.wobbleKeyFrames();}
@-ms-keyframes wobble {.wobbleKeyFrames();}
@-o-keyframes wobble {.wobbleKeyFrames();}
@keyframes wobble {.wobbleKeyFrames();}
@-moz-keyframes wobble {.wobbleKeyFrames();}
@-ms-keyframes wobble {.wobbleKeyFrames();}
@-o-keyframes wobble {.wobbleKeyFrames();}
@keyframes wobble {.wobbleKeyFrames();}
}
.popIn(@duration : @defaultDuration, @easing : @defaultEasing){
.popIn(@duration : @defaultDuration, @easing : @defaultEasing) {
.createAnimation(popIn, @duration, @easing);
.popInKeyFrames(){
0% { .transform(scale(0));}
70% { .transform(scale(1.4));}
.popInKeyFrames() {
0% { .transform(scale(0));}
70% { .transform(scale(1.4));}
100% { .transform(scale(1));}
}
@-webkit-keyframes popIn {.popInKeyFrames();}
@-moz-keyframes popIn {.popInKeyFrames();}
@-ms-keyframes popIn {.popInKeyFrames();}
@-o-keyframes popIn {.popInKeyFrames();}
@keyframes popIn {.popInKeyFrames();}
@-moz-keyframes popIn {.popInKeyFrames();}
@-ms-keyframes popIn {.popInKeyFrames();}
@-o-keyframes popIn {.popInKeyFrames();}
@keyframes popIn {.popInKeyFrames();}
}

View File

@@ -23,47 +23,47 @@
@grey : #7F8C8D;
#backgroundColors {
&.tealLight{ background-color : @tealLight };
&.teal{ background-color : @teal };
&.greenLight{ background-color : @greenLight };
&.green{ background-color : @green };
&.blueLight{ background-color : @blueLight };
&.blue{ background-color : @blue };
&.purpleLight{ background-color : @purpleLight };
&.purple{ background-color : @purple };
&.steelLight{ background-color : @steelLight };
&.steel{ background-color : @steel };
&.yellowLight{ background-color : @yellowLight };
&.yellow{ background-color : @yellow };
&.orangeLight{ background-color : @orangeLight };
&.orange{ background-color : @orange };
&.redLight{ background-color : @redLight };
&.red{ background-color : @red };
&.silverLight{ background-color : @silverLight };
&.silver{ background-color : @silver };
&.greyLight{ background-color : @greyLight };
&.grey{ background-color : @grey };
&.tealLight { background-color : @tealLight; };
&.teal { background-color : @teal; };
&.greenLight { background-color : @greenLight; };
&.green { background-color : @green; };
&.blueLight { background-color : @blueLight; };
&.blue { background-color : @blue; };
&.purpleLight { background-color : @purpleLight; };
&.purple { background-color : @purple; };
&.steelLight { background-color : @steelLight; };
&.steel { background-color : @steel; };
&.yellowLight { background-color : @yellowLight; };
&.yellow { background-color : @yellow; };
&.orangeLight { background-color : @orangeLight; };
&.orange { background-color : @orange; };
&.redLight { background-color : @redLight; };
&.red { background-color : @red; };
&.silverLight { background-color : @silverLight; };
&.silver { background-color : @silver; };
&.greyLight { background-color : @greyLight; };
&.grey { background-color : @grey; };
}
#backgroundColorsHover {
&.tealLight:hover{ background-color : @tealLight };
&.teal:hover{ background-color : @teal };
&.greenLight:hover{ background-color : @greenLight };
&.green:hover{ background-color : @green };
&.blueLight:hover{ background-color : @blueLight };
&.blue:hover{ background-color : @blue };
&.purpleLight:hover{ background-color : @purpleLight };
&.purple:hover{ background-color : @purple };
&.steelLight:hover{ background-color : @steelLight };
&.steel:hover{ background-color : @steel };
&.yellowLight:hover{ background-color : @yellowLight };
&.yellow:hover{ background-color : @yellow };
&.orangeLight:hover{ background-color : @orangeLight };
&.orange:hover{ background-color : @orange };
&.redLight:hover{ background-color : @redLight };
&.red:hover{ background-color : @red };
&.silverLight:hover{ background-color : @silverLight };
&.silver:hover{ background-color : @silver };
&.greyLight:hover{ background-color : @greyLight };
&.grey:hover{ background-color : @grey };
&.tealLight:hover { background-color : @tealLight; };
&.teal:hover { background-color : @teal; };
&.greenLight:hover { background-color : @greenLight; };
&.green:hover { background-color : @green; };
&.blueLight:hover { background-color : @blueLight; };
&.blue:hover { background-color : @blue; };
&.purpleLight:hover { background-color : @purpleLight; };
&.purple:hover { background-color : @purple; };
&.steelLight:hover { background-color : @steelLight; };
&.steel:hover { background-color : @steel; };
&.yellowLight:hover { background-color : @yellowLight; };
&.yellow:hover { background-color : @yellow; };
&.orangeLight:hover { background-color : @orangeLight; };
&.orange:hover { background-color : @orange; };
&.redLight:hover { background-color : @redLight; };
&.red:hover { background-color : @red; };
&.silverLight:hover { background-color : @silverLight; };
&.silver:hover { background-color : @silver; };
&.greyLight:hover { background-color : @greyLight; };
&.grey:hover { background-color : @grey; };
}

View File

@@ -12,37 +12,31 @@
font-family : 'CodeBold';
src : data-uri('naturalcrit/styles/CODE Bold.otf') format('opentype');
}
html,body, #reactRoot{
html,body, #reactRoot {
height : 100vh;
min-height : 100vh;
margin : 0;
font-family : 'Open Sans', sans-serif;
}
*{
box-sizing : border-box;
}
.colorButton(@backgroundColor : @green){
* { box-sizing : border-box; }
.colorButton(@backgroundColor : @green) {
.animate(background-color);
display : inline-block;
padding : 0.6em 1.2em;
cursor : pointer;
background-color : @backgroundColor;
font-family : 'Open Sans', sans-serif;
font-size : 0.8em;
font-weight : 800;
color : white;
text-decoration : none;
text-transform : uppercase;
border : none;
text-decoration : none;
cursor : pointer;
outline : none;
&:hover{
background-color : darken(@backgroundColor, 5%);
}
&:active{
background-color : darken(@backgroundColor, 10%);
}
&:disabled{
background-color : @backgroundColor;
border : none;
&:hover { background-color : darken(@backgroundColor, 5%); }
&:active { background-color : darken(@backgroundColor, 10%); }
&:disabled {
cursor : not-allowed;
background-color : @silver !important;
cursor:not-allowed;
}
}

View File

@@ -1,86 +1,76 @@
@containerWidth : 1000px;
html, body{
html, body {
position : relative;
height : 100%;
min-height : 100%;
background-color : #eee;
font-family : 'Lato', sans-serif;
color : @copyGrey;
background-color : #EEEEEE;
}
.container{
.container {
position : relative;
max-width : @containerWidth;
margin : 0 auto;
padding-right : 20px;
padding-left : 20px;
margin : 0 auto;
}
h1{
h1 {
margin-top : 10px;
margin-bottom : 15px;
font-size : 2em;
}
h2{
h2 {
margin-top : 10px;
margin-bottom : 15px;
font-size : 1.5em;
font-weight : 900;
}
h3{
h3 {
margin-top : 5px;
margin-bottom : 7px;
font-size : 1em;
font-weight : 900;
}
p{
p {
margin-bottom : 1em;
font-size : 16px;
color : @copyGrey;
line-height : 1.5em;
color : @copyGrey;
}
code{
background-color : #F8F8F8;
font-family : 'Courier', mono;
code {
font-family : 'Courier', "mono";
color : black;
white-space : pre;
background-color : #F8F8F8;
}
a{
color : inherit;
}
strong{
font-weight : bold;
}
button{
a { color : inherit; }
strong { font-weight : bold; }
button {
.button();
}
.button(@backgroundColor : @green){
.button(@backgroundColor : @green) {
.animate(background-color);
display : inline-block;
padding : 0.6em 1.2em;
cursor : pointer;
background-color : @backgroundColor;
font-family : "Lato", Helvetica, Arial, sans-serif;
font-family : 'Lato', "Helvetica", "Arial", sans-serif;
font-size : 15px;
color : white;
text-decoration : none;
border : none;
outline : none;
&:hover{
background-color : darken(@backgroundColor, 5%);
}
&:active{
background-color : darken(@backgroundColor, 10%);
}
&:disabled{
background-color : @silver !important;
}
}
.iconButton(@backgroundColor : @green){
padding : 0.6em;
cursor : pointer;
outline : none;
background-color : @backgroundColor;
border : none;
&:hover { background-color : darken(@backgroundColor, 5%); }
&:active { background-color : darken(@backgroundColor, 10%); }
&:disabled { background-color : @silver !important; }
}
.iconButton(@backgroundColor : @green) {
padding : 0.6em;
font-size : 14px;
color : white;
text-align : center;
cursor : pointer;
background-color : @backgroundColor;
}

View File

@@ -1,33 +1,23 @@
:where(html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,button,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video){
border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0
:where(html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,button,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video) {padding : 0;margin : 0;font : inherit;font-size : 100%;vertical-align : baseline;
border : 0;
}
:where(article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section){
display:block
}
:where(article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section) { display : block; }
:where(body){
line-height:1
}
:where(body) { line-height : 1; }
:where(ol,ul){
list-style:none
}
:where(ol,ul) { list-style : none; }
:where(blockquote,q){
quotes:none
}
:where(blockquote,q) { quotes : none; }
:where(blockquote:before,blockquote:after,q:before,q:after){
content:none
}
:where(blockquote::before,blockquote::after,q::before,q::after) { content : none; }
:where(table){
border-collapse:collapse;border-spacing:0
:where(table) {border-spacing : 0;
border-collapse : collapse;
}
:where(button) {
background-color: unset;
text-transform: unset;
color: unset;
color : unset;
text-transform : unset;
background-color : unset;
}

View File

@@ -2,116 +2,115 @@
@tooltipColor : #383838;
@arrowSize : 6px;
@arrowPosition : 18px;
[data-tooltip]{
[data-tooltip] {
.tooltip(attr(data-tooltip));
}
[data-tooltip-top]{
[data-tooltip-top] {
.tooltipTop(attr(data-tooltip-top));
}
[data-tooltip-bottom]{
[data-tooltip-bottom] {
.tooltipBottom(attr(data-tooltip-bottom));
}
[data-tooltip-left]{
[data-tooltip-left] {
.tooltipLeft(attr(data-tooltip-left));
}
[data-tooltip-right]{
[data-tooltip-right] {
.tooltipRight(attr(data-tooltip-right));
}
.tooltip(@content){
.tooltip(@content) {
.tooltipBottom(@content);
}
.tooltipTop(@content){
.tooltipTop(@content) {
.tooltipBase(@content);
&:before {
&::before {
margin-bottom : -@arrowSize * 2;
border-top-color : @tooltipColor;
}
&:after{ margin-left: -18px; }
&:before, &:after{
&::after { margin-left : -18px; }
&::before, &::after {
bottom : 100%;
left : 50%;
}
&:hover:after, &:hover:before, &:focus:after, &:focus:before {
&:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateY(-(@arrowSize + 2)));
}
}
.tooltipBottom(@content){
.tooltipBottom(@content) {
.tooltipBase(@content);
&:before {
&::before {
margin-top : -@arrowSize * 2;
border-bottom-color : @tooltipColor;
}
&:after{ margin-left: -18px; }
&:before, &:after{
&::after { margin-left : -18px; }
&::before, &::after {
top : 100%;
left : 50%;
}
&:hover:after, &:hover:before, &:focus:after, &:focus:before {
&:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateY(@arrowSize + 2));
}
}
.tooltipLeft(@content){
.tooltipLeft(@content) {
.tooltipBase(@content);
&:before {
&::before {
margin-right : -@arrowSize * 2;
margin-bottom : -@arrowSize;
border-left-color : @tooltipColor;
}
&:after{ margin-bottom: -14px;}
&:before, &:after {
&::after { margin-bottom : -14px;}
&::before, &::after {
right : 100%;
bottom : 50%;
}
&:hover:after, &:hover:before, &:focus:after, &:focus:before {
&:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateX(-(@arrowSize + 2)));
}
}
.tooltipRight(@content){
.tooltipRight(@content) {
.tooltipBase(@content);
&:before {
&::before {
margin-bottom : -@arrowSize;
margin-left : -@arrowSize * 2;
border-right-color : @tooltipColor;
}
&:after{ margin-bottom: -14px;}
&:before, &:after {
&::after { margin-bottom : -14px;}
&::before, &::after {
bottom : 50%;
left : 100%;
}
&:hover:after, &:hover:before, &:focus:after, &:focus:before {
&:hover::after, &:hover::before, &:focus::after, &:focus::before {
.transform(translateX(@arrowSize + 2));
}
}
.tooltipShow(){
}
.tooltipBase(@content){
.tooltipShow(){ }
.tooltipBase(@content) {
//position: relative;
&:before, &:after{
&::before, &::after {
.animateAll();
position : absolute;
z-index : 1000000;
opacity : 0;
pointer-events : none;
opacity : 0;
}
//Arrow
&:before{
content : '';
&::before {
z-index : 1000001;
content : '';
background : transparent;
border : @arrowSize solid transparent;
}
//Box
&:after{
content : @content;
&::after {
visibility : hidden;
padding : 8px 10px;
background : @tooltipColor;
font-size : 12px;
color : white;
line-height : 12px;
color : white;
white-space : nowrap;
content : @content;
background : @tooltipColor;
}
&:hover:before, &:hover:after {
&:hover::before, &:hover::after {
visibility : visible;
opacity : 1;
}

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js';

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js';
@@ -92,12 +92,12 @@ describe('Multiline Definition Lists', ()=>{
test('Multiline Definition Term must have at least one non-empty Definition', function() {
const source = 'Term 1\n::';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<br>\n<br>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>`);
});
test('Multiline Definition List must have at least one non-newline character after ::', function() {
const source = 'Term 1\n::\nDefinition 1\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<br>\n<br>\n<p>Definition 1</p>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Definition 1</p>`);
});
});

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js';
@@ -6,37 +6,37 @@ describe('Hard Breaks', ()=>{
test('Single Break', function() {
const source = ':\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<br>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>`);
});
test('Double Break', function() {
const source = '::\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<br>\n<br>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>`);
});
test('Triple Break', function() {
const source = ':::\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<br>\n<br>\n<br>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
});
test('Many Break', function() {
const source = '::::::::::\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<br>\n<br>\n<br>\n<br>\n<br>\n<br>\n<br>\n<br>\n<br>\n<br>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
});
test('Multiple sets of Breaks', function() {
const source = ':::\n:::\n:::';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<br>\n<br>\n<br>\n<br>\n<br>\n<br>\n<br>\n<br>\n<br>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
});
test('Break directly between two paragraphs', function() {
const source = 'Line 1\n::\nLine 2';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<br>\n<br>\n<p>Line 2</p>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Line 2</p>`);
});
test('Ignored inside a code block', function() {

View File

@@ -1,72 +1,24 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js';
describe('Non-Breaking Spaces', ()=>{
test('Single Space', function() {
const source = ':>\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>&nbsp;</p>`);
});
test('Double Space', function() {
const source = ':>>\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>&nbsp;&nbsp;</p>`);
});
test('Triple Space', function() {
const source = ':>>>\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>&nbsp;&nbsp;&nbsp;</p>`);
});
test('Many Space', function() {
const source = ':>>>>>>>>>>\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>`);
});
test('Multiple sets of Spaces', function() {
const source = ':>>>\n:>>>\n:>>>';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>&nbsp;&nbsp;&nbsp;\n&nbsp;&nbsp;&nbsp;\n&nbsp;&nbsp;&nbsp;</p>`);
});
test('Pair of inline Spaces', function() {
const source = ':>>:>>';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>`);
});
test('Space directly between two paragraphs', function() {
const source = 'Line 1\n:>>\nLine 2';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1\n&nbsp;&nbsp;\nLine 2</p>`);
});
test('Ignored inside a code block', function() {
const source = '```\n\n:>\n\n```\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:&gt;\n</code></pre>`);
});
describe('Non-Breaking Spaces Interactions', ()=>{
test('I am actually a single-line definition list!', function() {
const source = 'Term ::> Definition 1\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt><dd>> Definition 1</dd>\n</dl>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt><dd>&gt; Definition 1</dd>\n</dl>`);
});
test('I am actually a definition list!', function() {
const source = 'Term\n::> Definition 1\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>> Definition 1</dd></dl>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>&gt; Definition 1</dd></dl>`);
});
test('I am actually a two-term definition list!', function() {
const source = 'Term\n::> Definition 1\n::>> Definition 2';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>> Definition 1</dd>\n<dd>>> Definition 2</dd></dl>`);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>&gt; Definition 1</dd>\n<dd>&gt;&gt; Definition 2</dd></dl>`);
});
});

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js';

View File

@@ -370,6 +370,30 @@ describe('Cross-page variables', ()=>{
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('<p>two</p><p>one</p>\\page<p>two</p>');
});
it('Page numbering across pages : default', function() {
const source0 = `$[HB_pageNumber]\n\n`;
const source1 = `$[HB_pageNumber]\n\n`;
renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('<p>1</p>\\page<p>2</p>');
});
it('Page numbering across pages : custom page number (Number)', function() {
const source0 = `[HB_pageNumber]:100\n\n$[HB_pageNumber]\n\n`;
const source1 = `$[HB_pageNumber]\n\n`;
renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('<p>100</p>\\page<p>101</p>');
});
it('Page numbering across pages : custom page number (NaN)', function() {
const source0 = `[HB_pageNumber]:a\n\n$[HB_pageNumber]\n\n`;
const source1 = `$[HB_pageNumber]\n\n`;
renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('<p>a</p>\\page<p>a</p>');
});
});
describe('Math function parameter handling', ()=>{
@@ -410,4 +434,102 @@ describe('Regression Tests', ()=>{
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>');
});
it('Handle Extra spaces in image alt-text 1', function(){
const source='![ where is my image??](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
});
it('Handle Extra spaces in image alt-text 2', function(){
const source='![where is my image??](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
});
it('Handle Extra spaces in image alt-text 3', function(){
const source='![where is my image?? ](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
});
it('Handle Extra spaces in image alt-text 4', function(){
const source='![where is my image??](http://i.imgur.com/hMna6G0.png){height=20%,width=20%}';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p><img style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" height=\"20%\" width=\"20%\"></p>');
});
});
describe('Custom Math Function Tests', ()=>{
it('Sign Test', function() {
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[sign(a)]\n\nNegative: $[sign(b)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Positive: +</p><p>Negative: -</p>');
});
it('Signed Test', function() {
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[signed(a)]\n\nNegative: $[signed(b)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Positive: +13</p><p>Negative: -11</p>');
});
it('Roman Numerals Test', function() {
const source = `[a]: 18\n\nRoman Numeral: $[toRomans(a)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
});
it('Roman Numerals Test - Uppercase', function() {
const source = `[a]: 18\n\nRoman Numeral: $[toRomansUpper(a)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
});
it('Roman Numerals Test - Lowercase', function() {
const source = `[a]: 18\n\nRoman Numeral: $[toRomansLower(a)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Roman Numeral: xviii</p>');
});
it('Number to Characters Test', function() {
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toChar(a)] $[toChar(b)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Characters: R AM</p>');
});
it('Number to Characters Test - Uppercase', function() {
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharUpper(a)] $[toCharUpper(b)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Characters: R AM</p>');
});
it('Number to Characters Test - Lowercase', function() {
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharLower(a)] $[toCharLower(b)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Characters: r am</p>');
});
it('Number to Words Test', function() {
const source = `[a]: 80085\n\nWords: $[toWords(a)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
});
it('Number to Words Test - Uppercase', function() {
const source = `[a]: 80085\n\nWords: $[toWordsUpper(a)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Words: EIGHTY THOUSAND AND EIGHTY-FIVE</p>');
});
it('Number to Words Test - Lowercase', function() {
const source = `[a]: 80085\n\nWords: $[toWordsLower(a)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
});
it('Number to Words Test - Capitalized', function() {
const source = `[a]: 80085\n\nWords: $[toWordsCaps(a)]`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>Words: Eighty Thousand And Eighty-Five</p>');
});
});

View File

@@ -9,26 +9,22 @@
@headerText : #58180D; // Dark maroon
@monsterStatBackground : #FDF1DC; // Lighter parchment
@captionText : #766649; // Brown
@page { margin: 0; }
body {
counter-reset : phb-page-numbers;
}
*{
-webkit-print-color-adjust : exact;
}
.useSansSerif(){
font-family : ScalySans;
em{
font-family : ScalySans;
@page { margin : 0; }
body { counter-reset : phb-page-numbers; }
* { -webkit-print-color-adjust : exact; }
.useSansSerif() {
font-family : 'ScalySans';
em {
font-family : 'ScalySans';
font-style : italic;
}
strong{
font-family : ScalySans;
strong {
font-family : 'ScalySans';
font-weight : 800;
letter-spacing : -0.02em;
}
}
.useColumns(@multiplier : 1){
.useColumns(@multiplier : 1) {
column-count : 2;
column-fill : auto;
column-gap : 1cm;
@@ -40,21 +36,21 @@ body {
-webkit-column-gap : 1cm;
-moz-column-gap : 1cm;
}
.phb, .page{
.phb, .page {
.useColumns();
counter-increment : phb-page-numbers;
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
height : 279.4mm;
width : 215.9mm;
height : 279.4mm;
padding : 1.0cm 1.7cm;
padding-bottom : 1.5cm;
overflow : hidden;
font-family : 'BookSanity';
font-size : 0.317cm;
counter-increment : phb-page-numbers;
background-color : @background;
background-image : @backgroundImage;
font-family : BookSanity;
font-size : 0.317cm;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
@@ -63,199 +59,175 @@ body {
contain-intrinsic-size : auto none;
}
.phb{
.phb {
//*****************************
// * BASE
// *****************************/
p{
p {
padding-bottom : 0.8em;
line-height : 1.269em;
&+p{
margin-top : -0.8em;
}
& + p { margin-top : -0.8em; }
}
ul{
margin-bottom : 0.8em;
ul {
padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.269em;
list-style-position : outside;
list-style-type : disc;
}
ol{
margin-bottom : 0.8em;
ol {
padding-left : 1.4em;
margin-bottom : 0.8em;
line-height : 1.269em;
list-style-position : outside;
list-style-type : decimal;
}
//Indents after p or lists
p+p, ul+p, ol+p{
text-indent : 1em;
}
img{
z-index : -1;
}
strong{
p + p, ul + p, ol + p { text-indent : 1em; }
img { z-index : -1; }
strong {
font-weight : bold;
letter-spacing : 0.03em;
}
em{
font-style : italic;
}
sup{
em { font-style : italic; }
sup {
font-size : smaller;
line-height : 0;
vertical-align : super;
font-size : smaller;
line-height : 0;
}
sub{
vertical-align : sub;
sub {
font-size : smaller;
line-height : 0;
vertical-align : sub;
}
//*****************************
// * HEADERS
// *****************************/
h1,h2,h3,h4{
h1,h2,h3,h4 {
margin-top : 0.2em;
margin-bottom : 0.2em;
font-family : MrJeeves;
font-family : 'MrJeeves';
font-weight : 800;
color : @headerText;
}
h1{
h1 {
column-span : all;
font-size : 0.987cm;
-webkit-column-span : all;
-moz-column-span : all;
&+p::first-letter{
& + p::first-letter {
float : left;
font-family : Solberry;
font-family : 'Solberry';
font-size : 10em;
color : #222;
line-height : 0.795em;
color : #222222;
}
}
h2{
font-size : 0.705cm;
}
h3{
h2 { font-size : 0.705cm; }
h3 {
font-size : 0.529cm;
border-bottom : 2px solid @headerUnderline;
}
h4{
h4 {
margin-bottom : 0.00em;
font-size : 0.458cm;
}
h5{
h5 {
margin-bottom : 0.2em;
font-family : ScalySansSmallCaps;
font-family : 'ScalySansSmallCaps';
font-size : 0.423cm;
font-weight : 900;
}
//*****************************
// * TABLE
// *****************************/
table{
table {
.useSansSerif();
width : 100%;
margin-bottom : 1em;
font-size : 10pt;
thead{
display: table-row-group;
thead {
display : table-row-group;
font-weight : 800;
th{
vertical-align : bottom;
padding-bottom : 0.3em;
th {
padding-right : 0.1em;
padding-bottom : 0.3em;
padding-left : 0.1em;
vertical-align : bottom;
}
}
tbody{
tr{
td{
padding : 0.3em 0.1em;
}
&:nth-child(odd){
background-color : @noteGreen;
}
tbody {
tr {
td { padding : 0.3em 0.1em; }
&:nth-child(odd) { background-color : @noteGreen; }
}
}
}
//*****************************
// * NOTE
// *****************************/
blockquote{
blockquote {
.useSansSerif();
box-sizing : border-box;
margin-bottom : 1em;
padding : 5px 10px;
margin-bottom : 1em;
background-color : @noteGreen;
border-style : solid;
border-width : 11px;
border-image : @noteBorderImage 11;
border-image-outset : 9px 0px;
box-shadow : 1px 4px 14px #888;
p, ul{
box-shadow : 1px 4px 14px #888888;
p, ul {
font-size : 0.352cm;
line-height : 1.083em;
}
}
//If a note starts a column, give it space at the top to render border
pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote {
margin-top : 13px;
}
pre + blockquote, h2 + blockquote, h3 + blockquote, h4 + blockquote, h5 + blockquote { margin-top : 13px; }
//*****************************
// * MONSTER STAT BLOCK
// *****************************/
hr+blockquote{
hr+blockquote {
position : relative;
padding-top : 15px;
background-color : @monsterStatBackground;
border-style : solid;
border-width : 10px;
border-image : @monsterBorderImageLegacy 10;
h2{
h2 {
margin-top : -8px;
margin-bottom : 0px;
&+p{
padding-bottom : 0px;
}
& + p { padding-bottom : 0px; }
}
h3{
font-family : ScalySans;
font-weight : 400;
h3 {
font-family : 'ScalySans';
font-weight : normal;
border-bottom : 1px solid @headerText;
}
hr+ul{
color : @headerText;
}
ul{
hr + ul { color : @headerText; }
ul {
.useSansSerif();
padding-left : 1em;
font-size : 0.352cm;
}
// Monster Ability table
hr+table{
hr + table {
margin : 0;
background-color : transparent;
border-style : none;
border-image : none;
tbody{
tr:nth-child(odd), tr:nth-child(even){
background-color : transparent;
}
tbody {
tr:nth-child(odd), tr:nth-child(even) { background-color : transparent; }
}
}
table{
color : @headerText;
}
p+p{
margin-top : 0em;
table { color : @headerText; }
p + p {
padding-bottom : 0.5em;
margin-top : 0em;
text-indent : 0em;
}
//Triangle dividers
hr{
hr {
visibility : visible;
height : 6px;
margin : 4px 0px;
@@ -265,100 +237,90 @@ body {
}
}
//Full Width
hr+hr+blockquote{
hr + hr + blockquote {
.useColumns(0.96);
column-fill : balance;
}
//*****************************
// * FOOTER
// *****************************/
&:after{
content : "";
&:after {
position : absolute;
bottom : 0px;
left : 0px;
z-index : 100;
height : 50px;
width : 100%;
height : 50px;
content : '';
background-image : @footerAccentImage;
background-size : cover;
}
&:nth-child(even){
&:after{
transform : scaleX(-1);
}
.pageNumber{
left : 2px;
}
.footnote{
&:nth-child(even) {
&::after { transform : scaleX(-1); }
.pageNumber { left : 2px; }
.footnote {
left : 80px;
text-align : left;
}
}
.pageNumber{
.pageNumber {
position : absolute;
right : 2px;
bottom : 22px;
width : 50px;
font-size : 0.9em;
color : #c9ad6a;
color : #C9AD6A;
text-align : center;
&.auto::after {
content : counter(phb-page-numbers);
}
&.auto::after { content : counter(phb-page-numbers); }
}
.footnote{
.footnote {
position : absolute;
right : 80px;
bottom : 32px;
z-index : 150;
width : 200px;
font-size : 0.8em;
color : #c9ad6a;
color : #C9AD6A;
text-align : right;
}
//*****************************
// * EXTRAS
// *****************************/
hr{
hr {
visibility : hidden;
margin : 0px;
}
//Modified unorder list, used in spells
hr+ul{
margin-bottom : 0.5em;
hr + ul {
padding-left : 1em;
margin-bottom : 0.5em;
text-indent : -1em;
list-style-type : none;
}
//Column Break
pre, code{
pre, code {
visibility : hidden;
-webkit-column-break-after : always;
break-after : always;
-moz-column-break-after : always;
}
//Avoid breaking up
p,blockquote,table{
p,blockquote,table {
z-index : 15;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
//Better spacing for spell blocks
h4+p+hr+ul{
margin-top : -0.5em
}
h4 + p + hr + ul { margin-top : -0.5em; }
//Text indent right after table
table+p{
text-indent : 1em;
}
table + p { text-indent : 1em; }
// Nested lists
ul ul,ol ol,ul ol,ol ul{
ul ul,ol ol,ul ol,ol ul {
margin-bottom : 0px;
margin-left : 1.5em;
}
li{
li {
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
@@ -367,89 +329,81 @@ body {
//*****************************
// * SPELL LIST
// *****************************/
.phb .spellList{
.phb .spellList {
.useSansSerif();
column-count : 4;
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
ul+h5{
margin-top : 15px;
}
p, ul{
column-span : all;
ul + h5 { margin-top : 15px; }
p, ul {
font-size : 0.352cm;
line-height : 1.263em;
}
ul{
margin-bottom : 0.5em;
ul {
padding-left : 1em;
margin-bottom : 0.5em;
text-indent : -1em;
list-style-type : none;
break-inside : auto;
-webkit-column-break-inside : auto;
page-break-inside : auto;
break-inside : auto;
}
}
//*****************************
// * WIDE
// *****************************/
.phb .wide{
column-span : all;
.phb .wide {
-webkit-column-span : all;
-moz-column-span : all;
column-span : all;
}
//*****************************
// * CLASS TABLE
// *****************************/
.phb .classTable{
.phb .classTable {
margin-top : 25px;
margin-bottom : 40px;
border-collapse : separate;
background-color : white;
border : initial;
border-style : solid;
border-image-source : @frameBorderImage;
border-image-slice : 150 200 150 200;
border-image-width : 47px;
border-image-outset : 25px 17px;
border-image-repeat : stretch;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorderImage;
border-image-width : 47px;
h5{
margin-bottom : 10px;
}
h5 { margin-bottom : 10px; }
}
//************************************
// * DESCRIPTIVE TEXT BOX
// ************************************/
.phb .descriptive{
.phb .descriptive {
margin-bottom : 1em;
background-color : #faf7ea;
font-family : ScalySans;
font-family : 'ScalySans';
background-color : #FAF7EA;
border-style : solid;
border-width : 7px;
border-image : @descriptiveBoxImage 12 stretch;
border-image-outset : 4px;
box-shadow : 0px 0px 6px #faf7ea;
p{
box-shadow : 0px 0px 6px #FAF7EA;
p {
display : block;
padding-bottom : 0px;
line-height : 1.47em;
}
p + p {
padding-top : .8em;
}
p + p { padding-top : 0.8em; }
em {
font-family : ScalySans;
font-family : 'ScalySans';
font-style : italic;
}
strong {
font-family : ScalySans;
font-family : 'ScalySans';
font-weight : 800;
letter-spacing : -0.02em;
}
}
.phb pre+.descriptive{
margin-top : 8px;
}
.phb pre + .descriptive { margin-top : 8px; }
//*****************************
// * ARTIST CREDIT BLOCK
@@ -457,47 +411,41 @@ body {
.phb {
.artist {
position : absolute;
text-align : center;
font-family : WalterTurncoat;
font-family : 'WalterTurncoat';
font-size : 0.27cm;
color : @captionText;
text-align : center;
p, p + p {
margin : unset;
text-indent : unset;
line-height : 0.941em;
text-indent : unset;
}
h5 {
h5 {
font-family : 'WalterTurncoat';
font-size : 1.3em;
font-family : WalterTurncoat;
}
a{
a {
color : inherit;
text-decoration : unset;
&:hover {
text-decoration : underline;
}
&:hover { text-decoration : underline; }
}
}
}
//*****************************
// * TABLE OF CONTENTS
// *****************************/
.phb .toc{
.phb .toc {
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
a{
a {
color : black;
text-decoration : none;
&:hover{
text-decoration : underline;
}
&:hover { text-decoration : underline; }
}
ul{
ul {
padding-left : 0;
list-style-type : none;
}
&>ul>li{
margin-bottom : 10px;
}
& > ul > li { margin-bottom : 10px; }
}

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
module.exports = [
];

View File

@@ -7,37 +7,29 @@
}
.page {
background-image : url(/assets/DMG_background.png);
background-image : url('/assets/DMG_background.png');
background-size : cover;
/*TABLES WITHIN NOTES*/
.note table tbody tr:nth-child(odd) {
background:#fff;
}
/* TABLES WITHIN NOTES */
.note table tbody tr:nth-child(odd) { background : #FFFFFF; }
/*DROP CAP*/
/* DROP CAP */
h1 + p::first-letter {
background-image: unset;
color:black;
color : black;
background-image : unset;
}
.quote p:first-child::first-line {
all: unset;
.quote p:first-child::first-line { all : unset; }
&::after {
height : 58px;
background-image : url('/assets/DMG_footerAccent.png');
}
&:after {
background-image : url(/assets/DMG_footerAccent.png);
height: 58px;
}
.footnote {
bottom : 40px;
}
.footnote { bottom : 40px; }
}
.page:has(.partCover) {
.partCover {
background-image: @partCoverHeaderDMG;
}
.partCover { background-image : @partCoverHeaderDMG; }
}

View File

@@ -6,164 +6,12 @@ const MonsterBlockGen = require('./snippets/monsterblock.gen.js');
const scriptGen = require('./snippets/script.gen.js');
const ClassFeatureGen = require('./snippets/classfeature.gen.js');
const CoverPageGen = require('./snippets/coverpage.gen.js');
const TableOfContentsGen = require('./snippets/tableOfContents.gen.js');
const indexGen = require('./snippets/index.gen.js');
const QuoteGen = require('./snippets/quote.gen.js');
const dedent = require('dedent-tabs').default;
module.exports = [
{
groupName : 'Text Editor',
icon : 'fas fa-pencil-alt',
view : 'text',
snippets : [
{
name : 'Table of Contents',
icon : 'fas fa-book',
gen : TableOfContentsGen,
experimental : true,
subsnippets : [
{
name : 'Generate Table of Contents',
icon : 'fas fa-book',
gen : TableOfContentsGen,
experimental : true
},
{
name : 'Table of Contents Individual Inclusion',
icon : 'fas fa-book',
gen : dedent `\n{{tocInclude# CHANGE # to your header level
}}\n`,
subsnippets : [
{
name : 'Individual Inclusion H1',
icon : 'fas fa-book',
gen : dedent `\n{{tocIncludeH1 \n
}}\n`,
},
{
name : 'Individual Inclusion H2',
icon : 'fas fa-book',
gen : dedent `\n{{tocIncludeH2 \n
}}\n`,
},
{
name : 'Individual Inclusion H3',
icon : 'fas fa-book',
gen : dedent `\n{{tocIncludeH3 \n
}}\n`,
},
{
name : 'Individual Inclusion H4',
icon : 'fas fa-book',
gen : dedent `\n{{tocIncludeH4 \n
}}\n`,
},
{
name : 'Individual Inclusion H5',
icon : 'fas fa-book',
gen : dedent `\n{{tocIncludeH5 \n
}}\n`,
},
{
name : 'Individual Inclusion H6',
icon : 'fas fa-book',
gen : dedent `\n{{tocIncludeH6 \n
}}\n`,
}
]
},
{
name : 'Table of Contents Range Inclusion',
icon : 'fas fa-book',
gen : dedent `\n{{tocDepthH3
}}\n`,
subsnippets : [
{
name : 'Include in ToC up to H3',
icon : 'fas fa-dice-three',
gen : dedent `\n{{tocDepthH3
}}\n`,
},
{
name : 'Include in ToC up to H4',
icon : 'fas fa-dice-four',
gen : dedent `\n{{tocDepthH4
}}\n`,
},
{
name : 'Include in ToC up to H5',
icon : 'fas fa-dice-five',
gen : dedent `\n{{tocDepthH5
}}\n`,
},
{
name : 'Include in ToC up to H6',
icon : 'fas fa-dice-six',
gen : dedent `\n{{tocDepthH6
}}\n`,
},
]
},
{
name : 'Table of Contents Individual Exclusion',
icon : 'fas fa-book',
gen : dedent `\n{{tocExcludeH1 \n
}}\n`,
subsnippets : [
{
name : 'Individual Exclusion H1',
icon : 'fas fa-book',
gen : dedent `\n{{tocExcludeH1 \n
}}\n`,
},
{
name : 'Individual Exclusion H2',
icon : 'fas fa-book',
gen : dedent `\n{{tocExcludeH2 \n
}}\n`,
},
{
name : 'Individual Exclusion H3',
icon : 'fas fa-book',
gen : dedent `\n{{tocExcludeH3 \n
}}\n`,
},
{
name : 'Individual Exclusion H4',
icon : 'fas fa-book',
gen : dedent `\n{{tocExcludeH4 \n
}}\n`,
},
{
name : 'Individual Exclusion H5',
icon : 'fas fa-book',
gen : dedent `\n{{tocExcludeH5 \n
}}\n`,
},
{
name : 'Individual Exclusion H6',
icon : 'fas fa-book',
gen : dedent `\n{{tocExcludeH6 \n
}}\n`,
},
]
},
]
},
{
name : 'Index',
icon : 'fas fa-bars',
gen : indexGen,
experimental : true
}
]
},
{
groupName : 'Style Editor',
icon : 'fas fa-pencil-alt',
@@ -192,70 +40,9 @@ module.exports = [
line-height: 1em;
}\n\n`
},
{
name : 'Table of Contents Toggles',
icon : 'fas fa-book',
subsnippets : [
{
name : 'Enable H1-H4 all pages',
icon : 'fas fa-dice-four',
gen : `.page {\n\th4 {--TOC: include; }\n}\n\n`,
},
{
name : 'Enable H1-H5 all pages',
icon : 'fas fa-dice-five',
gen : `.page {\n\th4, h5 {--TOC: include; }\n}\n\n`,
},
{
name : 'Enable H1-H6 all pages',
icon : 'fas fa-dice-six',
gen : `.page {\n\th4, h5, h6 {--TOC: include; }\n}\n\n`,
},
]
}
]
},
/*********************** IMAGES *******************/
{
groupName : 'Images',
icon : 'fas fa-images',
view : 'text',
snippets : [
{
name : 'Image',
icon : 'fas fa-image',
gen : dedent`
![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
##### Cat Warrior
[Kyoung Hwan Kim](https://www.artstation.com/tahra)
}}`
},
{
name : 'Background Image',
icon : 'fas fa-tree',
gen : dedent`
![homebrew mug](http://i.imgur.com/hMna6G0.png) {position:absolute,top:50px,right:30px,width:280px}
{{artist,top:80px,right:30px
##### Homebrew Mug
[naturalcrit](https://homebrew.naturalcrit.com)
}}`
},
{
name : 'Watermark',
icon : 'fas fa-id-card',
gen : dedent`
{{watermark Homebrewery}}\n`
},
]
},
/************************* PHB ********************/
{
groupName : 'PHB',
icon : 'fas fa-book',
@@ -450,9 +237,6 @@ module.exports = [
]
},
/**************** PAGE *************/
{

View File

@@ -148,7 +148,7 @@ const genAction = function(){
'Turnbuckle Roll'
]);
return `***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `;
return `***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5 ft., one target. *Hit:* 5 (1d6 + 2) `;
};
@@ -161,8 +161,8 @@ module.exports = {
*${getType()}, ${getAlignment()}*
___
**Armor Class** :: ${_.random(10, 20)} (chain mail, shield)
**Hit Points** :: ${_.random(1, 150)}(1d4 + 5)
**Speed** :: ${_.random(0, 50)}ft.
**Hit Points** :: ${_.random(1, 150)} (1d4 + 5)
**Speed** :: ${_.random(0, 50)} ft.
___
| STR | DEX | CON | INT | WIS | CHA |
|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|

View File

@@ -305,12 +305,12 @@
margin-left : -0.16cm;
background-color : var(--HB_Color_MonsterStatBackground);
background-image : @monsterBlockBackground;
background-blend-mode : overlay;
border-style : solid;
border-width : 7px 6px;
border-image : @monsterBorderImage 14 round;
border-image-outset : 0px 2px;
box-shadow : 1px 4px 14px #888888;
background-blend-mode : overlay;
}
position : relative;
@@ -335,9 +335,9 @@
//Triangle dividers
hr {
visibility : visible;
height : 6px;
margin : 0.12cm 0cm;
visibility : visible;
background-image : @redTriangleImage;
background-size : 100% 100%;
border : none;
@@ -355,8 +355,8 @@
}
.bonus {
float: right;
padding-right: 0.5em;
float : right;
padding-right : 0.5em;
}
// Monster Ability table
@@ -456,8 +456,8 @@
// * EXTRAS
// *****************************/
hr {
margin : 0px;
visibility : hidden;
margin : 0px;
}
//Text indent right after table
table + p { text-indent : 1em; }
@@ -525,10 +525,10 @@
content : '';
background-image : @classTableDecoration,
@classTableDecoration;
filter : drop-shadow(0px 0px 1px #C8C5C080);
background-repeat : no-repeat, no-repeat;
background-position : top, bottom;
background-size : contain, contain;
filter : drop-shadow(0px 0px 1px #C8C5C080);
transform : translateY(-50%) translateX(-50%);
}
&.decoration.wide::before {
@@ -545,39 +545,40 @@
columns : 1;
text-align : center;
&::after { display : none; }
.frontCover { position : absolute; }
h1 {
margin-top : 1.2cm;
margin-bottom : 0;
font-family : 'NodestoCapsCondensed';
font-size : 2.245cm;
font-weight : normal;
line-height : 1.9cm;
color : white;
text-shadow : unset;
text-transform : uppercase;
-webkit-text-stroke: 0.2cm black;
paint-order:stroke;
margin-top : 1.55cm;
margin-bottom : 0;
font-family : 'NodestoCapsCondensed';
font-size : 2.245cm;
font-weight : normal;
line-height : 1.9cm;
color : white;
text-transform : uppercase;
text-shadow : unset;
-webkit-text-stroke : 0.2cm black;
paint-order : stroke;
}
h2 {
font-family : 'NodestoCapsCondensed';
font-size : 0.85cm;
font-weight : normal;
color : white;
letter-spacing : 0.1cm;
-webkit-text-stroke: 0.14cm black;
paint-order:stroke;
font-family : 'NodestoCapsCondensed';
font-size : 0.85cm;
font-weight : normal;
color : white;
letter-spacing : 0.1cm;
-webkit-text-stroke : 0.14cm black;
paint-order : stroke;
}
hr {
position : relative;
display : block;
visibility : visible;
width : 12cm;
height : 0.5cm;
margin : auto;
visibility : visible;
background-image : @horizontalRule;
filter : drop-shadow(0 0 3px black);
background-size : 100% 100%;
border : none;
filter : drop-shadow(0 0 3px black);
}
.banner {
position : absolute;
@@ -600,19 +601,19 @@
filter : drop-shadow(2px 2px 2px black);
}
.footnote {
position : absolute;
right : 0;
bottom : 1.3cm;
left : 0;
width : 70%;
margin-right : auto;
margin-left : auto;
font-family : 'Overpass';
font-size : 0.496cm;
color : white;
text-align : center;
-webkit-text-stroke: 0.1cm black;
paint-order:stroke;
position : absolute;
right : 0;
bottom : 1.3cm;
left : 0;
width : 70%;
margin-right : auto;
margin-left : auto;
font-family : 'Overpass';
font-size : 0.496cm;
color : white;
text-align : center;
-webkit-text-stroke : 0.1cm black;
paint-order : stroke;
}
.logo {
position : absolute;
@@ -620,10 +621,7 @@
right : 0;
left : 0;
filter : drop-shadow(0 0 0.075cm black);
img {
width : 100%;
height : 2cm;
}
img { height : 2cm; }
}
}
// *****************************
@@ -633,8 +631,9 @@
columns : 1;
text-align : center;
&::after { display : none; }
.insideCover { position : absolute; }
h1 {
margin-top : 1.2cm;
margin-top : 1.55cm;
margin-bottom : 0;
font-family : 'NodestoCapsCondensed';
font-size : 2.1cm;
@@ -651,10 +650,10 @@
hr {
position : relative;
display : block;
visibility : visible;
width : 12cm;
height : 0.5cm;
margin : auto;
visibility : visible;
background-image : @horizontalRule;
background-size : 100% 100%;
border : none;
@@ -665,31 +664,28 @@
bottom : 1cm;
left : 0;
height : 2cm;
img {
width : 100%;
height : 2cm;
}
img { height : 2cm; }
}
}
// *****************************
// * BACK COVER
// *****************************/
.page:has(.backCover) {
padding : 2.25cm 1.3cm 2cm 1.3cm;
color : #FFFFFF;
columns : 1;
padding : 2.25cm 1.3cm 2cm 1.3cm;
line-height : 1.4em;
color : #FFFFFF;
columns : 1;
&::after { display : none; }
.columnWrapper { width : 7.6cm; }
.backCover {
position : absolute;
inset : 0;
z-index : -1;
width : 11cm;
background-image : @backCover;
background-repeat : no-repeat;
background-size : contain;
}
.blank { height : 1.4em; }
h1 {
margin-bottom : 0.3cm;
font-family : 'NodestoCapsCondensed';
@@ -707,12 +703,12 @@
height : 100%;
}
hr {
visibility : visible;
width : 4.5cm;
height : 0.53cm;
margin-top : 1.1cm;
margin-right : auto;
margin-left : auto;
visibility : visible;
background-image : @horizontalRule;
background-size : 100% 100%;
border : none;
@@ -736,7 +732,6 @@
img {
position : relative;
z-index : 0;
width : 100%;
height : 1.5cm;
}
p {
@@ -796,54 +791,13 @@
// * TABLE OF CONTENTS
// *****************************/
// Default Exclusions
// Anything not excluded is included, default Headers are H1, H2, and H3.
h4,
h5,
h6,
.page:has(.frontCover),
.page:has(.backCover),
.page:has(.insideCover),
.monster,
.noToC,
.toc { --TOC: exclude; }
// Brew level default inclusion changes.
// These add Headers 'back' to inclusion.
//NOTE: DO NOT USE :HAS WITH .PAGES!!! EXTREMELY SLOW TO RENDER ON LARGE DOCS!
// Block level inclusion changes
// These include either a single (include) or a range (depth)
.tocIncludeH1 h1 {--TOC: include; }
.tocIncludeH2 h2 {--TOC: include; }
.tocIncludeH3 h3 {--TOC: include; }
.tocIncludeH4 h4 {--TOC: include; }
.tocIncludeH5 h5 {--TOC: include; }
.tocIncludeH6 h6 {--TOC: include; }
.tocDepthH2 :is(h1, h2) {--TOC: include; }
.tocDepthH3 :is(h1, h2, h3) {--TOC: include; }
.tocDepthH4 :is(h1, h2, h3, h4) {--TOC: include; }
.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC: include; }
.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC: include; }
// Block level exclusion changes
// These exclude a single block level
.tocExcludeH1 h1 {--TOC: exclude; }
.tocExcludeH2 h2 {--TOC: exclude; }
.tocExcludeH3 h3 {--TOC: exclude; }
.tocExcludeH4 h4 {--TOC: exclude; }
.tocExcludeH5 h5 {--TOC: exclude; }
.tocExcludeH6 h6 {--TOC: exclude; }
// Additional Default Exclusions
.monster { --TOC : exclude; }
.page:has(.partCover) {
--TOC: exclude;
& h1 {
--TOC: include;
}
}
--TOC : exclude;
& h1 { --TOC : include; }
}
.page {
&:has(.toc)::after { display : none; }
@@ -909,9 +863,7 @@ h6,
.useColumns(0.96, @fillMode: balance);
}
}
.toc.wide li {
break-inside: auto;
}
.toc.wide li { break-inside : auto; }
}
// *****************************
@@ -936,9 +888,7 @@ h6,
.page h1 + * { margin-top : 0; }
.page .descriptive.wide + * {
margin-top: 0;
}
.page .descriptive.wide + * { margin-top : 0; }
//*****************************
// * RUNE TABLE
@@ -953,8 +903,8 @@ h6,
width : 1.3cm;
height : 1.3cm;
font-weight : normal;
text-transform : uppercase;
vertical-align : middle;
text-transform : uppercase;
outline : 1px solid #000000;
}
th {
@@ -975,6 +925,7 @@ h6,
}
}
}
// *****************************
// * INDEX
// *****************************/

View File

@@ -4,6 +4,8 @@ const WatercolorGen = require('./snippets/watercolor.gen.js');
const ImageMaskGen = require('./snippets/imageMask.gen.js');
const FooterGen = require('./snippets/footer.gen.js');
const dedent = require('dedent-tabs').default;
const TableOfContentsGen = require('./snippets/tableOfContents.gen.js');
const indexGen = require('./snippets/index.gen.js');
module.exports = [
@@ -36,6 +38,11 @@ module.exports = [
icon : 'fas fa-sort-numeric-down',
gen : '{{pageNumber,auto}}\n'
},
{
name : 'Variable Auto Page Number',
icon : 'fas fa-sort-numeric-down',
gen : '{{pageNumber $[HB_pageNumber]}}\n'
},
{
name : 'Skip Page Number Increment this Page',
icon : 'fas fa-xmark',
@@ -141,7 +148,53 @@ module.exports = [
[Homebrewery.Naturalcrit.com](https://homebrewery.naturalcrit.com)
}}\n\n`;
},
}
},
{
name : 'Table of Contents',
icon : 'fas fa-book',
gen : TableOfContentsGen,
experimental : true,
subsnippets : [
{
name : 'Table of Contents',
icon : 'fas fa-book',
gen : TableOfContentsGen,
experimental : true
},
{
name : 'Include in ToC up to H3',
icon : 'fas fa-dice-three',
gen : dedent `\n{{tocDepthH3
}}\n`,
},
{
name : 'Include in ToC up to H4',
icon : 'fas fa-dice-four',
gen : dedent `\n{{tocDepthH4
}}\n`,
},
{
name : 'Include in ToC up to H5',
icon : 'fas fa-dice-five',
gen : dedent `\n{{tocDepthH5
}}\n`,
},
{
name : 'Include in ToC up to H6',
icon : 'fas fa-dice-six',
gen : dedent `\n{{tocDepthH6
}}\n`,
}
]
},
{
name : 'Index',
icon : 'fas fa-bars',
gen : indexGen,
experimental : true
},
]
},
{
@@ -153,7 +206,7 @@ module.exports = [
name : 'Add Comment',
icon : 'fas fa-code',
gen : '/* This is a comment that will not be rendered into your brew. */'
},
}
]
},
@@ -438,6 +491,15 @@ module.exports = [
icon : 'fas fa-print',
view : 'style',
snippets : [
{
name : 'US Letter Page Size',
icon : 'far fa-file',
gen : dedent`/* US Letter Page Size */
.page {
width : 215.9mm; /* 8.5in */
height : 279.4mm; /* 11in */
}\n\n`,
},
{
name : 'A3 Page Size',
icon : 'far fa-file',

View File

@@ -5,6 +5,7 @@
@import (less) './themes/fonts/iconFonts/diceFont.less';
@import (less) './themes/fonts/iconFonts/gameIcons.less';
@import (less) './themes/fonts/iconFonts/fontAwesome.less';
@import (less) './themes/fonts/Journal/fonts.less';
:root {
//Colors
@@ -21,9 +22,9 @@ body { counter-reset : page-numbers 0; }
// *****************************/
.page {
.block {
break-inside : avoid;
display : inline-block;
width : 100%;
break-inside : avoid;
img { z-index : 0; }
}
.inline-block {
@@ -58,8 +59,8 @@ body { counter-reset : page-numbers 0; }
content-visibility : auto;
contain-intrinsic-size : auto none;
}
//*****************************
// * BASE
//*****************************
// * BASE
// *****************************/
.page {
p {
@@ -120,7 +121,7 @@ body { counter-reset : page-numbers 0; }
// * CODE BLOCKS
// ************************************/
code {
font-family : 'Courier New', "Courier", monospace;
font-family : 'Courier New', 'Courier', monospace;
overflow-wrap : break-word;
white-space : pre-wrap;
}
@@ -133,10 +134,10 @@ body { counter-reset : page-numbers 0; }
// * EXTRAS
// *****************************/
.columnSplit {
margin-top : 0;
visibility : hidden;
-webkit-column-break-after : always;
margin-top : 0;
break-after : always;
-webkit-column-break-after : always;
-moz-column-break-after : always;
& + * { margin-top : 0; }
}
@@ -199,11 +200,11 @@ body { counter-reset : page-numbers 0; }
background-color : var(--HB_Color_WatercolorStain); /* default color */
background-size : cover;
-webkit-mask-image : var(--wc);
-webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-image : var(--wc);
mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-repeat : no-repeat;
-webkit-mask-size : contain;
mask-size : contain;
--wc : @watercolor1; /* default image */
}
@@ -231,15 +232,15 @@ body { counter-reset : page-numbers 0; }
height : 200%;
background-image : var(--checkerboard);
background-size : 20px;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : repeat-x;
-webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge
-webkit-mask-position : 50% calc(50% - var(--offset));
mask-image : var(--wc);
-webkit-mask-repeat : repeat-x;
mask-repeat : repeat-x;
mask-size : 50%;
-webkit-mask-position : 50% calc(50% - var(--offset));
mask-position : 50% calc(50% - var(--offset));
-webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge
mask-size : 50%;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
--rotation : 0;
--revealer : none;
--checkerboard : none;
@@ -276,19 +277,19 @@ body { counter-reset : page-numbers 0; }
}
&.revealImage {
--revealer : linear-gradient(0deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2));
--checkerboard : url("/assets/waterColorMasks/missingImage.png"); //shows any masked regions not filled by image
--checkerboard : url('/assets/waterColorMasks/missingImage.png'); //shows any masked regions not filled by image
}
}
.imageMaskEdge {
&1 { --wc : url("/assets/waterColorMasks/edge/0001.webp"); }
&2 { --wc : url("/assets/waterColorMasks/edge/0002.webp"); }
&3 { --wc : url("/assets/waterColorMasks/edge/0003.webp"); }
&4 { --wc : url("/assets/waterColorMasks/edge/0004.webp"); }
&5 { --wc : url("/assets/waterColorMasks/edge/0005.webp"); }
&6 { --wc : url("/assets/waterColorMasks/edge/0006.webp"); }
&7 { --wc : url("/assets/waterColorMasks/edge/0007.webp"); }
&8 { --wc : url("/assets/waterColorMasks/edge/0008.webp"); }
&1 { --wc : url('/assets/waterColorMasks/edge/0001.webp'); }
&2 { --wc : url('/assets/waterColorMasks/edge/0002.webp'); }
&3 { --wc : url('/assets/waterColorMasks/edge/0003.webp'); }
&4 { --wc : url('/assets/waterColorMasks/edge/0004.webp'); }
&5 { --wc : url('/assets/waterColorMasks/edge/0005.webp'); }
&6 { --wc : url('/assets/waterColorMasks/edge/0006.webp'); }
&7 { --wc : url('/assets/waterColorMasks/edge/0007.webp'); }
&8 { --wc : url('/assets/waterColorMasks/edge/0008.webp'); }
}
[class*='imageMaskCenter'] {
@@ -296,15 +297,15 @@ body { counter-reset : page-numbers 0; }
left : calc(var(--offsetX));
width : 100%;
height : 100%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
-webkit-mask-position : 0% 0%;
mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
mask-repeat : no-repeat;
mask-size : 100% 100%; //Scale both dimensions to fit page size
-webkit-mask-position : 0% 0%;
mask-position : 50% 50%;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
mask-size : 100% 100%; //Scale both dimensions to fit page size
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
& > p:has(img) {
position : absolute;
@@ -321,23 +322,23 @@ body { counter-reset : page-numbers 0; }
}
.imageMaskCenter {
&1 { --wc : url("/assets/waterColorMasks/center/0001.webp"); }
&2 { --wc : url("/assets/waterColorMasks/center/0002.webp"); }
&3 { --wc : url("/assets/waterColorMasks/center/0003.webp"); }
&4 { --wc : url("/assets/waterColorMasks/center/0004.webp"); }
&5 { --wc : url("/assets/waterColorMasks/center/0005.webp"); }
&6 { --wc : url("/assets/waterColorMasks/center/0006.webp"); }
&7 { --wc : url("/assets/waterColorMasks/center/0007.webp"); }
&8 { --wc : url("/assets/waterColorMasks/center/0008.webp"); }
&9 { --wc : url("/assets/waterColorMasks/center/0009.webp"); }
&10 { --wc : url("/assets/waterColorMasks/center/0010.webp"); }
&11 { --wc : url("/assets/waterColorMasks/center/0011.webp"); }
&12 { --wc : url("/assets/waterColorMasks/center/0012.webp"); }
&13 { --wc : url("/assets/waterColorMasks/center/0013.webp"); }
&14 { --wc : url("/assets/waterColorMasks/center/0014.webp"); }
&15 { --wc : url("/assets/waterColorMasks/center/0015.webp"); }
&16 { --wc : url("/assets/waterColorMasks/center/0016.webp"); }
&special { --wc : url("/assets/waterColorMasks/center/special.webp"); }
&1 { --wc : url('/assets/waterColorMasks/center/0001.webp'); }
&2 { --wc : url('/assets/waterColorMasks/center/0002.webp'); }
&3 { --wc : url('/assets/waterColorMasks/center/0003.webp'); }
&4 { --wc : url('/assets/waterColorMasks/center/0004.webp'); }
&5 { --wc : url('/assets/waterColorMasks/center/0005.webp'); }
&6 { --wc : url('/assets/waterColorMasks/center/0006.webp'); }
&7 { --wc : url('/assets/waterColorMasks/center/0007.webp'); }
&8 { --wc : url('/assets/waterColorMasks/center/0008.webp'); }
&9 { --wc : url('/assets/waterColorMasks/center/0009.webp'); }
&10 { --wc : url('/assets/waterColorMasks/center/0010.webp'); }
&11 { --wc : url('/assets/waterColorMasks/center/0011.webp'); }
&12 { --wc : url('/assets/waterColorMasks/center/0012.webp'); }
&13 { --wc : url('/assets/waterColorMasks/center/0013.webp'); }
&14 { --wc : url('/assets/waterColorMasks/center/0014.webp'); }
&15 { --wc : url('/assets/waterColorMasks/center/0015.webp'); }
&16 { --wc : url('/assets/waterColorMasks/center/0016.webp'); }
&special { --wc : url('/assets/waterColorMasks/center/special.webp'); }
}
@@ -346,15 +347,15 @@ body { counter-reset : page-numbers 0; }
left : calc(-50% + var(--offsetX));
width : 200%;
height : 200%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
-webkit-mask-position : 50% 50%;
mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat;
mask-repeat : no-repeat;
mask-size : 100% 100%; //Scale both dimensions to fit page size
-webkit-mask-position : 50% 50%;
mask-position : 50% 50%;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
mask-size : 100% 100%; //Scale both dimensions to fit page size
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
& > p:has(img) {
bottom : 25%;
left : 25%;
@@ -367,43 +368,43 @@ body { counter-reset : page-numbers 0; }
}
}
.imageMaskCorner {
&1 { --wc : url("/assets/waterColorMasks/corner/0001.webp"); }
&2 { --wc : url("/assets/waterColorMasks/corner/0002.webp"); }
&3 { --wc : url("/assets/waterColorMasks/corner/0003.webp"); }
&4 { --wc : url("/assets/waterColorMasks/corner/0004.webp"); }
&5 { --wc : url("/assets/waterColorMasks/corner/0005.webp"); }
&6 { --wc : url("/assets/waterColorMasks/corner/0006.webp"); }
&7 { --wc : url("/assets/waterColorMasks/corner/0007.webp"); }
&8 { --wc : url("/assets/waterColorMasks/corner/0008.webp"); }
&9 { --wc : url("/assets/waterColorMasks/corner/0009.webp"); }
&10 { --wc : url("/assets/waterColorMasks/corner/0010.webp"); }
&11 { --wc : url("/assets/waterColorMasks/corner/0011.webp"); }
&12 { --wc : url("/assets/waterColorMasks/corner/0012.webp"); }
&13 { --wc : url("/assets/waterColorMasks/corner/0013.webp"); }
&14 { --wc : url("/assets/waterColorMasks/corner/0014.webp"); }
&15 { --wc : url("/assets/waterColorMasks/corner/0015.webp"); }
&16 { --wc : url("/assets/waterColorMasks/corner/0016.webp"); }
&17 { --wc : url("/assets/waterColorMasks/corner/0017.webp"); }
&18 { --wc : url("/assets/waterColorMasks/corner/0018.webp"); }
&19 { --wc : url("/assets/waterColorMasks/corner/0019.webp"); }
&20 { --wc : url("/assets/waterColorMasks/corner/0020.webp"); }
&21 { --wc : url("/assets/waterColorMasks/corner/0021.webp"); }
&22 { --wc : url("/assets/waterColorMasks/corner/0022.webp"); }
&23 { --wc : url("/assets/waterColorMasks/corner/0023.webp"); }
&24 { --wc : url("/assets/waterColorMasks/corner/0024.webp"); }
&25 { --wc : url("/assets/waterColorMasks/corner/0025.webp"); }
&26 { --wc : url("/assets/waterColorMasks/corner/0026.webp"); }
&27 { --wc : url("/assets/waterColorMasks/corner/0027.webp"); }
&28 { --wc : url("/assets/waterColorMasks/corner/0028.webp"); }
&29 { --wc : url("/assets/waterColorMasks/corner/0029.webp"); }
&30 { --wc : url("/assets/waterColorMasks/corner/0030.webp"); }
&31 { --wc : url("/assets/waterColorMasks/corner/0031.webp"); }
&32 { --wc : url("/assets/waterColorMasks/corner/0032.webp"); }
&33 { --wc : url("/assets/waterColorMasks/corner/0033.webp"); }
&34 { --wc : url("/assets/waterColorMasks/corner/0034.webp"); }
&35 { --wc : url("/assets/waterColorMasks/corner/0035.webp"); }
&36 { --wc : url("/assets/waterColorMasks/corner/0036.webp"); }
&37 { --wc : url("/assets/waterColorMasks/corner/0037.webp"); }
&1 { --wc : url('/assets/waterColorMasks/corner/0001.webp'); }
&2 { --wc : url('/assets/waterColorMasks/corner/0002.webp'); }
&3 { --wc : url('/assets/waterColorMasks/corner/0003.webp'); }
&4 { --wc : url('/assets/waterColorMasks/corner/0004.webp'); }
&5 { --wc : url('/assets/waterColorMasks/corner/0005.webp'); }
&6 { --wc : url('/assets/waterColorMasks/corner/0006.webp'); }
&7 { --wc : url('/assets/waterColorMasks/corner/0007.webp'); }
&8 { --wc : url('/assets/waterColorMasks/corner/0008.webp'); }
&9 { --wc : url('/assets/waterColorMasks/corner/0009.webp'); }
&10 { --wc : url('/assets/waterColorMasks/corner/0010.webp'); }
&11 { --wc : url('/assets/waterColorMasks/corner/0011.webp'); }
&12 { --wc : url('/assets/waterColorMasks/corner/0012.webp'); }
&13 { --wc : url('/assets/waterColorMasks/corner/0013.webp'); }
&14 { --wc : url('/assets/waterColorMasks/corner/0014.webp'); }
&15 { --wc : url('/assets/waterColorMasks/corner/0015.webp'); }
&16 { --wc : url('/assets/waterColorMasks/corner/0016.webp'); }
&17 { --wc : url('/assets/waterColorMasks/corner/0017.webp'); }
&18 { --wc : url('/assets/waterColorMasks/corner/0018.webp'); }
&19 { --wc : url('/assets/waterColorMasks/corner/0019.webp'); }
&20 { --wc : url('/assets/waterColorMasks/corner/0020.webp'); }
&21 { --wc : url('/assets/waterColorMasks/corner/0021.webp'); }
&22 { --wc : url('/assets/waterColorMasks/corner/0022.webp'); }
&23 { --wc : url('/assets/waterColorMasks/corner/0023.webp'); }
&24 { --wc : url('/assets/waterColorMasks/corner/0024.webp'); }
&25 { --wc : url('/assets/waterColorMasks/corner/0025.webp'); }
&26 { --wc : url('/assets/waterColorMasks/corner/0026.webp'); }
&27 { --wc : url('/assets/waterColorMasks/corner/0027.webp'); }
&28 { --wc : url('/assets/waterColorMasks/corner/0028.webp'); }
&29 { --wc : url('/assets/waterColorMasks/corner/0029.webp'); }
&30 { --wc : url('/assets/waterColorMasks/corner/0030.webp'); }
&31 { --wc : url('/assets/waterColorMasks/corner/0031.webp'); }
&32 { --wc : url('/assets/waterColorMasks/corner/0032.webp'); }
&33 { --wc : url('/assets/waterColorMasks/corner/0033.webp'); }
&34 { --wc : url('/assets/waterColorMasks/corner/0034.webp'); }
&35 { --wc : url('/assets/waterColorMasks/corner/0035.webp'); }
&36 { --wc : url('/assets/waterColorMasks/corner/0036.webp'); }
&37 { --wc : url('/assets/waterColorMasks/corner/0037.webp'); }
}
}
@@ -437,6 +438,11 @@ body { counter-reset : page-numbers 0; }
margin-bottom : 1em;
& + * { margin-top : 0; }
}
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
//*****************************
@@ -461,8 +467,8 @@ body { counter-reset : page-numbers 0; }
height : 1.5cm;
margin : 0 auto;
background-color : black;
-webkit-mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat;
mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat;
-webkit-mask : url('/assets/naturalCritLogoWhite.svg') center / contain no-repeat;
mask : url('/assets/naturalCritLogoWhite.svg') center / contain no-repeat;
}
.homebreweryIcon.red { background-color : red; }
.homebreweryIcon.gold { background-image : linear-gradient(to top left, brown 22.5%, gold 40%, white 60%, gold 67.5%, brown 82.5%); }
@@ -486,12 +492,122 @@ body { counter-reset : page-numbers 0; }
.pageNumber { left : 30px; }
}
.resetCounting {
counter-set : page-numbers 1;
}
.resetCounting { counter-set : page-numbers 1; }
&:not(:has(.skipCounting)) {
counter-increment : page-numbers;
}
&:not(:has(.skipCounting)) { counter-increment : page-numbers; }
}
// *****************************
// * INDEX
// *****************************/
.page {
.index {
ul ul { margin : 0; }
ul {
padding-left : 0;
text-indent : 0;
list-style-type : none;
}
& > ul > li {
padding-left : 1.5em;
text-indent : -1.5em;
}
}
}
// *****************************
// * TABLE OF CONTENTS
// *****************************/
// Default Exclusions
// Anything not exlcuded is included, default Headers are H1, H2, and H3.
h4,
h5,
h6,
.page:has(.frontCover),
.page:has(.backCover),
.page:has(.insideCover),
.noToC,
.toc { --TOC : exclude; }
.tocDepthH2 :is(h1, h2) {--TOC : include; }
.tocDepthH3 :is(h1, h2, h3) {--TOC : include; }
.tocDepthH4 :is(h1, h2, h3, h4) {--TOC : include; }
.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC : include; }
.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC : include; }
.tocIncludeH1 h1 {--TOC : include; }
.tocIncludeH2 h2 {--TOC : include; }
.tocIncludeH3 h3 {--TOC : include; }
.tocIncludeH4 h4 {--TOC : include; }
.tocIncludeH5 h5 {--TOC : include; }
.tocIncludeH6 h6 {--TOC : include; }
.page {
&:has(.toc)::after { display : none; }
.toc {
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
h1 {
margin-bottom : 0.3cm;
text-align : center;
}
a {
display : inline;
color : inherit;
text-decoration : none;
&:hover { text-decoration : underline; }
}
h4 {
margin-top : 0.2cm;
line-height : 0.4cm;
& + ul li { line-height : 1.2em; }
}
ul {
padding-left : 0;
margin-top : 0;
list-style-type : none;
a {
display : flex;
flex-flow : row nowrap;
justify-content : space-between;
width : 100%;
}
li + li h3 {
margin-top : 0.26cm;
line-height : 1em;
}
h3 span:first-child::after { border : none; }
span {
display : contents;
&:first-child::after {
bottom : 0.08cm;
flex : 1;
margin-right : 0.16cm;
margin-bottom : 0.08cm;
margin-left : 0.08cm; /* Spacing before dot leaders */
content : '';
border-bottom : 0.05cm dotted #000000;
}
&:last-child {
display : inline-block;
align-self : flex-end;
font-size : 0.34cm;
font-weight : normal;
}
}
ul { /* List indent */
margin-left : 1em;
}
}
&.wide {
.useColumns(0.96, @fillMode: balance);
}
}
.toc.wide li { break-inside : auto; }
}

View File

@@ -1,4 +1,4 @@
/* eslint-disable max-lines */
module.exports = [
];

View File

@@ -11,47 +11,35 @@
--HB_Color_WatercolorStain : #BBAD82; // Light brown
}
.useSansSerif(){
font-family : PermanentMarker;
.useSansSerif() {
font-family : 'PermanentMarker';
font-size : 0.3cm;
line-height : 1.2em;
color : var(--HB_Color_Text2);
p,dl,ul,ol {
line-height : 1.2em;
}
ul, ol {
padding-left : 1em;
}
em{
font-style : italic;
}
strong{
font-weight : 800;
p,dl,ul,ol { line-height : 1.2em; }
ul, ol { padding-left : 1em; }
em { font-style : italic; }
strong {
font-size : 1.1em;
font-weight : 800;
}
h5 + * {
margin-top : 0.1cm;
}
}
.useColumns(@multiplier : 1, @fillMode: balance){
column-gap : 0.5cm;
h5 + * { margin-top : 0.1cm; }
}
.useColumns(@multiplier : 1, @fillMode: balance) { column-gap : 0.5cm; }
.page{
background-size : 200% 100%;
background-repeat : no-repeat;
filter : drop-shadow(1px 4px 14px black);
background-image : url(/assets/Journal/Background1.webp);
.page {
padding : 2.1cm 1.9cm 1.7cm 3.8cm;
&:nth-of-type(2n + 1) {
background-position : left;
}
background-image : url('/assets/Journal/Background1.webp');
background-repeat : no-repeat;
background-size : 200% 100%;
filter : drop-shadow(1px 4px 14px black);
&:nth-of-type(2n + 1) { background-position : left; }
&:nth-of-type(2n) {
background-position : right;
padding : 2.1cm 3.9cm 1.7cm 1.8cm;
background-position : right;
}
&:nth-of-type(2) {
background-image : url(/assets/Journal/Background2.webp); //Only first page should show ribbon
background-image : url('/assets/Journal/Background2.webp'); //Only first page should show ribbon
}
& .columnWrapper {
@@ -59,167 +47,137 @@
}
}
//*****************************
// * BASE
//*****************************
// * BASE
// *****************************/
.page{
color : var(--HB_Color_Text);
font-family : ReenieBeanie;
.page {
font-family : 'ReenieBeanie';
font-size : 0.53cm;
line-height : 0.8em;
p + * {
margin-top : 0.325cm;
}
p + p{
margin-top : 0;
}
ul{
margin-bottom : 0.8em;
}
ol{
margin-bottom : 0.8em;
}
em{
color : var(--HB_Color_Text);
p + * { margin-top : 0.325cm; }
p + p { margin-top : 0; }
ul { margin-bottom : 0.8em; }
ol { margin-bottom : 0.8em; }
em {
font-style : unset;
text-decoration : underline;
font-style : unset;
}
del{
text-decoration-style: double;
}
del { text-decoration-style : double; }
//Indents after p or lists
p+p, ul+p, ol+p{
text-indent : 1em;
}
p + p, ul + p, ol + p { text-indent : 1em; }
//*****************************
// * HEADERS
// *****************************/
h1,h2,h3,h4,h5{
font-family : FrederickaTheGreat;
h1,h2,h3,h4,h5 {
font-family : 'FrederickaTheGreat';
font-weight : unset;
color : var(--HB_Color_HeaderText);
}
h1{
h1 {
margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE
font-size : 0.89cm;
line-height : 1em;
font-variant : small-caps;
&+p::first-letter{
line-height : 1em;
& + p::first-letter {
float : left;
font-family : FrederickaTheGreat;
line-height : 1em;
font-size : 1.9em;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-bottom : -20px;
margin-left : -40px;
margin-right : 0.1em;
padding-top : 0.3em;
padding-bottom : 2px;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-right : 0.1em;
margin-bottom : -20px;
margin-left : -40px;
font-family : 'FrederickaTheGreat';
font-size : 1.9em;
line-height : 1em;
}
&+p::first-line{
font-variant : small-caps;
}
& + p::first-line { font-variant : small-caps; }
}
h2{
h2 {
font-size : 0.62cm;
line-height : 0.988em; //Font is misaligned. Shift up slightly
}
h3{
h3 {
margin-left : -0.9em;
font-size : 0.575cm;
line-height : 0.995em; //Font is misaligned. Shift up slightly
margin-left : -0.9em;
}
h4{
h4 {
padding-bottom : 5px;
font-size : 0.55cm;
line-height : 0.971em; //Font is misaligned. Shift up slightly
color : var(--HB_Color_Text);
padding-bottom : 5px;
transform:rotate(0deg);
&:nth-of-type(2n) {
transform:rotate(1deg);
}
&:nth-of-type(3n) {
transform:rotate(-1.5deg);
}
transform : rotate(0deg);
&:nth-of-type(2n) { transform : rotate(1deg); }
&:nth-of-type(3n) { transform : rotate(-1.5deg); }
}
h5{
font-family : PermanentMarker;
h5 {
font-family : 'PermanentMarker';
font-size : 0.4cm;
color : var(--HB_Color_Text2);
font-weight : bold;
line-height : 0.951em; //Font is misaligned. Shift up slightly
& + * {
margin-top : 0.2cm;
}
color : var(--HB_Color_Text2);
& + * { margin-top : 0.2cm; }
}
//*****************************
// * TABLE
// *****************************/
table{
table {
.useSansSerif();
& + * {
margin-top : 0.325cm;
}
thead{
th{
vertical-align : bottom;
& + * { margin-top : 0.325cm; }
thead {
th {
padding : 0.14em 0;
vertical-align : bottom;
}
}
tbody{
tr{
td{
padding : 0.14em 0;
}
&:nth-child(odd){
background-image : linear-gradient(to left, #41212100, #41212122, #41212100);
}
tbody {
tr {
td { padding : 0.14em 0; }
&:nth-child(odd) { background-image : linear-gradient(to left, #41212100, #41212122, #41212100); }
}
}
}
//*****************************
// * NOTE
// *****************************/
.note{
.note {
.useSansSerif();
padding : 0.2cm;
background-image : url('/assets/Journal/HashMarks.png'),
linear-gradient(to bottom right, #FF000000, #A36A4E14, #41212100);
background-repeat : no-repeat;
background-position : center;
background-size : 120% 120%;
border-style : solid;
border-width : 1px;
border-image-source : url(/assets/Journal/Border1.png);
border-image-source : url('/assets/Journal/Border1.png');
border-image-slice : 18 18 18 18;
border-image-width : 6px 6px 6px 6px;
border-image-outset : 5px 5px 5px 5px;
border-image-repeat : stretch stretch;
background-image : url(/assets/Journal/HashMarks.png),
linear-gradient(to bottom right, #ff000000, #a36a4e14, #41212100);
background-size : 120% 120%;
background-repeat : no-repeat;
background-position : center;
padding : 0.2cm;
:where(&) {
margin-top : 9px; //Prevent top border getting cut off on colbreak
}
& + * {
margin-top : 0.45cm;
}
h5 {
font-size : 0.375cm;
}
p{
padding-bottom : 0px;
}
:last-child {
margin-bottom : 0;
}
& + * { margin-top : 0.45cm; }
h5 { font-size : 0.375cm; }
p { padding-bottom : 0px; }
:last-child { margin-bottom : 0; }
}
//************************************
// * DESCRIPTIVE TEXT BOX
// ************************************/
* + .descriptive {
margin-top : 0.6cm;
}
.descriptive{
* + .descriptive { margin-top : 0.6cm; }
.descriptive {
.useSansSerif();
padding : 0.2cm;
background-image : url('/assets/Journal/HashMarks.png'),
linear-gradient(to bottom right, #FF000000, #41212114, #41212100);
background-repeat : no-repeat;
background-position : center;
background-size : 120% 120%;
border-style : solid;
border-width : 1px;
border-image-source : url('/assets/Journal/Border2.png');
@@ -227,27 +185,13 @@
border-image-width : 20px;
border-image-outset : 16px 20px 16px 20px;
border-image-repeat : stretch stretch;
background-image : url(/assets/Journal/HashMarks.png),
linear-gradient(to bottom right, #ff000000, #41212114, #41212100);
background-size : 120% 120%;
background-repeat : no-repeat;
background-position : center;
padding : 0.2cm;
:where(&) {
margin-top : 4px; //Prevent top border getting cut off on colbreak
}
& + * {
margin-top : 0.45cm;
}
h5 {
font-size : 0.375cm;
}
p{
padding-bottom : 0px;
}
:last-child {
margin-bottom : 0;
}
& + * { margin-top : 0.45cm; }
h5 { font-size : 0.375cm; }
p { padding-bottom : 0px; }
:last-child { margin-bottom : 0; }
}
//*****************************
// * Images Snippets
@@ -257,25 +201,23 @@
.artist {
position : absolute;
width : auto;
text-align : center;
font-family : WalterTurncoat;
font-family : 'WalterTurncoat';
font-size : 0.27cm;
color : var(--HB_Color_CaptionText);
text-align : center;
p, p + p {
margin : unset;
text-indent : unset;
line-height : 1em;
text-indent : unset;
}
h5 {
h5 {
font-family : 'WalterTurncoat';
font-size : 1.3em;
font-family : WalterTurncoat;
}
a{
a {
color : inherit;
text-decoration : unset;
&:hover {
text-decoration : underline;
}
&:hover { text-decoration : underline; }
}
}
@@ -285,6 +227,10 @@
.monster {
.useSansSerif();
&.frame {
padding : 0.2cm;
background-image : url('/assets/Journal/HashMarks.png'),
linear-gradient(to bottom right, #FF000000, #A36A4E14, #41212100);
background-size : 100%;
border-style : solid;
border-width : 7px 6px;
border-image-source : url('/assets/Journal/Border3.png');
@@ -292,33 +238,29 @@
border-image-width : 15px 20px 15px 20px;
border-image-outset : 12px 12px 12px 12px;
border-image-repeat : stretch round;
background-image : url('/assets/Journal/HashMarks.png'),
linear-gradient(to bottom right, #ff000000, #a36a4e14, #41212100);
background-blend-mode : screen multiply;
background-size : 100%;
padding : 0.2cm;
}
color: var(--HB_Color_Text);
position : relative;
padding : 0px;
margin-bottom : 0.325cm;
color : var(--HB_Color_Text);
//Headers
h2{
h2 {
margin : 0;
font-size : 0.62cm;
line-height : 1em;
margin : 0;
&+p {
& + p {
margin-bottom : 0; //Monster size and type subtext
}
}
h3{
h3 {
padding-bottom : 0.05cm;
margin-left : 0;
font-variant : small-caps;
padding-bottom : 0.05cm;
}
hr{
hr {
visibility : visible;
height : 6px;
margin : 0.12cm 0cm;
@@ -330,24 +272,18 @@
}
// Monster Ability table
hr + table:first-of-type{
hr + table:first-of-type {
margin : 0;
column-span : none;
background-image : none;
color : inherit;
background-image : none;
border-style : none;
border-image : none;
color : inherit;
tr {
background-image : none;
}
td,th {
padding: 0px;
}
column-span : none;
tr { background-image : none; }
td,th { padding : 0px; }
}
:last-child {
margin-bottom : 0;
}
:last-child { margin-bottom : 0; }
strong, em {
font-style : normal;
@@ -356,29 +292,27 @@
}
//Full Width
.monster.wide{
.monster.wide {
.useColumns(0.96, @fillMode: balance);
}
//*****************************
// * FOOTER
// *****************************/
&:nth-child(odd){
.pageNumber{
left : 3cm;
}
.footnote{
&:nth-child(odd) {
.pageNumber { left : 3cm; }
.footnote {
left : 4.5cm;
text-align : left;
}
}
.pageNumber{
font-family : FrederickaTheGreat;
.pageNumber {
right : 3cm;
bottom : 1.25cm;
font-family : 'FrederickaTheGreat';
color : var(--HB_Color_HeaderText);
}
.footnote{
.footnote {
position : absolute;
right : 4.5cm;
bottom : 1.25cm;
@@ -391,154 +325,134 @@
//************************************
// * CODE BLOCKS
// ************************************/
code{
font-size : 0.3cm;
code {
padding : 0px 4px;
color : var(--HB_Color_Text);
font-size : 0.3cm;
vertical-align : middle;
background-color : #faf7ea;
color : var(--HB_Color_Text);
background-color : #FAF7EA;
border-radius : 4px;
}
pre code{
pre code {
padding : 0.15cm;
margin-bottom : 2px;
border-style : solid;
border-width : 1px;
border-radius : 12px;
border-image : @codeBorderImage 26 stretch;
border-image-width : 10px;
border-image-outset : 2px;
border-radius : 12px;
margin-bottom : 2px;
padding : 0.15cm;
.page :where(&) {
margin-top : 2px; //Prevent top border getting cut off on colbreak
}
& + * {
margin-top : 0.325cm;
}
& + * { margin-top : 0.325cm; }
}
//*****************************
// * EXTRAS
// *****************************/
hr{
hr {
visibility : hidden;
border : none;
margin : 0px;
border : none;
}
//Text indent right after table
table+p{
text-indent : 1em;
}
table + p { text-indent : 1em; }
a, a:visited, a:hover {
color: var(--HB_Color_Text);
transition:all 1s ease;
}
a:hover {
color:red;
color : var(--HB_Color_Text);
transition : all 1s ease;
}
a:hover { color : red; }
}
//*****************************
// * SPELL LIST
// *****************************/
.page .spellList{
.page .spellList {
.useSansSerif();
font-family : PermanentMarker;
font-family : 'PermanentMarker';
column-count : 2;
ul+h5{
margin-top : 15px;
}
ul{
margin-bottom : 0.5em;
ul + h5 { margin-top : 15px; }
ul {
padding-left : 1em;
margin-bottom : 0.5em;
text-indent : -1em;
list-style-type : none;
break-inside : auto;
-webkit-column-break-inside : auto;
page-break-inside : auto;
break-inside : auto;
}
&.wide{
column-count : 4;
}
&.wide { column-count : 4; }
}
//*****************************
// * CLASS TABLE
// *****************************/
.page .classTable{
th[colspan]:not([rowspan]) {
white-space : nowrap;
}
h5 + table{
margin-top : 0.2cm;
}
.page .classTable {
th[colspan]:not([rowspan]) { white-space : nowrap; }
h5 + table { margin-top : 0.2cm; }
}
//*****************************
// * TABLE OF CONTENTS
// *****************************/
.page .toc{
.page .toc {
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
h1 {
text-align : center;
margin-bottom : 0.3cm;
text-align : center;
}
a{
a {
display : inline;
color : inherit;
text-decoration : none;
&:hover{
text-decoration : underline;
}
&:hover { text-decoration : underline; }
}
h4 {
margin-top : 0.2cm;
line-height : 0.4cm;
& + ul li {
line-height: 1.2em;
}
& + ul li { line-height : 1.2em; }
}
ul{
ul {
padding-left : 0;
list-style-type : none;
li + li h3 {
margin-top : 0.26cm;
line-height : 1em
}
h3 span:first-child::after {
border : none;
line-height : 1em;
}
h3 span:first-child::after { border : none; }
span {
display : table-cell;
&:first-child {
position : relative;
overflow : hidden;
position : relative;
overflow : hidden;
&::after {
content : "";
position : absolute;
bottom : 0.08cm;
margin-left : 0.06cm; /* Spacing before dot leaders */
width : 100%;
border-bottom : 0.05cm dotted #000;
margin-left : 0.06cm; /* Spacing before dot leaders */
content : '';
border-bottom : 0.05cm dotted #000000;
}
}
&:last-child {
font-family : ReenieBeanie;
font-size : 0.34cm;
font-weight : normal;
color : black;
text-align : right;
vertical-align : bottom; /* Keep page number bottom-aligned */
width : 1%;
padding-left : 0.06cm; /* Spacing after dot leaders */
/*white-space : nowrap; /* Uncomment if needed */
font-family : 'ReenieBeanie';
font-size : 0.34cm;
font-weight : normal;
vertical-align : bottom; /* Keep page number bottom-aligned */
color : black;
text-align : right;
/* white-space : nowrap; /* Uncomment if needed */
}
}
ul { /*List indent*/
ul { /* List indent */
margin-left : 1em;
}
}
&.wide{
&.wide {
.useColumns(0.96, @fillMode: balance);
}
}
@@ -546,6 +460,4 @@
//*****************************
// * WIDE
// *****************************/
.page .wide {
margin-bottom : 0.45cm;
}
.page .wide { margin-bottom : 0.45cm; }

View File

@@ -1,88 +1,83 @@
.editor .codeEditor .CodeMirror {
// Themes with dark backgrounds
&.cm-s-3024-night,
&.cm-s-abbott,
&.cm-s-abcdef,
&.cm-s-ambiance,
&.cm-s-ayu-dark,
&.cm-s-ayu-mirage,
&.cm-s-base16-dark,
&.cm-s-bespin,
&.cm-s-blackboard,
&.cm-s-cobalt,
&.cm-s-colorforth,
&.cm-s-darcula,
&.cm-s-dracula,
&.cm-s-duotone-dark,
&.cm-s-erlang-dark,
&.cm-s-gruvbox-dark,
&.cm-s-hopscotch,
&.cm-s-icecoder,
&.cm-s-isotope,
&.cm-s-lesser-dark,
&.cm-s-liquibyte,
&.cm-s-lucario,
&.cm-s-material,
&.cm-s-material-darker,
&.cm-s-material-ocean,
&.cm-s-material-palenight,
&.cm-s-mbo,
&.cm-s-midnight,
&.cm-s-monokai,
&.cm-s-moxer,
&.cm-s-night,
&.cm-s-nord,
&.cm-s-oceanic-next,
&.cm-s-panda-syntax,
&.cm-s-paraiso-dark,
&.cm-s-pastel-on-dark,
&.cm-s-railscasts,
&.cm-s-rubyblue,
&.cm-s-seti,
&.cm-s-shadowfox,
&.cm-s-the-matrix,
&.cm-s-tomorrow-night-bright,
&.cm-s-tomorrow-night-eighties,
&.cm-s-twilight,
&.cm-s-vibrant-ink,
&.cm-s-xq-dark,
&.cm-s-yonce,
&.cm-s-zenburn
{
.CodeMirror-code {
.block:not(.cm-comment) {
color: magenta;
}
.columnSplit {
color: black;
background-color: rgba(35,153,153,0.5);
}
.pageLine {
background-color: rgba(255,255,255,0.5);
& ~ pre.CodeMirror-line {
color: black;
}
}
}
}
// Themes with light backgrounds
&.cm-s-default,
&.cm-s-3024-day,
&.cm-s-ambiance-mobile,
&.cm-s-base16-light,
&.cm-s-duotone-light,
&.cm-s-eclipse,
&.cm-s-elegant,
&.cm-s-juejin,
&.cm-s-neat,
&.cm-s-neo,
&.cm-s-paraiso-lightm
&.cm-s-solarized,
&.cm-s-ssms,
&.cm-s-ttcn,
&.cm-s-xq-light,
&.cm-s-yeti {
// Future styling for themes with light backgrounds
--dummyVar: 'currently unused';
}
// Themes with dark backgrounds
&.cm-s-3024-night,
&.cm-s-abbott,
&.cm-s-abcdef,
&.cm-s-ambiance,
&.cm-s-ayu-dark,
&.cm-s-ayu-mirage,
&.cm-s-base16-dark,
&.cm-s-bespin,
&.cm-s-blackboard,
&.cm-s-cobalt,
&.cm-s-colorforth,
&.cm-s-darcula,
&.cm-s-dracula,
&.cm-s-duotone-dark,
&.cm-s-erlang-dark,
&.cm-s-gruvbox-dark,
&.cm-s-hopscotch,
&.cm-s-icecoder,
&.cm-s-isotope,
&.cm-s-lesser-dark,
&.cm-s-liquibyte,
&.cm-s-lucario,
&.cm-s-material,
&.cm-s-material-darker,
&.cm-s-material-ocean,
&.cm-s-material-palenight,
&.cm-s-mbo,
&.cm-s-midnight,
&.cm-s-monokai,
&.cm-s-moxer,
&.cm-s-night,
&.cm-s-nord,
&.cm-s-oceanic-next,
&.cm-s-panda-syntax,
&.cm-s-paraiso-dark,
&.cm-s-pastel-on-dark,
&.cm-s-railscasts,
&.cm-s-rubyblue,
&.cm-s-seti,
&.cm-s-shadowfox,
&.cm-s-the-matrix,
&.cm-s-tomorrow-night-bright,
&.cm-s-tomorrow-night-eighties,
&.cm-s-twilight,
&.cm-s-vibrant-ink,
&.cm-s-xq-dark,
&.cm-s-yonce,
&.cm-s-zenburn {
.CodeMirror-code {
.block:not(.cm-comment) { color : magenta; }
.columnSplit {
color : black;
background-color : rgba(35,153,153,0.5);
}
.pageLine {
background-color : rgba(255,255,255,0.5);
& ~ pre.CodeMirror-line { color : black; }
}
}
}
// Themes with light backgrounds
&.cm-s-default,
&.cm-s-3024-day,
&.cm-s-ambiance-mobile,
&.cm-s-base16-light,
&.cm-s-duotone-light,
&.cm-s-eclipse,
&.cm-s-elegant,
&.cm-s-juejin,
&.cm-s-neat,
&.cm-s-neo,
&.cm-s-paraiso-lightm
&.cm-s-solarized,
&.cm-s-ssms,
&.cm-s-ttcn,
&.cm-s-xq-light,
&.cm-s-yeti {
// Future styling for themes with light backgrounds
--dummyVar : 'currently unused';
}
}

View File

@@ -1,61 +1,61 @@
/* Main Font, serif */
@font-face {
font-family: BookSanity;
src: url('../../../fonts/5e legacy/Bookinsanity.woff2');
font-weight: normal;
font-style: normal;
font-family : "BookSanity";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e legacy/Bookinsanity.woff2');
}
@font-face {
font-family: BookSanity;
src: url('../../../fonts/5e legacy/Bookinsanity Bold.woff2');
font-weight: bold;
font-style: normal;
font-family : "BookSanity";
font-style : normal;
font-weight : bold;
src : url('../../../fonts/5e legacy/Bookinsanity Bold.woff2');
}
@font-face {
font-family: BookSanity;
src: url('../../../fonts/5e legacy/Bookinsanity Italic.woff2');
font-weight: normal;
font-style: italic;
font-family : "BookSanity";
font-style : italic;
font-weight : normal;
src : url('../../../fonts/5e legacy/Bookinsanity Italic.woff2');
}
@font-face {
font-family: BookSanity;
src: url('../../../fonts/5e legacy/Bookinsanity Bold Italic.woff2');
font-weight: bold;
font-style: italic;
font-family : "BookSanity";
font-style : italic;
font-weight : bold;
src : url('../../../fonts/5e legacy/Bookinsanity Bold Italic.woff2');
}
/* Notes and Tables, sans-serif */
@font-face {
font-family: ScalySans;
src: url('../../../fonts/5e legacy/Scaly Sans.woff2');
font-weight: normal;
font-style: normal;
font-family : "ScalySans";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e legacy/Scaly Sans.woff2');
}
@font-face {
font-family: ScalySansSmallCaps;
src: url('../../../fonts/5e legacy/Scaly Sans Caps.woff2');
font-weight: normal;
font-style: normal;
font-family : "ScalySansSmallCaps";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e legacy/Scaly Sans Caps.woff2');
}
@font-face {
font-family: WalterTurncoat;
src: url('../../../fonts/5e legacy/WalterTurncoat-Regular.woff2');
font-weight: normal;
font-style: normal;
font-family : "WalterTurncoat";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e legacy/WalterTurncoat-Regular.woff2');
}
/* Headers */
@font-face {
font-family: MrJeeves;
src: url('../../../fonts/5e legacy/Mr Eaves Small Caps.woff2');
font-weight: normal;
font-style: normal;
font-family : "MrJeeves";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e legacy/Mr Eaves Small Caps.woff2');
}
/* Fancy Drop Cap */
@font-face {
font-family: Solberry;
src: url('../../../fonts/5e legacy/Solbera Imitation.woff2');
font-weight: normal;
font-style: normal;
font-family : "Solberry";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e legacy/Solbera Imitation.woff2');
}

View File

@@ -1,143 +1,143 @@
/* Main Font, serif */
@font-face {
font-family: BookInsanityRemake;
src: url('../../../fonts/5e/Bookinsanity.woff2');
font-weight: normal;
font-style: normal;
font-family : "BookInsanityRemake";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/Bookinsanity.woff2');
}
@font-face {
font-family: BookInsanityRemake;
src: url('../../../fonts/5e/Bookinsanity Bold.woff2');
font-weight: bold;
font-style: normal;
font-family : "BookInsanityRemake";
font-style : normal;
font-weight : bold;
src : url('../../../fonts/5e/Bookinsanity Bold.woff2');
}
@font-face {
font-family: BookInsanityRemake;
src: url('../../../fonts/5e/Bookinsanity Italic.woff2');
font-weight: normal;
font-style: italic;
font-family : "BookInsanityRemake";
font-style : italic;
font-weight : normal;
src : url('../../../fonts/5e/Bookinsanity Italic.woff2');
}
@font-face {
font-family: BookInsanityRemake;
src: url('../../../fonts/5e/Bookinsanity Bold Italic.woff2');
font-weight: bold;
font-style: italic;
font-family : "BookInsanityRemake";
font-style : italic;
font-weight : bold;
src : url('../../../fonts/5e/Bookinsanity Bold Italic.woff2');
}
/* Notes and Tables, sans-serif */
@font-face {
font-family: ScalySansRemake;
src: url('../../../fonts/5e/Scaly Sans.woff2');
font-weight: normal;
font-style: normal;
font-family : "ScalySansRemake";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/Scaly Sans.woff2');
}
@font-face {
font-family: ScalySansRemake;
src: url('../../../fonts/5e/Scaly Sans Bold.woff2');
font-weight: bold;
font-style: normal;
font-family : "ScalySansRemake";
font-style : normal;
font-weight : bold;
src : url('../../../fonts/5e/Scaly Sans Bold.woff2');
}
@font-face {
font-family: ScalySansRemake;
src: url('../../../fonts/5e/Scaly Sans Italic.woff2');
font-weight: normal;
font-style: italic;
font-family : "ScalySansRemake";
font-style : italic;
font-weight : normal;
src : url('../../../fonts/5e/Scaly Sans Italic.woff2');
}
@font-face {
font-family: ScalySansRemake;
src: url('../../../fonts/5e/Scaly Sans Bold Italic.woff2');
font-weight: bold;
font-style: italic;
font-family : "ScalySansRemake";
font-style : italic;
font-weight : bold;
src : url('../../../fonts/5e/Scaly Sans Bold Italic.woff2');
}
@font-face {
font-family: ScalySansSmallCapsRemake;
src: url('../../../fonts/5e/Scaly Sans Caps.woff2');
font-weight: normal;
font-style: normal;
font-family : "ScalySansSmallCapsRemake";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/Scaly Sans Caps.woff2');
}
@font-face {
font-family: WalterTurncoat;
src: url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
font-weight: normal;
font-style: normal;
font-family : "WalterTurncoat";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
}
/* Headers */
@font-face {
font-family: MrEavesRemake;
src: url('../../../fonts/5e/Mr Eaves Small Caps.woff2');
font-weight: normal;
font-style: normal;
font-family : "MrEavesRemake";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/Mr Eaves Small Caps.woff2');
}
/* Fancy Drop Cap */
@font-face {
font-family: SolberaImitationRemake; //Tweaked 5e version
src: url('../../../fonts/5e/Solbera Imitation Tweak.woff2');
font-weight: 100 1000;
font-style: normal;
font-style: italic;
font-family : "SolberaImitationRemake"; //Tweaked 5e version
font-style : normal;
font-style : italic;
font-weight : 100 1000;
src : url('../../../fonts/5e/Solbera Imitation Tweak.woff2');
}
/* Cover Page */
@font-face {
font-family: NodestoCapsCondensed;
src: url('../../../fonts/5e/Nodesto Caps Condensed.woff2');
font-weight: normal;
font-style: normal;
font-family : "NodestoCapsCondensed";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/Nodesto Caps Condensed.woff2');
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2');
font-weight: bold;
font-style: normal;
font-family : "NodestoCapsCondensed";
font-style : normal;
font-weight : bold;
src : url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2');
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2');
font-weight: normal;
font-style: italic;
font-family : "NodestoCapsCondensed";
font-style : italic;
font-weight : normal;
src : url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2');
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
font-weight: bold;
font-style: italic;
font-family : "NodestoCapsCondensed";
font-style : italic;
font-weight : bold;
src : url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
}
@font-face {
font-family: NodestoCapsWide;
src: url('../../../fonts/5e/Nodesto Caps Wide.woff2');
font-weight: normal;
font-style: normal
font-family : "NodestoCapsWide";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/Nodesto Caps Wide.woff2');
}
@font-face {
font-family: Overpass;
src: url('../../../fonts/5e/Overpass Medium.woff2');
font-weight: 500;
font-style: normal;
font-family : "Overpass";
font-style : normal;
font-weight : 500;
src : url('../../../fonts/5e/Overpass Medium.woff2');
}
@font-face {
font-family: Davek;
src: url('../../../fonts/5e/Davek.woff2');
font-weight: 500;
font-style: normal;
font-family : "Davek";
font-style : normal;
font-weight : 500;
src : url('../../../fonts/5e/Davek.woff2');
}
@font-face {
font-family: Iokharic;
src: url('../../../fonts/5e/Iokharic.woff2');
font-weight: 500;
font-style: normal;
font-family : "Iokharic";
font-style : normal;
font-weight : 500;
src : url('../../../fonts/5e/Iokharic.woff2');
}
@font-face {
font-family: Rellanic;
src: url('../../../fonts/5e/Rellanic.woff2');
font-weight: 500;
font-style: normal;
font-family : "Rellanic";
font-style : normal;
font-weight : 500;
src : url('../../../fonts/5e/Rellanic.woff2');
}

View File

@@ -18,29 +18,29 @@ License:
*/
@font-face {
font-family: Pagella;
src: url('../../../fonts/Blank/texgyrepagella-regular.woff2');
font-weight: normal;
font-style: normal;
font-family : "Pagella";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/Blank/texgyrepagella-regular.woff2');
}
@font-face {
font-family: Pagella;
src: url('../../../fonts/Blank/texgyrepagella-bold.woff2');
font-weight: bold;
font-style: normal;
font-family : "Pagella";
font-style : normal;
font-weight : bold;
src : url('../../../fonts/Blank/texgyrepagella-bold.woff2');
}
@font-face {
font-family: Pagella;
src: url('../../../fonts/Blank/texgyrepagella-italic.woff2');
font-weight: normal;
font-style: italic;
font-family : "Pagella";
font-style : italic;
font-weight : normal;
src : url('../../../fonts/Blank/texgyrepagella-italic.woff2');
}
@font-face {
font-family: Pagella;
src: url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2');
font-weight: bold;
font-style: italic;
font-family : "Pagella";
font-style : italic;
font-weight : bold;
src : url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2');
}

View File

@@ -1,58 +1,58 @@
/* Main Font, serif */
@font-face {
font-family: ReenieBeanie;
src: url('../../../fonts/Journal/ReenieBeanie-Regular.woff2');
font-weight: normal;
font-style: normal;
font-family : "ReenieBeanie";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/Journal/ReenieBeanie-Regular.woff2');
}
/* Notes and Tables, sans-serif */
@font-face {
font-family: PermanentMarker;
src: url('../../../fonts/Journal/PermanentMarker-Regular.woff2');
font-weight: normal;
font-style: normal;
font-family : "PermanentMarker";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/Journal/PermanentMarker-Regular.woff2');
}
@font-face {
font-family: WalterTurncoat;
src: url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
font-weight: normal;
font-style: normal;
font-family : "WalterTurncoat";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
}
/* Headers */
@font-face {
font-family: FrederickaTheGreat;
src: url('../../../fonts/Journal/FrederickaTheGreat-Regular.woff2');
font-weight: normal;
font-style: normal;
font-family : "FrederickaTheGreat";
font-style : normal;
font-weight : normal;
src : url('../../../fonts/Journal/FrederickaTheGreat-Regular.woff2');
}
/* Cover Page */
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed.woff2');
font-weight: normal;
font-style: normal;
font-family : "NodestoCapsCondensed";
font-style : normal;
font-weight : normal;
src : url('../fonts/5e/Nodesto Caps Condensed.woff2');
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Bold.woff2');
font-weight: bold;
font-style: normal;
font-family : "NodestoCapsCondensed";
font-style : normal;
font-weight : bold;
src : url('../fonts/5e/Nodesto Caps Condensed Bold.woff2');
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Italic.woff2');
font-weight: normal;
font-style: italic;
font-family : "NodestoCapsCondensed";
font-style : italic;
font-weight : normal;
src : url('../fonts/5e/Nodesto Caps Condensed Italic.woff2');
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
font-weight: bold;
font-style: italic;
font-family : "NodestoCapsCondensed";
font-style : italic;
font-weight : bold;
src : url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
}

View File

@@ -13,8 +13,8 @@
font-weight : normal;
font-variant : normal;
line-height : 1;
text-decoration : inherit;
text-transform : none;
text-decoration : inherit;
text-rendering : optimizeLegibility;
/* Better Font Rendering =========== */

View File

@@ -1,3 +1,5 @@
/* eslint-disable max-lines */
const fontAwesome = {
// FONT-AWESOME SOLID
'fas_0' : 'fas fa-0',

View File

@@ -1,31 +1,31 @@
.phb{
.phb {
//Double hr for full width elements
hr+hr+blockquote{
column-span : all;
hr + hr + blockquote {
-webkit-column-span : all;
-moz-column-span : all;
column-span : all;
}
//*****************************
// * CLASS TABLE
// *****************************/
hr+table{
hr+table {
padding-top : 10px;
margin-top : -5px;
margin-bottom : 50px;
padding-top : 10px;
border-collapse : separate;
background-color : white;
border : initial;
border-style : solid;
border-image-source : @frameBorderImage;
border-image-slice : 150 200 150 200;
border-image-width : 47px;
border-image-outset : 37px 17px;
border-image-repeat : round;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorderImage;
border-image-width : 47px;
}
h5+hr+table{
column-span : all;
h5 + hr + table {
-webkit-column-span : all;
-moz-column-span : all;
column-span : all;
}
}