0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-23 14:23:21 +00:00

Compare commits

...

447 Commits

Author SHA1 Message Date
Trevor Buckner
c74c2c8efe Merge branch 'master' into v3.19.0 2025-05-22 14:31:02 -04:00
Trevor Buckner
1efe570dae Up version to 3.19.0 2025-05-22 14:30:15 -04:00
Trevor Buckner
2571460f42 Merge pull request #4199 from naturalcrit/dependabot/npm_and_yarn/eslint-9.27.0
Bump eslint from 9.26.0 to 9.27.0
2025-05-22 11:49:48 -04:00
dependabot[bot]
dbb67113b9 Bump eslint from 9.26.0 to 9.27.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.26.0 to 9.27.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.26.0...v9.27.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-22 00:18:55 +00:00
Trevor Buckner
33e3e018f3 Merge pull request #4202 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-12.1.0
Bump @googleapis/drive from 12.0.0 to 12.1.0
2025-05-21 20:17:36 -04:00
dependabot[bot]
07adf0342d Bump @googleapis/drive from 12.0.0 to 12.1.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 12.0.0 to 12.1.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/12.0.0...iam-v12.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 19:56:20 +00:00
Trevor Buckner
b2b1cb4985 Merge pull request #4203 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.12
Bump marked from 15.0.11 to 15.0.12
2025-05-21 15:55:01 -04:00
dependabot[bot]
c4d6cc4579 Bump marked from 15.0.11 to 15.0.12
Bumps [marked](https://github.com/markedjs/marked) from 15.0.11 to 15.0.12.
- [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.11...v15.0.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 03:25:36 +00:00
Trevor Buckner
01fbb4439e Merge pull request #4197 from naturalcrit/dependabot/npm_and_yarn/supertest-7.1.1
Bump supertest from 7.1.0 to 7.1.1
2025-05-20 13:52:29 -04:00
dependabot[bot]
eb48d981d6 Bump supertest from 7.1.0 to 7.1.1
Bumps [supertest](https://github.com/ladjs/supertest) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/ladjs/supertest/releases)
- [Commits](https://github.com/ladjs/supertest/compare/v7.1.0...v7.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 14:13:10 +00:00
Trevor Buckner
3624fcef0f Merge pull request #4198 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.14.3
Bump mongoose from 8.14.2 to 8.14.3
2025-05-14 10:11:50 -04:00
dependabot[bot]
ab62f0fcf9 Bump mongoose from 8.14.2 to 8.14.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.14.2 to 8.14.3.
- [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.14.2...8.14.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 03:13:55 +00:00
Víctor Losada Hernández
9e78671e4f Merge pull request #4195 from dbolack-ab/F5_to_F6
Correct missed Font Awesome version number references
2025-05-11 22:18:56 +02:00
Víctor Losada Hernández
f64a7b38ae Merge branch 'master' into F5_to_F6 2025-05-11 22:14:02 +02:00
David Bolack
3fdedd8861 Correct missed Font Awesome Reference versions 2025-05-11 15:08:20 -05:00
Víctor Losada Hernández
1d4ebbb689 Merge pull request #4191 from naturalcrit/fix-darkbrewery
Fix-darkbrewery
2025-05-11 16:00:53 +02:00
Víctor Losada Hernández
c4f148a3a1 Merge branch 'master' into fix-darkbrewery 2025-05-11 15:56:46 +02:00
David Bolack
5bde870586 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-10 18:37:45 -05:00
Trevor Buckner
7ea78870bf Merge pull request #4186 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.27.2
Bump @babel/preset-env from 7.26.9 to 7.27.2
2025-05-10 17:50:08 -04:00
Trevor Buckner
393caa86eb add babel/runtime 2025-05-10 17:41:39 -04:00
dependabot[bot]
9b7a3c5c70 Bump @babel/preset-env from 7.26.9 to 7.27.2
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.9 to 7.27.2.
- [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.2/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-version: 7.27.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-10 17:32:41 +00:00
Trevor Buckner
fe69bd50b5 Merge pull request #4190 from naturalcrit/dependabot/npm_and_yarn/idb-keyval-6.2.2
Bump idb-keyval from 6.2.1 to 6.2.2
2025-05-10 13:31:24 -04:00
Trevor Buckner
a2c4f604b3 Merge pull request #4193 from dbolack-ab/issue_4192
Resolve Issue 4192
2025-05-10 13:31:08 -04:00
Trevor Buckner
083e8c9b52 remove duplicate comment 2025-05-10 13:30:38 -04:00
David Bolack
d2a025ca41 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-10 12:27:33 -05:00
Trevor Buckner
181d6b7e0a Merge branch 'master' into pr/4193 2025-05-10 13:24:48 -04:00
Trevor Buckner
dd20fc8475 Revert "Revert debris"
This reverts commit ab400b82d6.
2025-05-10 13:21:35 -04:00
David Bolack
33ea397915 Restore removed line and warn.
Yeah, it's a bit extra.
2025-05-10 09:50:19 -05:00
Víctor Losada Hernández
320fb02543 remove empty lines in css 2025-05-09 20:44:59 +02:00
Víctor Losada Hernández
e127a6a557 refactor the theme 2025-05-09 20:42:23 +02:00
Víctor Losada Hernández
e774dfd97d change darkbrewery's name 2025-05-09 20:42:16 +02:00
Víctor Losada Hernández
1dcea0fe6a fix snippet editor button tooltip 2025-05-09 20:42:01 +02:00
dependabot[bot]
5395a759ed Bump idb-keyval from 6.2.1 to 6.2.2
Bumps [idb-keyval](https://github.com/jakearchibald/idb-keyval) from 6.2.1 to 6.2.2.
- [Changelog](https://github.com/jakearchibald/idb-keyval/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jakearchibald/idb-keyval/compare/v6.2.1...v6.2.2)

---
updated-dependencies:
- dependency-name: idb-keyval
  dependency-version: 6.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 13:32:48 +00:00
Trevor Buckner
8f470fb000 Merge pull request #4188 from naturalcrit/dependabot/npm_and_yarn/react-router-7.6.0
Bump react-router from 7.5.3 to 7.6.0
2025-05-09 09:31:27 -04:00
Trevor Buckner
90c375a5c8 Merge pull request #4184 from dbolack-ab/GMB_Compats_1
Allow \pagebreak and \columnbreak compatibility for GMB users
2025-05-09 09:31:17 -04:00
Trevor Buckner
e8cc4a0c58 Merge branch 'master' into GMB_Compats_1 2025-05-09 09:29:12 -04:00
dependabot[bot]
cf68cc46ad Bump react-router from 7.5.3 to 7.6.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.5.3 to 7.6.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.6.0/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 13:24:37 +00:00
Trevor Buckner
653e20b4e4 Merge pull request #4189 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.14.2
Bump mongoose from 8.14.1 to 8.14.2
2025-05-09 09:23:15 -04:00
dependabot[bot]
e97d45e5b5 Bump mongoose from 8.14.1 to 8.14.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.14.1 to 8.14.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.14.1...8.14.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 03:28:37 +00:00
Trevor Buckner
691cd048e2 Merge pull request #4161 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-12.0.0
Bump @googleapis/drive from 11.0.0 to 12.0.0
2025-05-08 11:25:10 -04:00
Trevor Buckner
5071105f8c Merge pull request #4187 from naturalcrit/dependabot/npm_and_yarn/globals-16.1.0
Bump globals from 16.0.0 to 16.1.0
2025-05-08 00:12:04 -04:00
dependabot[bot]
9cd009e89b Bump globals from 16.0.0 to 16.1.0
Bumps [globals](https://github.com/sindresorhus/globals) from 16.0.0 to 16.1.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.0.0...v16.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-08 04:00:08 +00:00
Trevor Buckner
acaf293c7c Merge branch 'master' into dependabot/npm_and_yarn/googleapis/drive-12.0.0 2025-05-07 16:57:30 -04:00
Trevor Buckner
79503dd17f Merge pull request #4156 from 5e-Cleric/toolbar-few-fixes
toolbar, small fixes
2025-05-07 16:34:08 -04:00
dependabot[bot]
485b6a0041 Bump @googleapis/drive from 11.0.0 to 12.0.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 11.0.0 to 12.0.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/12.0.0/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/11.0.0...12.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 20:27:44 +00:00
Trevor Buckner
983781303b Merge branch 'master' into toolbar-few-fixes 2025-05-07 16:27:29 -04:00
Trevor Buckner
9c8e03f961 Merge pull request #4181 from naturalcrit/dependabot/npm_and_yarn/react-router-7.5.3
Bump react-router from 7.5.1 to 7.5.3
2025-05-07 16:26:28 -04:00
Víctor Losada Hernández
a298288888 Merge branch 'master' into toolbar-few-fixes 2025-05-07 21:38:46 +02:00
dependabot[bot]
c48703aed5 Bump react-router from 7.5.1 to 7.5.3
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.5.1 to 7.5.3.
- [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.3/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 19:05:23 +00:00
Trevor Buckner
09000bd20f Merge pull request #4173 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.27.1
Bump @babel/plugin-transform-runtime from 7.26.10 to 7.27.1
2025-05-07 15:03:59 -04:00
Trevor Buckner
237caa84f7 Merge branch 'master' into dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.27.1 2025-05-07 15:00:43 -04:00
Trevor Buckner
d292d60ee9 Merge pull request #4172 from naturalcrit/dependabot/npm_and_yarn/core-js-3.42.0
Bump core-js from 3.41.0 to 3.42.0
2025-05-07 15:00:27 -04:00
dependabot[bot]
395e406d65 Bump @babel/plugin-transform-runtime from 7.26.10 to 7.27.1
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.26.10 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-plugin-transform-runtime)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:52:34 +00:00
Trevor Buckner
806c3f63bb Merge branch 'master' into dependabot/npm_and_yarn/core-js-3.42.0 2025-05-07 14:51:56 -04:00
Trevor Buckner
4a296809a0 Merge pull request #4175 from naturalcrit/dependabot/npm_and_yarn/babel/preset-react-7.27.1
Bump @babel/preset-react from 7.26.3 to 7.27.1
2025-05-07 14:51:18 -04:00
dependabot[bot]
f8361fa141 Bump core-js from 3.41.0 to 3.42.0
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.41.0 to 3.42.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.42.0/packages/core-js)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:43:22 +00:00
Trevor Buckner
8542056d6e Merge branch 'master' into dependabot/npm_and_yarn/babel/preset-react-7.27.1 2025-05-07 14:42:30 -04:00
Trevor Buckner
f23be91b6d Merge pull request #4174 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.27.1
Bump @babel/core from 7.26.10 to 7.27.1
2025-05-07 14:41:59 -04:00
dependabot[bot]
f810bea4c8 Bump @babel/preset-react from 7.26.3 to 7.27.1
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.26.3 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-preset-react)

---
updated-dependencies:
- dependency-name: "@babel/preset-react"
  dependency-version: 7.27.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:37:10 +00:00
dependabot[bot]
42136b89fd Bump @babel/core from 7.26.10 to 7.27.1
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.10 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-core)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:37:04 +00:00
Trevor Buckner
eb604d9201 Merge pull request #4182 from naturalcrit/dependabot/npm_and_yarn/eslint-9.26.0
Bump eslint from 9.25.1 to 9.26.0
2025-05-07 14:35:43 -04:00
dependabot[bot]
e341069196 Bump eslint from 9.25.1 to 9.26.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.25.1 to 9.26.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.25.1...v9.26.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:32:37 +00:00
Trevor Buckner
3a54ac9d7d Merge pull request #4164 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.11
Bump marked from 15.0.9 to 15.0.11
2025-05-07 14:31:10 -04:00
Trevor Buckner
42d8c1b33f Merge branch 'master' into dependabot/npm_and_yarn/marked-15.0.11 2025-05-07 14:27:58 -04:00
Trevor Buckner
f700620373 Merge pull request #4167 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.19.1
Bump stylelint from 16.18.0 to 16.19.1
2025-05-07 14:27:46 -04:00
dependabot[bot]
0f059bce66 Bump stylelint from 16.18.0 to 16.19.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.18.0 to 16.19.1.
- [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.18.0...16.19.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 17:21:48 +00:00
Trevor Buckner
0eb68aaf72 Merge pull request #4183 from naturalcrit/dependabot/npm_and_yarn/superagent-10.2.1
Bump superagent from 10.2.0 to 10.2.1
2025-05-07 13:20:24 -04:00
Trevor Buckner
b9f825c168 Merge branch 'master' into dependabot/npm_and_yarn/marked-15.0.11 2025-05-07 13:20:10 -04:00
Víctor Losada Hernández
58c2504394 Merge branch 'toolbar-few-fixes' of https://github.com/5e-Cleric/homebrewery into toolbar-few-fixes 2025-05-07 12:01:16 +02:00
Víctor Losada Hernández
a9aadbfef9 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-05-07 12:01:09 +02:00
Víctor Losada Hernández
dae5922fd0 Merge branch 'master' into toolbar-few-fixes 2025-05-07 12:00:46 +02:00
Víctor Losada Hernández
5fb20991bb fix ttolbar visibility storage 2025-05-07 11:57:51 +02:00
Víctor Losada Hernández
75fe7b2c67 increase visibility of toolbar toggle 2025-05-07 11:57:41 +02:00
David Bolack
ab400b82d6 Revert debris 2025-05-06 16:36:19 -05:00
David Bolack
6867cb5a4a Clear up debris that was on the wrong branch 2025-05-06 16:35:53 -05:00
David Bolack
742de8582c Allow pagebreak and columnbreak compatibility 2025-05-06 16:27:55 -05:00
David Bolack
600ff5f367 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-06 16:14:02 -05:00
dependabot[bot]
e751facf32 Bump superagent from 10.2.0 to 10.2.1
Bumps [superagent](https://github.com/ladjs/superagent) from 10.2.0 to 10.2.1.
- [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.2.0...v10.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-06 03:36:49 +00:00
Víctor Losada Hernández
959d5fb6c9 Merge pull request #4169 from dbolack-ab/self-host-fa
Localise out Fontawesome use.
2025-05-06 00:28:26 +02:00
Víctor Losada Hernández
3456d503b2 Merge branch 'master' into self-host-fa 2025-05-06 00:25:16 +02:00
David Bolack
9ef291a8ae Verbage change 2025-05-05 08:53:47 -05:00
David Bolack
ff174870e2 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-05 08:52:23 -05:00
Víctor Losada Hernández
a015714d5e Merge pull request #4178 from naturalcrit/template-metadata-brew
Add author as metadata for link unfurling
2025-05-03 00:32:01 +02:00
Víctor Losada Hernández
9bcab7b82b Merge branch 'master' into template-metadata-brew 2025-05-02 21:28:59 +02:00
dependabot[bot]
bc0cb0d0be Bump marked from 15.0.9 to 15.0.11
Bumps [marked](https://github.com/markedjs/marked) from 15.0.9 to 15.0.11.
- [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.9...v15.0.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-02 18:51:01 +00:00
Víctor Losada Hernández
ce4299a1f0 Merge pull request #4158 from G-Ambatte/fixNavigationScrollError-#4157
Change page overflow from hidden to clip
2025-05-02 20:50:27 +02:00
Víctor Losada Hernández
398e985e65 Merge branch 'master' into fixNavigationScrollError-#4157 2025-05-02 20:50:13 +02:00
Trevor Buckner
a5f597f598 Merge pull request #4149 from naturalcrit/dependabot/npm_and_yarn/react-router-7.5.1
Bump react-router from 7.5.0 to 7.5.1
2025-05-02 14:49:43 -04:00
Víctor Losada Hernández
beb7ecd0a9 Merge branch 'master' into fixNavigationScrollError-#4157 2025-05-02 20:46:34 +02:00
Trevor Buckner
ea625a0fbc Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.1 2025-05-02 14:41:50 -04:00
Trevor Buckner
932120883b Merge pull request #4171 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.14.1
Bump mongoose from 8.13.2 to 8.14.1
2025-05-02 14:41:07 -04:00
Víctor Losada Hernández
b29406da8b Merge branch 'master' into fixNavigationScrollError-#4157 2025-05-02 20:34:09 +02:00
Víctor Losada Hernández
4cc2d429c5 try to pass it as title 2025-05-02 17:15:24 +02:00
Víctor Losada Hernández
77563d12a6 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into template-metadata-brew 2025-05-02 17:11:02 +02:00
Víctor Losada Hernández
b914bf3bf5 simple as cake 2025-05-02 17:09:09 +02:00
David Bolack
44713eda4e Might work? 2025-04-29 23:20:06 -05:00
dependabot[bot]
e552282299 Bump mongoose from 8.13.2 to 8.14.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.13.2 to 8.14.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.13.2...8.14.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-30 03:28:34 +00:00
David Bolack
5ee1cf6aa5 Localise out Fontawesome use. 2025-04-29 19:17:40 -05:00
Trevor Buckner
1295f635dc Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.1 2025-04-22 14:00:53 -04:00
Trevor Buckner
60142d9467 Merge pull request #4159 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.9
Bump marked from 15.0.8 to 15.0.9
2025-04-22 13:57:56 -04:00
dependabot[bot]
6dc4355972 Bump marked from 15.0.8 to 15.0.9
Bumps [marked](https://github.com/markedjs/marked) from 15.0.8 to 15.0.9.
- [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.8...v15.0.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 13:46:13 +00:00
Trevor Buckner
555a26f0d6 Merge pull request #4160 from naturalcrit/dependabot/npm_and_yarn/eslint-9.25.1
Bump eslint from 9.25.0 to 9.25.1
2025-04-22 09:44:51 -04:00
dependabot[bot]
abce7d8531 Bump eslint from 9.25.0 to 9.25.1
Bumps [eslint](https://github.com/eslint/eslint) from 9.25.0 to 9.25.1.
- [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.25.0...v9.25.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 03:31:44 +00:00
Víctor Losada Hernández
678d981121 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-04-22 00:45:52 +02:00
Víctor Losada Hernández
32f8c18adc Merge branch 'store-toolbar-state-in-storage' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-04-22 00:43:09 +02:00
Víctor Losada Hernández
0aead96dcf fix columnGap 2025-04-22 00:42:48 +02:00
G.Ambatte
c238094e4c Change page overflow from hidden to clip 2025-04-22 10:28:35 +12:00
Trevor Buckner
657eeea4d5 Merge pull request #4155 from naturalcrit/dependabot/npm_and_yarn/eslint-9.25.0
Bump eslint from 9.24.0 to 9.25.0
2025-04-21 16:51:46 -04:00
Víctor Losada Hernández
1e34e85aab actually fix it 2025-04-21 22:23:55 +02:00
Víctor Losada Hernández
b747968e74 fixing toFit to actually fit in any mode 2025-04-21 22:17:52 +02:00
Víctor Losada Hernández
25629173c9 Merge branch 'store-toolbar-state-in-storage' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-04-21 20:45:54 +02:00
Víctor Losada Hernández
96642c07d3 initial attempt at facing width button 2025-04-21 20:45:48 +02:00
Víctor Losada Hernández
2bd0f909f3 fix snippetbar being eaten 2025-04-21 20:45:36 +02:00
Víctor Losada Hernández
9b4047f3f9 small css changes 2025-04-21 20:12:42 +02:00
dependabot[bot]
91e2916199 Bump eslint from 9.24.0 to 9.25.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.24.0 to 9.25.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.24.0...v9.25.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 03:46:54 +00:00
Trevor Buckner
3fcc677f96 Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.1 2025-04-19 21:32:21 -04:00
Trevor Buckner
3f77e32550 Merge pull request #4147 from G-Ambatte/fixFacingFlowPrintIssues
Add print-specific styling for facing and flow page layouts
2025-04-18 07:50:27 -04:00
dependabot[bot]
c4903c4993 Bump react-router from 7.5.0 to 7.5.1
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.5.0 to 7.5.1.
- [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.1/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-18 03:47:56 +00:00
Trevor Buckner
630f9002aa Merge branch 'master' into fixFacingFlowPrintIssues 2025-04-17 15:40:13 -04:00
Trevor Buckner
aea7809fbd Merge pull request #4049 from dbolack-ab/marked-definition-lists
Move Definition Lists extensions to NPM
2025-04-17 15:38:46 -04:00
Trevor Buckner
30e644d5e0 Merge branch 'marked-definition-lists' of https://github.com/dbolack-ab/homebrewery into pr/4049 2025-04-17 15:35:01 -04:00
Trevor Buckner
fe2f5a405c Update package-lock.json 2025-04-17 15:31:25 -04:00
Trevor Buckner
07a1890ed9 Merge branch 'master' into pr/4049 2025-04-17 15:18:40 -04:00
G.Ambatte
fc400c226c Merge branch 'master' into fixFacingFlowPrintIssues 2025-04-16 10:31:45 +12:00
Trevor Buckner
8e3ccec855 Remove old references to extensions 2025-04-15 15:29:53 -04:00
Trevor Buckner
25c09bc241 Merge branch 'master' into marked-definition-lists 2025-04-15 13:57:57 -04:00
Trevor Buckner
0eaba3de01 Merge pull request #4145 from naturalcrit/dependabot/npm_and_yarn/nconf-0.13.0
Bump nconf from 0.12.1 to 0.13.0
2025-04-15 13:01:23 -04:00
Trevor Buckner
ece1a7e9a7 Merge branch 'master' into dependabot/npm_and_yarn/nconf-0.13.0 2025-04-15 11:47:33 -04:00
Trevor Buckner
2ef7a1521b Merge pull request #4148 from naturalcrit/rebuildPackageLock
Rebuild package-lock
2025-04-15 11:47:20 -04:00
Trevor Buckner
b72357096a Rebuild package-lock 2025-04-15 14:58:16 +00:00
G.Ambatte
8f4c74d0ce Add print-specific styling for facing and flow page layouts 2025-04-15 19:36:35 +12:00
dependabot[bot]
2589e6d919 Bump nconf from 0.12.1 to 0.13.0
Bumps [nconf](https://github.com/flatiron/nconf) from 0.12.1 to 0.13.0.
- [Release notes](https://github.com/flatiron/nconf/releases)
- [Changelog](https://github.com/indexzero/nconf/blob/v0.13.0/CHANGELOG.md)
- [Commits](https://github.com/flatiron/nconf/compare/v0.12.1...v0.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 21:36:06 +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
b7a7446f75 Merge branch 'master' into marked-definition-lists 2025-04-08 21:43:14 -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
551763fecb initial commit 2025-03-19 14:04:29 +01: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
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
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
20baa9984f Cleanup 2025-03-06 11:13:39 -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
4b9b1ec9ac Merge branch 'master' into marked-definition-lists 2025-03-05 10:36:50 -06:00
David Bolack
01f075d3f5 Merge branch 'master' into marked-definition-lists 2025-02-28 17:06:44 -06:00
David Bolack
de18a53efe Update markd-definition-lists 2025-02-28 17:03:51 -06:00
David Bolack
caca578709 Merge branch 'master' into marked-definition-lists 2025-02-28 00:00:47 -06: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
David Bolack
09ac8b8a32 Move definition list tokens to an extension 2025-02-12 22:57:53 -06: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
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
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
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
342ac76982 Merge branch 'master' into localSnippetEditor 2025-01-24 14:30:12 -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
103 changed files with 12953 additions and 3761 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
@@ -64,9 +64,6 @@ jobs:
- run:
name: Test - Mustache Spans
command: npm run test:mustache-syntax
- run:
name: Test - Definition Lists
command: npm run test:definition-lists
- run:
name: Test - Hard Breaks
command: npm run test:hard-breaks

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,24 +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
### Thursday 05/22/2025 - v3.19.0
{{taskList
##### abquintic
* [x] Fix crash due to colons after `\page`
Fixes issue [#4105](https://github.com/naturalcrit/homebrewery/issues/4105)
* [x] Fix images with spaces in alt text not rendering
Fixes issue [#3659](https://github.com/naturalcrit/homebrewery/issues/3659)
* [x] Custom snippets! Open the new {{openSans **:fas_table_list: SNIPPETS**}} tab (next to the {{openSans **:fas_paintbrush: STYLE**}} tab). Custom snippets will appear in a new snippet dropdown, and will be included when imported as a custom theme.
* [x] Move several generic styles/snippets from PHB to the Blank theme; generic snippets like image masks no longer require the PHB theme.
* [x] Extract several Markdown+ syntax extensions into their own NPM packages, for use by the wider community.
* [x] Allow `\pagebreak` and `\columnbreak` as alternatives to `\page` and `\column`
Partially fixes issue [#4035](https://github.com/naturalcrit/homebrewery/issues/4035)
* [x] Fix misbehaving column breaks on old Chrome
Fixes issue [#4192](https://github.com/naturalcrit/homebrewery/issues/4192)
* [x] Self-host font-awesome icons; fix missing icons on local installs
Fixes issue [#1965](https://github.com/naturalcrit/homebrewery/issues/1965)
Fixes issue [#1548](https://github.com/naturalcrit/homebrewery/issues/1548)
##### G-Ambatte
* [x] Revert colon rendering from br elements to blank divs
* [x] Fix CORS issue on local installs
* [x] Fix print size issues when using the Facing and Flow view options.
Fixes issue [#4146](https://github.com/naturalcrit/homebrewery/issues/4146)
* [x] New built-in `$[HB_pageNumber]` variable. Works with math operations or can be reassigned like any other variable for more customization over the old `{{pageNumber,auto}}` snippet.\
New snippet found at {{openSans **:fas_pencil: TEXT EDITOR :fas_arrow_right: :fas_bookmark: PAGE NUMBERING :fas_arrow_right: :fas_arrow_down_1_9: VARIABLE AUTO PAGE NUMBER**}}
##### 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] Fix search bar covering up snippet bar
Fixes issue [#4098](https://github.com/naturalcrit/homebrewery/issues/4098)
* [x] Save view toolbar settings across sessions
Fixes issue [#3835](https://github.com/naturalcrit/homebrewery/issues/3835)
* [x] Fix styling issues on the view toolbar
* [x] Update the Darkbrewery editor theme
Fixes issue [#3312](https://github.com/naturalcrit/homebrewery/issues/3312)
* [x] Add US Letter size page snippet
Fixes issue [#3893](https://github.com/naturalcrit/homebrewery/issues/3893)
}}
\page
### Monday 03/10/2025 - v3.18.0
{{taskList
##### dbolack
##### abquintic
* [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
@@ -167,7 +213,7 @@ Fixes issue [#4073](https://github.com/naturalcrit/homebrewery/issues/4073)
* [x] Fix Reddit link crash when title has non-latin chars
##### dbolack
##### abquintic
* [x] Fix page shadows toolbar option

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

@@ -19,12 +19,11 @@ const { printCurrentBrew } = require('../../../shared/helpers.js');
import HeaderNav from './headerNav/headerNav.jsx';
import { safeHTML } from './safeHTML.js';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m;
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const PAGE_HEIGHT = 1056;
const INITIAL_CONTENT = dedent`
<!DOCTYPE html><html><head>
<link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href='/homebrew/bundle.css' type="text/css" rel='stylesheet' />
<base target=_blank>
@@ -117,6 +116,13 @@ const BrewRenderer = (props)=>{
pageShadows : true
});
//useEffect to store or gather toolbar state from storage
useEffect(()=>{
const toolbarState = JSON.parse(window.localStorage.getItem('hb_toolbarState'));
console.log('toolbar state:', toolbarState);
toolbarState && setDisplayOptions(toolbarState);
}, []);
const [headerState, setHeaderState] = useState(false);
const mainRef = useRef(null);
@@ -186,17 +192,20 @@ 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
}
let html = Markdown.render(pageText, index);
// DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME.
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} />;
}
@@ -271,6 +280,7 @@ const BrewRenderer = (props)=>{
const handleDisplayOptionsChange = (newDisplayOptions)=>{
setDisplayOptions(newDisplayOptions);
localStorage.setItem('hb_toolbarState', JSON.stringify(newDisplayOptions));
};
const pagesStyle = {

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;
@@ -74,16 +68,18 @@
@media print {
.toolBar { display : none; }
.brewRenderer {
height : 100%;
padding-top : unset;
overflow-y : unset;
height : 100%;
padding : unset;
overflow-y : unset;
&:has(.facing, .flow) {
padding : unset;
}
.pages {
margin : 0px;
zoom: 100% !important;
margin : 0px;
zoom : 100% !important;
display : block;
& > .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

@@ -86,8 +86,8 @@
width : 100%;
}
.blank {
height : 1em;
margin-top: 0;
& + * { margin-top: 0; }
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}

View File

@@ -20,6 +20,11 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
setPageNum(pageRange);
}, [visiblePages]);
useEffect(()=>{
const visibility = localStorage.getItem('hb_toolbarVisibility') === 'true';
setToolsVisible(visibility);
}, []);
const handleZoomButton = (zoom)=>{
handleOptionChange('zoomLevel', _.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
};
@@ -55,15 +60,30 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
// find widest page, in case pages are different widths, so that the zoom is adapted to not cut the widest page off screen.
const widestPage = _.maxBy([...pages], 'offsetWidth').offsetWidth;
desiredZoom = (iframeWidth / widestPage) * 100;
if(displayOptions.spread === 'facing')
desiredZoom = (iframeWidth / ((widestPage * 2) + parseInt(displayOptions.columnGap))) * 100;
else
desiredZoom = (iframeWidth / (widestPage + 20)) * 100;
} else if(mode == 'fit'){
let minDimRatio;
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
if(displayOptions.spread === 'facing')
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth / 2), Infinity); // if 'facing' spread, fit two pages in view
let minDimRatio;
if(displayOptions.spread === 'active')
minDimRatio = [...pages].reduce(
(minRatio, page)=>Math.min(minRatio,
iframeWidth / page.offsetWidth,
iframeHeight / page.offsetHeight
),
Infinity
);
else
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
minDimRatio = [...pages].reduce(
(minRatio, page)=>Math.min(minRatio,
iframeWidth / ((page.offsetWidth * 2) + parseInt(displayOptions.columnGap)),
iframeHeight / page.offsetHeight
),
Infinity
);
desiredZoom = minDimRatio * 100;
}
@@ -77,7 +97,10 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
return (
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
<div className='toggleButton'>
<button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
<button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{
setToolsVisible(!toolsVisible);
localStorage.setItem('hb_toolbarVisibility', !toolsVisible);
}}><i className='fas fa-glasses' /></button>
<button title={`${headerState ? 'Hide' : 'Show'} Header Navigation`} onClick={()=>{setHeaderState(!headerState);}}><i className='fas fa-rectangle-list' /></button>
</div>
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
@@ -167,11 +190,11 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
<h1>Options</h1>
<label title='Modify the horizontal space between pages.'>
Column gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('columnGap', evt.target.value)} />
<input type='range' min={0} max={200} defaultValue={displayOptions.columnGap || 10} className='range-input' onChange={(evt)=>handleOptionChange('columnGap', evt.target.value)} />
</label>
<label title='Modify the vertical space between rows of pages.'>
Row gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('rowGap', evt.target.value)} />
<input type='range' min={0} max={200} defaultValue={displayOptions.rowGap || 10} className='range-input' onChange={(evt)=>handleOptionChange('rowGap', evt.target.value)} />
</label>
<label title='Start 1st page on the right side, such as if you have cover page.'>
Start on right

View File

@@ -6,12 +6,12 @@
box-sizing : border-box;
display : flex;
flex-wrap : wrap;
gap : 8px 30px;
gap : 8px 20px;
align-items : center;
justify-content : center;
width : 100%;
height : auto;
padding : 2px 0;
padding : 2px 10px 2px 90px;
font-family : 'Open Sans', sans-serif;
font-size : 13px;
color : #CCCCCC;
@@ -153,10 +153,10 @@
align-items : center;
justify-content : center;
width : auto;
min-width : 46px;
min-width : 40px;
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;
@@ -169,7 +169,7 @@
width : 92px;
overflow : hidden;
background-color : unset;
opacity : 0.5;
opacity : 0.7;
transition : all 0.3s ease;
& > *:not(.toggleButton) {
opacity : 0;
@@ -182,8 +182,11 @@
position : absolute;
left : 0;
z-index : 5;
width : 32px;
min-width : unset;
height : 100%;
display : flex;
height : 100%;
button i {
filter: drop-shadow(0 0 2px black) drop-shadow(0 0 1px black);
}
}

View File

@@ -12,7 +12,8 @@ const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m;
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\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,23 +193,25 @@ 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(line.match(/^\\column$/)){
if(this.props.renderer === 'V3') {
if(line.match(/^\\column(?:break)?$/)){
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,8 @@
}
}
@container editor (width < 683px) {
.editor .codeEditor .CodeMirror { height : calc(100% - 51px);}
.homePage .editor .codeEditor .CodeMirror { height : calc(100% - 25px);}
}

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');
}
&.snippet {
.tooltipLeft('Snippets');
}
&.undo {
.tooltipLeft('Undo');
font-size : 0.75em;
@@ -92,7 +93,7 @@
&.editorTheme {
.tooltipLeft('Editor Themes');
font-size : 0.75em;
color : black;
color : inherit;
&.active {
position : relative;
background-color : #999999;
@@ -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 6 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 6 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 6 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 6 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 6 Free';
content : '\f00c';
}
}
}
@@ -60,11 +60,9 @@
list-style : square;
}
.blank {
height: 1em;
margin-top: 0;
& + * {
margin-top: 0;
}
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
}

View File

@@ -150,6 +150,18 @@ const EditPage = createClass({
}), ()=>{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();});
},
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : { ...prevState.brew, style: style }
@@ -443,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
@@ -451,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

@@ -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'); }

View File

@@ -14,7 +14,6 @@ const template = async function(name, title='', props = {}){
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" />
<link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href=${`/${name}/bundle.css`} type="text/css" rel='stylesheet' />
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />

View File

@@ -0,0 +1,3 @@
# About
Run `deploy.bash` to download, extract, and deploy the font awesome files into place for building. Should only be needed when Font Awesome version changes and we want the new version.

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Deploys the Font Awesome files for HB self-hosting to settle various issues.
THEURL=https://use.fontawesome.com/releases/v6.7.2/fontawesome-free-6.7.2-web.zip
THEFILE=fontawesome-free-6.7.2-web.zip
if [ ! "$(which wget)" ]; then
echo "Please manually download ${THEURL}"
exit -1
fi
wget ${THEURL}
if [ $? -ne 0 ]; then
echo "Error downloading ${THEURL}"
exit -2
fi
if [ ! "$(which unzip)" ]; then
echo "Please unzip the file with your tool of choice."
exit -3
fi
unzip fontawesome-free-6.7.2-web.zip
if [ $? -ne 0 ]; then
echo "Error extracting ${THEFILE}"
fi
echo "Copying fonts"
cp -rv fontawesome-free-*-web/webfonts/*.woff2 ../themes/fonts/iconFonts
echo "Copying and updating css"
echo "fontawesome-free.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/fontawesome.css > ../themes/fonts/iconFonts/fontawesome-free.less
echo "fontawesome-solid.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/solid.css > ../themes/fonts/iconFonts/fontawesome-solid.less
echo "fontawesome-brands.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/brands.css > ../themes/fonts/iconFonts/fontawesome-brands.less
echo "fontawesome-regular.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/regular.css > ../themes/fonts/iconFonts/fontawesome-regular.less

2247
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.18.1",
"version": "3.19.0",
"type": "module",
"engines": {
"npm": "^10.2.x",
"npm": "^10.8.x",
"node": "^20.18.x"
},
"repository": {
@@ -36,7 +36,6 @@
"test:mustache-syntax:inline": "jest \".*(mustache-syntax).*\" -t '^Inline:.*' --verbose --noStackTrace",
"test:mustache-syntax:block": "jest \".*(mustache-syntax).*\" -t '^Block:.*' --verbose --noStackTrace",
"test:mustache-syntax:injection": "jest \".*(mustache-syntax).*\" -t '^Injection:.*' --verbose --noStackTrace",
"test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace",
"test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace",
"test:non-breaking-spaces": "jest tests/markdown/non-breaking-spaces.test.js --verbose --noStackTrace",
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
@@ -84,62 +83,68 @@
]
},
"dependencies": {
"@babel/core": "^7.26.9",
"@babel/plugin-transform-runtime": "^7.26.9",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^8.16.0",
"body-parser": "^1.20.2",
"@babel/core": "^7.27.1",
"@babel/plugin-transform-runtime": "^7.27.1",
"@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1",
"@babel/runtime": "^7.27.1",
"@googleapis/drive": "^12.1.0",
"body-parser": "^2.2.0",
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.7",
"core-js": "^3.41.0",
"core-js": "^3.42.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"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",
"idb-keyval": "^6.2.1",
"idb-keyval": "^6.2.2",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "14.0.0",
"marked": "15.0.12",
"marked-alignment-paragraphs": "^1.0.0",
"marked-definition-lists": "^1.0.1",
"marked-emoji": "^2.0.0",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.0.1",
"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.12.1",
"nanoid": "5.1.3",
"nconf": "^0.12.1",
"mongoose": "^8.14.3",
"nanoid": "5.1.5",
"nconf": "^0.13.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
"react-router": "^7.3.0",
"react-router": "^7.6.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.1",
"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.22.0",
"eslint": "^9.27.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^16.0.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.1.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.15.0",
"stylelint": "^16.19.1",
"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.1"
}
}

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,14 +69,11 @@ const corsOptions = {
'https://homebrewery-stage.herokuapp.com',
];
if(isLocalEnvironment) {
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+$/;
allowedOrigins.push(localNetworkRegex);
}
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');
@@ -437,7 +433,7 @@ app.get('/new', asyncHandler(async(req, res, next)=>{
app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
const { brew } = req;
req.ogMeta = { ...defaultMetaTags,
title : req.brew.title || 'Untitled Brew',
title : `${req.brew.title || 'Untitled Brew'} - ${req.brew.authors[0] || 'No author.'}`,
description : req.brew.description || 'No description.',
image : req.brew.thumbnail || defaultMetaTags.image,
type : 'article'

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

@@ -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

@@ -4,10 +4,15 @@ import _ from 'lodash';
import { Parser as MathParser } from 'expr-eval';
import { marked as Marked } from 'marked';
import MarkedExtendedTables from 'marked-extended-tables';
import MarkedDefinitionLists from 'marked-definition-lists';
import MarkedAlignedParagraphs from 'marked-alignment-paragraphs';
import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces';
import MarkedSubSuperText from 'marked-subsuper-text';
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 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,6 +64,53 @@ 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 (token) {
@@ -86,8 +138,8 @@ renderer.paragraph = function(token){
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (token) {
let {href, title, tokens} = token;
const text = this.parser.parseInline(tokens)
let { href, title, tokens } = token;
const text = this.parser.parseInline(tokens);
let self = false;
if(href[0] == '#') {
self = true;
@@ -99,7 +151,7 @@ renderer.link = function (token) {
}
let out = `<a href="${escape(href)}"`;
if(title) {
out += ` title="${title}"`;
out += ` title="${escape(title)}"`;
}
if(self) {
out += ' target="_self"';
@@ -110,7 +162,7 @@ renderer.link = function (token) {
// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS
renderer.image = function (token) {
let {href, title, text} = token;
const { href, title, text } = token;
if(href === null)
return text;
@@ -338,42 +390,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',
@@ -395,121 +411,13 @@ const forcedParagraphBreaks = {
}
};
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('');
}
};
const definitionListsSingleLine = {
name : 'definitionListsSingleLine',
level : 'block',
start(src) { return src.match(/\n[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
let match;
let endIndex = 0;
const definitions = [];
while (match = regex.exec(src)) {
const originalLine = match[0]; // This line and below to handle conflict with emojis
let firstLine = originalLine; // Remove in V4 when definitionListsInline updated to
this.lexer.inlineTokens(firstLine.trim()) // require spaces around `::`
.filter((t)=>t.type == 'emoji')
.map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length)));
const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine);
if(newMatch) {
definitions.push({
dt : this.lexer.inlineTokens(originalLine.slice(0, newMatch[1].length).trim()),
dd : this.lexer.inlineTokens(originalLine.slice(newMatch[1].length + 2).trim())
});
} // End of emoji hack.
endIndex = regex.lastIndex;
}
if(definitions.length) {
return {
type : 'definitionListsSingleLine',
raw : src.slice(0, endIndex),
definitions
};
}
},
renderer(token) {
return `<dl>${token.definitions.reduce((html, def)=>{
return `${html}<dt>${this.parser.parseInline(def.dt)}</dt>`
+ `<dd>${this.parser.parseInline(def.dd)}</dd>\n`;
}, '')}</dl>`;
}
};
const definitionListsMultiLine = {
name : 'definitionListsMultiLine',
level : 'block',
start(src) { return src.match(/\n[^\n]*\n::[^:\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /(\n?\n?(?!::)[^\n]+?(?=\n::[^:\n]))|\n::([^:\n](?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y;
let match;
let endIndex = 0;
const definitions = [];
while (match = regex.exec(src)) {
if(match[1]) {
if(this.lexer.blockTokens(match[1].trim())[0]?.type !== 'paragraph') // DT must not be another block-level token besides <p>
break;
definitions.push({
dt : this.lexer.inlineTokens(match[1].trim()),
dds : []
});
}
if(match[2] && definitions.length) {
definitions[definitions.length - 1].dds.push(
this.lexer.inlineTokens(match[2].trim().replace(/\s/g, ' '))
);
}
endIndex = regex.lastIndex;
}
if(definitions.length) {
return {
type : 'definitionListsMultiLine',
raw : src.slice(0, endIndex),
definitions
};
}
},
renderer(token) {
let returnVal = `<dl>`;
token.definitions.forEach((def)=>{
const dds = def.dds.map((s)=>{
return `\n<dd>${this.parser.parseInline(s).trim()}</dd>`;
}).join('');
returnVal += `<dt>${this.parser.parseInline(def.dt)}</dt>${dds}\n`;
});
returnVal = returnVal.trim();
return `${returnVal}</dl>`;
}
};
//v=====--------------------< Variable Handling >-------------------=====v// 242 lines
const replaceVar = function(input, hoist=false, allowUnresolved=false) {
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
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;
@@ -664,8 +572,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',
@@ -674,7 +582,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',
@@ -683,7 +591,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 )
@@ -715,7 +623,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',
@@ -771,12 +679,14 @@ const tableTerminators = [
];
Marked.use(MarkedVariables());
Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
nonbreakingSpaces, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(MarkedDefinitionLists());
Marked.use({ extensions : [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({interruptPatterns : tableTerminators}), MarkedGFMHeadingId({ globalSlugs: true }),
Marked.use(MarkedExtendedTables({ interruptPatterns: tableTerminators }), MarkedGFMHeadingId({ globalSlugs: true }),
MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
function cleanUrl(href) {
@@ -841,12 +751,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,
@@ -862,8 +772,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();
@@ -873,7 +783,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;
@@ -886,7 +796,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,
@@ -902,14 +812,20 @@ 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) {
MarkedGFMResetHeadingIDs();
}
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`);
rawBrewText = rawBrewText.replace(/^\\column(?:break)?$/gm, `\n<div class='columnSplit'></div>\n`);
const opts = Marked.defaults;

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,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

@@ -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 {
@@ -547,38 +547,38 @@
&::after { display : none; }
.frontCover { position : absolute; }
h1 {
margin-top : 1.55cm;
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;
@@ -601,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;
@@ -621,9 +621,7 @@
right : 0;
left : 0;
filter : drop-shadow(0 0 0.075cm black);
img {
height : 2cm;
}
img { height : 2cm; }
}
}
// *****************************
@@ -652,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;
@@ -666,19 +664,17 @@
bottom : 1cm;
left : 0;
height : 2cm;
img {
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 {
@@ -689,7 +685,7 @@
background-repeat : no-repeat;
background-size : contain;
}
.blank { height: 1.4em; }
.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;
@@ -795,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; }
@@ -908,9 +863,7 @@ h6,
.useColumns(0.96, @fillMode: balance);
}
}
.toc.wide li {
break-inside: auto;
}
.toc.wide li { break-inside : auto; }
}
// *****************************
@@ -935,9 +888,7 @@ h6,
.page h1 + * { margin-top : 0; }
.page .descriptive.wide + * {
margin-top: 0;
}
.page .descriptive.wide + * { margin-top : 0; }
//*****************************
// * RUNE TABLE
@@ -952,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 {
@@ -974,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. */'
},
}
]
},

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 {
@@ -51,15 +52,15 @@ body { counter-reset : page-numbers 0; }
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden;
overflow : clip;
background-color : var(--HB_Color_Background);
text-rendering : optimizeLegibility;
contain : strict;
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'); }
}
}
@@ -438,11 +439,9 @@ body { counter-reset : page-numbers 0; }
& + * { margin-top : 0; }
}
.blank {
height: 1em;
margin-top: 0;
& + * {
margin-top: 0;
}
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
@@ -468,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%); }
@@ -493,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,129 +0,0 @@
/* Main BG color and normal text color */
.CodeMirror {
background: #293134;
color: #91A6AA;
}
/* Brew BG */
.brewRenderer {
background-color: #293134;
}
/* Blinking cursor */
.CodeMirror-cursor {
border-left: 1px solid #e0e2e4;
}
/* HB DARK NAV START*/
/* Bars at the top */
.snippetBar {
background-color: #2F393C;
color: white;
}
nav {
background-color: #293134;
}
nav .navItem {
background-color: #293134;
}
/* Fix for Homebrewery custom Snippet icons */
.snippetBar .fac {
filter: invert(1);
}
.snippetBar .snippetGroup .dropdown {
background-color: #2F393C;
}
/* HB DARK NAV END */
/* Line number stuff */
.CodeMirror-gutter-elt {
color: #81969A;
}
.CodeMirror-linenumber {
background-color: #293134;
}
.CodeMirror-gutter {
background-color: #293134;
}
/* column splits */
.editor .codeEditor .columnSplit {
font-style: italic;
color: inherit;
background-color:#1f5763;
border-bottom: #299 solid 1px;
}
/* Colors for headings and such */
/* ###Headings */
.cm-s-default .cm-header {
color: #c51b1b;
-webkit-text-stroke-width: 0.1px;
-webkit-text-stroke-color: #000;
}
/* bold points */
.cm-header, .cm-strong {
font-weight: bold;
color: #309dd2;
}
/* Link headings */
.cm-s-default .cm-link {
color: #dd6300;
}
/* links */
.cm-s-default .cm-string {
color: #aa8261;
}
/*@import*/
.cm-s-default .cm-def {
color:#2986cc;
}
/* Bullets and such */
.cm-s-default .cm-variable-2 {
color: #3cbf30;
}
/* blocks */
.editor .codeEditor .block:not(.cm-comment) {
color: #e3e3e3;
}
/* inline blocks */
.editor .codeEditor .inline-block {
color: #e3e3e3;
}
/* Tags (divs) */
.cm-s-default .cm-tag {
color: #e3ff00;
}
.cm-s-default .cm-attribute {
color: #e3ff00;
}
.cm-s-default .cm-atom {
color:#000;
}
.cm-s-default .cm-qualifier{
color:#ee1919;
}
.cm-s-default .cm-comment{
color:#bbc700;
}
.cm-s-default .cm-keyword {
color:#c302df;
background-color:#b1b1b1;
}
.cm-s-default .cm-property.cm-error {
color:#c50202;
}
.CodeMirror-foldmarker {
color:#f0ff00;
}
/* New page */
.editor .codeEditor .pageLine {
background: #000;
color:#000;
border-bottom: 1px solid #fff;
}
.cm-s-default .cm-builtin {
color:#fff;
}

View File

@@ -0,0 +1,134 @@
/*stylelint-disable*/
.editor .snippetBar {
color: white;
background-color: #2F393C;
.dropdown {
background-color: #2F393C;
}
.editors {
border-color: #ccc;
}
}
/* Main BG color and normal text color */
.CodeMirror {
--bg: #293134;
--highlight: #bcbcbc;
color: #91A6AA;
background: var(--bg);
.CodeMirror-scroll {
.CodeMirror-gutters {
border-right: 1px solid #555;
background: var(--bg);
.CodeMirror-gutter {
background-color: var(--bg);
&.CodeMirror-foldgutter {
cursor: pointer;
border-left: 1px solid #555;
transition: background 0.1s;
&:hover {
background: #555;
}
}
}
}
.CodeMirror-lines {
/* Line numbers*/
.CodeMirror-linenumber.CodeMirror-gutter-elt {
background-color: var(--bg);
color: #81969A;
}
/* Blinking cursor */
.CodeMirror-cursor {
border-left: 1px solid #E0E2E4;
}
.pageLine {
color: #000000;
background: #000000;
border-bottom: 1px solid #FFFFFF;
}
.CodeMirror-code .CodeMirror-line {
&.columnSplit {
font-style: italic;
color: inherit;
background-color: #1F5763;
border-bottom: #229999 solid 1px;
}
/*syntax*/
.cm-header {
font-weight: bold;
color: #C51B1B;
-webkit-text-stroke-width: 0.1px;
-webkit-text-stroke-color: #000000;
}
.cm-strong {
color: #309DD2;
}
.cm-em {
/*italics*/
}
.cm-link {
color: #DD6300;
}
.cm-string {
color: #AA8261;
}
/* @import */
.cm-def {
color: #2986CC;
}
/* Bullets and such */
.cm-variable-2 {
color: #3CBF30;
}
.block:not(.cm-comment) {
color: #E3E3E3;
}
.inline-block {
color: #E3E3E3;
}
.cm-tag {
color: #E3FF00;
}
.cm-attribute {
color: #E3FF00;
}
.cm-atom {
color: #c1939a;
}
.cm-number {
color: #2986CC;
}
.cm-property:not(.cm-error) ~ .cm-variable {
color:#9e1f9e;
}
.cm-qualifier {
color: #EE1919;
}
.cm-comment {
color: #BBC700;
}
.cm-keyword {
color: white;
}
.cm-error {
color: #C50202;
}
.CodeMirror-foldmarker {
color: #F0FF00;
}
.cm-builtin {
color: #FFFFFF;
}
.dt-highlight {
background: #ffffff14;
}
.dl-colon-highlight {
background: #ccc;
}
.dl-highlight.dd-highlight {
color: #b5858d;
}
}
}
}
}

View File

@@ -15,7 +15,7 @@
"cobalt",
"colorforth",
"darcula",
"darkbrewery-v301",
"darkbrewery",
"darkvision",
"dracula",
"duotone-dark",

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 =========== */

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -1,2 +1,10 @@
@import (less) "./themes/fonts/iconFonts/fontawesome-free.less";
@import (less) "./themes/fonts/iconFonts/fontawesome-solid.less";
@import (less) "./themes/fonts/iconFonts/fontawesome-brands.less";
@import (less) "./themes/fonts/iconFonts/fontawesome-regular.less";
/* Icon Font: Font Awesome */
.far,.fas,.fab { display : inline; }
.far,.fas,.fab { display : inline; }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More