0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-24 07:43:01 +00:00

Compare commits

...

204 Commits

Author SHA1 Message Date
dependabot[bot]
be107939ca Bump react-frame-component from 4.1.3 to 5.2.7
Bumps [react-frame-component](https://github.com/ryanseddon/react-frame-component) from 4.1.3 to 5.2.7.
- [Release notes](https://github.com/ryanseddon/react-frame-component/releases)
- [Commits](https://github.com/ryanseddon/react-frame-component/compare/v4.1.3...v5.2.7)

---
updated-dependencies:
- dependency-name: react-frame-component
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-20 18:04:02 +00:00
Trevor Buckner
5f9cc48fe1 Merge pull request #4060 from naturalcrit/dependabot/npm_and_yarn/react-router-7.2.0
Bump react-router from 7.1.5 to 7.2.0
2025-02-20 13:02:43 -05:00
dependabot[bot]
56d1855518 Bump react-router from 7.1.5 to 7.2.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.5 to 7.2.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.2.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-02-20 03:59:59 +00:00
Trevor Buckner
758b508955 Merge pull request #4058 from naturalcrit/Change_Hardbreak_-_to_-br-
Change hardbreak  to `<br>`
2025-02-19 16:25:54 -05:00
Trevor Buckner
3221b40903 Merge pull request #4043 from dbolack-ab/marked-subsuper
Move Superscript/Subscript functions into their own module
2025-02-19 16:25:31 -05:00
Trevor Buckner
39a49a6d62 Merge branch 'master' into marked-subsuper 2025-02-19 16:21:04 -05:00
Trevor Buckner
02f63e0b02 Merge branch 'master' into Change_Hardbreak_-_to_-br- 2025-02-19 16:15:49 -05:00
Trevor Buckner
0ba943ceb0 Update Test Cases 2025-02-19 16:15:31 -05:00
Trevor Buckner
578a8d7eba Add \n after each <br> 2025-02-19 16:00:37 -05:00
Trevor Buckner
9a9d7a6b5e Merge pull request #3984 from G-Ambatte/headerNaxExtension
Extend header nav to skip headers in "top level" pages
2025-02-19 15:54:32 -05:00
Trevor Buckner
917b6b3145 Shrinking down some logic 2025-02-19 15:52:52 -05:00
Trevor Buckner
b36376f9e8 Linting 2025-02-19 13:44:28 -05:00
David Bolack
58a22750c5 Update package-lock 2025-02-19 11:38:38 -06:00
David Bolack
df1b601de7 Merge branch 'master' into marked-subsuper 2025-02-19 11:37:38 -06:00
David Bolack
1ed44282e3 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-02-19 11:36:51 -06:00
G.Ambatte
f421ce1d93 Fix missing depth styling 2025-02-19 16:10:27 +13:00
G.Ambatte
ca0f18acd6 Update getTextContent function to getHeaderContent 2025-02-19 15:22:08 +13:00
G.Ambatte
87d76ea8f6 Change topLevelPages functions for cover pages 2025-02-19 13:46:35 +13:00
G.Ambatte
9f5a29099c Address requested changes 2025-02-19 13:24:07 +13:00
G.Ambatte
0360d6b6c5 Merge branch 'master' into headerNaxExtension 2025-02-19 07:59:38 +13:00
Trevor Buckner
8d2057431b Merge pull request #3923 from dbolack-ab/writeinBrewTheme
WIP: User Brew Theme Write-in
2025-02-18 13:54:34 -05:00
Trevor Buckner
ee0d737b9c Merge branch 'master' into writeinBrewTheme 2025-02-18 13:44:56 -05:00
Trevor Buckner
cb27b26103 Merge pull request #4055 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.26.9
Bump @babel/preset-env from 7.26.8 to 7.26.9
2025-02-18 09:39:19 -05:00
dependabot[bot]
0564fb82f6 Bump @babel/preset-env from 7.26.8 to 7.26.9
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.8 to 7.26.9.
- [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.9/packages/babel-preset-env)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-18 02:06:21 +00:00
Trevor Buckner
5596f2d9da Merge pull request #4056 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.10.1
Bump mongoose from 8.10.0 to 8.10.1
2025-02-17 21:05:06 -05:00
dependabot[bot]
a11b67f139 Bump mongoose from 8.10.0 to 8.10.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.10.0 to 8.10.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.10.0...8.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 22:02:38 +00:00
Trevor Buckner
17717ea2a9 Merge pull request #4053 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.0
Bump nanoid from 5.0.9 to 5.1.0
2025-02-17 17:01:18 -05:00
dependabot[bot]
c15e7b2da3 Bump nanoid from 5.0.9 to 5.1.0
Bumps [nanoid](https://github.com/ai/nanoid) from 5.0.9 to 5.1.0.
- [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.0.9...5.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 21:53:00 +00:00
Trevor Buckner
fcca56f502 Merge pull request #4054 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.9
Bump @babel/core from 7.26.8 to 7.26.9
2025-02-17 16:51:47 -05:00
dependabot[bot]
68f66b2bac Bump @babel/core from 7.26.8 to 7.26.9
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.8 to 7.26.9.
- [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.9/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-02-17 21:47:15 +00:00
Trevor Buckner
0d71f291e7 Merge pull request #4057 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.9
Bump @babel/plugin-transform-runtime from 7.26.8 to 7.26.9
2025-02-17 16:45:56 -05:00
Trevor Buckner
fc065d250b Fix useSansSerif() case (monster stat blocks, descriptiven note, etc.)
Removed line-height value that affects only <br> height. Doesn't impact anywhere else because they all have their own explicit line-heights already.
2025-02-17 16:27:22 -05:00
Trevor Buckner
01d93b98d5 Remove LESS styling for .blank 2025-02-17 15:09:04 -05:00
Trevor Buckner
f5aa37bd5e Change Marked extension to emit <br> instead of <div class="blank"> 2025-02-17 15:08:47 -05:00
dependabot[bot]
d6d445dad5 Bump @babel/plugin-transform-runtime from 7.26.8 to 7.26.9
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.26.8 to 7.26.9.
- [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.9/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-02-17 03:58:22 +00:00
Trevor Buckner
854a2ab35e Fix /new 2025-02-13 17:56:39 -05:00
Trevor Buckner
42accdb54f linting 2025-02-13 17:54:37 -05:00
Trevor Buckner
7e5bade4fa Merge branch 'master' into writeinBrewTheme 2025-02-13 16:21:17 -05:00
Trevor Buckner
ed30a1cd7d Update other tests to pass 2025-02-13 16:20:59 -05:00
Trevor Buckner
94f478477d Add test for missing meta:theme tag 2025-02-13 16:20:50 -05:00
Trevor Buckner
50bda9455f Immediately clear errors if a theme successfully loads 2025-02-13 15:51:22 -05:00
Trevor Buckner
d8d672fada Error message if chosen theme does not have "meta:theme" tag. 2025-02-13 15:51:06 -05:00
Trevor Buckner
bf297939dc Debounce validation popup 2025-02-13 15:01:35 -05:00
Trevor Buckner
df563b9294 Change combobox default text 2025-02-13 14:53:02 -05:00
Trevor Buckner
e584eec8c2 When clicked, combobox textbox clears 2025-02-13 14:51:45 -05:00
Trevor Buckner
557178172b Merge pull request #4050 from naturalcrit/dependabot/npm_and_yarn/elliptic-6.6.1
Bump elliptic from 6.6.0 to 6.6.1
2025-02-13 14:14:17 -05:00
Trevor Buckner
45e98debbd Remove z-index for metadata editor to not cover nav bar (errors/dropdowns/etc)
The other editor panels don't have a z-index. For some reason the metadata editor does, and it covers items from the navbar
2025-02-13 00:43:14 -05:00
Trevor Buckner
0bd5ac42b6 Remove too-small height for themes + thumbnail 2025-02-13 00:40:59 -05:00
dependabot[bot]
af729de096 Bump elliptic from 6.6.0 to 6.6.1
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.6.0 to 6.6.1.
- [Commits](https://github.com/indutny/elliptic/compare/v6.6.0...v6.6.1)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 05:13:33 +00:00
Trevor Buckner
40cd53fcb8 Merge pull request #4048 from naturalcrit/dependabot/npm_and_yarn/globals-15.15.0
Bump globals from 15.14.0 to 15.15.0
2025-02-13 00:12:26 -05:00
Trevor Buckner
f326d11232 Added input validation (allows Share ID or Share URL) 2025-02-13 00:05:30 -05:00
dependabot[bot]
85ea91fed8 Bump globals from 15.14.0 to 15.15.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.14.0 to 15.15.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.14.0...v15.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 03:21:18 +00:00
David Bolack
a0c9b8849c Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-02-12 16:23:54 -06:00
Víctor Losada Hernández
ff91ebb06a Merge pull request #4022 from 5e-Cleric/update-admin
Update admin
2025-02-12 23:14:39 +01:00
Víctor Losada Hernández
21baab784e Merge branch 'update-admin' of https://github.com/5e-Cleric/homebrewery into update-admin 2025-02-12 23:11:17 +01:00
Víctor Losada Hernández
1f3a0f1f99 adapt width of table to date iso and remove colors 2025-02-12 23:10:51 +01:00
Trevor Buckner
6b4f5bd0af Linting on .less files 2025-02-12 15:30:20 -05:00
David Bolack
52cf1ddea0 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-02-12 10:03:45 -06:00
Víctor Losada Hernández
b79c5954ff minor style changes 2025-02-12 13:02:26 +01:00
Víctor Losada Hernández
9944398e4c add decent table styles 2025-02-12 12:54:34 +01:00
Víctor Losada Hernández
489f00b785 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into update-admin 2025-02-12 12:08:34 +01:00
Trevor Buckner
1a515f8d9c Merge pull request #4045 from naturalcrit/dependabot/npm_and_yarn/eslint-9.20.1
Bump eslint from 9.20.0 to 9.20.1
2025-02-11 23:16:57 -05:00
dependabot[bot]
f386ba3f45 Bump eslint from 9.20.0 to 9.20.1
Bumps [eslint](https://github.com/eslint/eslint) from 9.20.0 to 9.20.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.20.0...v9.20.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-12 03:52:31 +00:00
David Bolack
db16248afb Remove Paragraph Justifcation test execution. 2025-02-11 14:57:24 -06:00
Trevor Buckner
634450d4a9 Merge pull request #4031 from G-Ambatte/addAdminGetBrewsByUser
Add Admin function to get list of brews by username
2025-02-11 15:21:22 -05:00
G.Ambatte
559f55f781 Merge branch 'master' into addAdminGetBrewsByUser 2025-02-12 08:23:43 +13:00
G.Ambatte
64b7527ad0 Convert space indentation to tabs 2025-02-12 08:01:07 +13:00
G.Ambatte
d48d5260a4 Fix missing } 2025-02-12 07:54:15 +13:00
David Bolack
41dc78375c Merge branch 'master' into marked-subsuper 2025-02-11 12:50:46 -06:00
G.Ambatte
bbc601cf47 Simplify sort algorithm
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2025-02-12 07:42:35 +13:00
G.Ambatte
e89920bd1e Remove unneeded .then()
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2025-02-12 07:41:53 +13:00
Trevor Buckner
b77af1bcc8 Merge pull request #4040 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.26.8
Bump @babel/preset-env from 7.26.7 to 7.26.8
2025-02-11 11:22:14 -05:00
dependabot[bot]
45d188fea1 Bump @babel/preset-env from 7.26.7 to 7.26.8
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.7 to 7.26.8.
- [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.8/packages/babel-preset-env)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 16:19:50 +00:00
Trevor Buckner
1ce26ca953 Merge pull request #4041 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.8
Bump @babel/core from 7.26.7 to 7.26.8
2025-02-11 11:18:26 -05:00
dependabot[bot]
d1c0557341 Bump @babel/core from 7.26.7 to 7.26.8
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.7 to 7.26.8.
- [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.8/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-02-11 16:04:52 +00:00
Trevor Buckner
4e857a1a99 Merge pull request #4033 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.10.0
Bump mongoose from 8.9.6 to 8.10.0
2025-02-11 11:03:19 -05:00
Trevor Buckner
547ac11756 Merge branch 'master' into dependabot/npm_and_yarn/mongoose-8.10.0 2025-02-11 11:01:27 -05:00
Trevor Buckner
0e2443f772 Merge pull request #4039 from naturalcrit/dependabot/npm_and_yarn/eslint-9.20.0
Bump eslint from 9.19.0 to 9.20.0
2025-02-11 11:01:03 -05:00
dependabot[bot]
9d16f4556e Bump mongoose from 8.9.6 to 8.10.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.6 to 8.10.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.9.6...8.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 15:23:08 +00:00
dependabot[bot]
6d0d0057f6 Bump eslint from 9.19.0 to 9.20.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.19.0 to 9.20.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.19.0...v9.20.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 15:22:28 +00:00
Trevor Buckner
b8d9023c98 Merge pull request #4038 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.8
Bump @babel/plugin-transform-runtime from 7.25.9 to 7.26.8
2025-02-11 10:21:48 -05:00
Trevor Buckner
4578cf6584 Merge branch 'master' into dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.8 2025-02-11 10:19:58 -05:00
Trevor Buckner
111869d33b Merge pull request #4032 from naturalcrit/dependabot/npm_and_yarn/stylistic/stylelint-plugin-3.1.2
Bump @stylistic/stylelint-plugin from 3.1.1 to 3.1.2
2025-02-11 10:19:31 -05:00
Trevor Buckner
d0b4486e15 Merge branch 'master' into dependabot/npm_and_yarn/stylistic/stylelint-plugin-3.1.2 2025-02-11 10:15:39 -05:00
Trevor Buckner
1aed753911 Use ComboBox component for Theme Selector 2025-02-10 22:20:54 -05:00
Trevor Buckner
c080e5b191 Add author to snippetBundle 2025-02-10 22:20:12 -05:00
David Bolack
11396389ab Move Superscript/Subscript functions into their own module 2025-02-10 20:47:17 -06:00
Trevor Buckner
0bcf228881 Merge branch 'master' into writeinBrewTheme 2025-02-10 00:49:22 -05:00
Trevor Buckner
de30722554 Merge pull request #4042 from naturalcrit/cleanUpCombobox
Clean up combobox component
2025-02-10 00:48:48 -05:00
Trevor Buckner
6cfdfad7d3 Clean up combobox component 2025-02-10 00:33:33 -05:00
dependabot[bot]
a9fa0bd32d Bump @babel/plugin-transform-runtime from 7.25.9 to 7.26.8
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.25.9 to 7.26.8.
- [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.8/packages/babel-plugin-transform-runtime)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 03:10:29 +00:00
Trevor Buckner
cfbf4021dc Remove unneeded filter 2025-02-09 12:23:42 -05:00
Trevor Buckner
e3780e844d Merge branch 'writeinBrewTheme' of https://github.com/dbolack-ab/homebrewery into pr/3923 2025-02-09 12:07:31 -05:00
Trevor Buckner
659510e364 Remove unused function 2025-02-09 12:07:09 -05:00
Trevor Buckner
29da0396fd Merge branch 'master' into writeinBrewTheme 2025-02-09 12:03:06 -05:00
Trevor Buckner
c3e08181e9 Merge branch 'master' into dependabot/npm_and_yarn/stylistic/stylelint-plugin-3.1.2 2025-02-09 12:02:12 -05:00
Víctor Losada Hernández
213a719337 Merge pull request #4036 from G-Ambatte/correctChangeLogTypo
Correct typo in date of v3.17.0
2025-02-09 11:33:37 +01:00
G.Ambatte
a7a7e46e89 Correct typo in date of v3.17.0 2025-02-09 22:40:54 +13:00
dependabot[bot]
ada06c9618 Bump @stylistic/stylelint-plugin from 3.1.1 to 3.1.2
Bumps [@stylistic/stylelint-plugin](https://github.com/stylelint-stylistic/stylelint-stylistic) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/stylelint-stylistic/stylelint-stylistic/releases)
- [Changelog](https://github.com/stylelint-stylistic/stylelint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint-stylistic/stylelint-stylistic/compare/v3.1.1...v3.1.2)

---
updated-dependencies:
- dependency-name: "@stylistic/stylelint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-06 03:24:23 +00:00
G.Ambatte
03798e945d Add UI 2025-02-05 21:35:33 +13:00
G.Ambatte
6d2cbaacc0 Add Admin API for getByUser 2025-02-05 21:35:21 +13:00
Trevor Buckner
10fae6dbac Merge pull request #4026 from 5e-Cleric/fix-errorpage-error-if-brew-title-doesn't-exist
fix undefined value in errorIndex.js if brew doesn't exist
2025-02-03 14:24:30 -05:00
Víctor Losada Hernández
ebc7f055fa Merge branch 'master' into fix-errorpage-error-if-brew-title-doesn't-exist 2025-02-03 15:26:36 +01:00
Víctor Losada Hernández
ce01b6c1ff initial commit 2025-02-03 15:10:34 +01:00
Trevor Buckner
553562611f Merge pull request #4024 from naturalcrit/dependabot/npm_and_yarn/react-router-7.1.5
Bump react-router from 7.1.4 to 7.1.5
2025-02-03 00:07:11 -05:00
dependabot[bot]
423caefe1a Bump react-router from 7.1.4 to 7.1.5
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.4 to 7.1.5.
- [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.1.5/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 04:45:39 +00:00
Trevor Buckner
ae1de819ea Merge pull request #4025 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.6
Bump mongoose from 8.9.5 to 8.9.6
2025-02-02 23:44:17 -05:00
dependabot[bot]
27c4cfd25c Bump mongoose from 8.9.5 to 8.9.6
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.5 to 8.9.6.
- [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.9.5...8.9.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:16:41 +00:00
Víctor Losada Hernández
bf22104474 move padding to a more deserving place 2025-02-02 22:19:38 +01:00
Víctor Losada Hernández
c3e0a687c0 Merge branch 'master' into writeinBrewTheme 2025-02-02 22:18:20 +01:00
Víctor Losada Hernández
00a2b130eb Merge branch 'master' into headerNaxExtension 2025-02-02 16:44:30 +01:00
Víctor Losada Hernández
a504703d41 removed unnecessary files and refactored layout of dl 2025-02-01 15:47:41 +01:00
Víctor Losada Hernández
3ce9bb1310 initial commit 2025-01-31 23:20:35 +01:00
Víctor Losada Hernández
66bfc8f27b style change 2025-01-31 22:33:47 +01:00
Trevor Buckner
6c8b94453e Merge pull request #4019 from naturalcrit/update-notif-to-handle-markdown
upadte notification popup to handle markdown
2025-01-31 14:31:31 -05:00
Víctor Losada Hernández
460fb655d8 bring margin back 2025-01-31 20:11:57 +01:00
Víctor Losada Hernández
be1742d01d remove unnecessary spaces 2025-01-31 20:08:10 +01:00
Víctor Losada Hernández
5d3742aea6 Merge branch 'update-notif-to-handle-markdown' of https://github.com/naturalcrit/homebrewery into update-notif-to-handle-markdown 2025-01-31 20:02:33 +01:00
Víctor Losada Hernández
1966027289 linting & suggested changes 2025-01-31 20:02:31 +01:00
Víctor Losada Hernández
35d50cc9d1 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into update-notif-to-handle-markdown 2025-01-31 20:01:29 +01:00
Trevor Buckner
3f41306306 Merge branch 'master' into update-notif-to-handle-markdown 2025-01-31 13:43:50 -05:00
Trevor Buckner
988bf1b0a9 Merge pull request #4020 from naturalcrit/FixStyleLintImport
Fix StyleLint require to import
2025-01-31 13:42:19 -05:00
Trevor Buckner
2f1ade8463 lint 2025-01-31 13:38:25 -05:00
Trevor Buckner
518924d725 Use import, run eslint 2025-01-31 13:36:42 -05:00
Trevor Buckner
6269651c8d Update Marked to v13.0.3 2025-01-31 12:42:54 -05:00
Víctor Losada Hernández
057abcda0d reduce style between li elements 2025-01-31 12:04:40 +01:00
Víctor Losada Hernández
b6b23a787c upadte notification popup to handle markdown 2025-01-31 11:52:46 +01:00
Trevor Buckner
899004cfaf Merge pull request #4017 from naturalcrit/dependabot/npm_and_yarn/react-router-7.1.4
Bump react-router from 7.1.3 to 7.1.4
2025-01-31 00:35:03 -05:00
dependabot[bot]
7e826cd4f5 Bump react-router from 7.1.3 to 7.1.4
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.3 to 7.1.4.
- [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.1.4/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-31 05:22:30 +00:00
Trevor Buckner
3b150891bc Merge pull request #4016 from naturalcrit/dependabot/npm_and_yarn/dompurify-3.2.4
Bump dompurify from 3.2.3 to 3.2.4
2025-01-31 00:21:09 -05:00
dependabot[bot]
e87acc3f0f Bump dompurify from 3.2.3 to 3.2.4
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.3...3.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-31 05:17:57 +00:00
Trevor Buckner
b1e99f1385 Marked 12.0.2 2025-01-31 00:16:37 -05:00
Trevor Buckner
4e0b6d634d Marked 12.0.1 2025-01-31 00:12:13 -05:00
Trevor Buckner
a72f0f2f34 Merge pull request #4018 from naturalcrit/up-Marked-to-v12
Update Marked.js to v12.0.0
2025-01-31 00:08:01 -05:00
Trevor Buckner
23944f4fe0 smartypants package updated to support higher Marked versions
Next Marked versions break things; need to update incrementally
2025-01-31 00:03:44 -05:00
Trevor Buckner
c244199190 Merge pull request #4015 from naturalcrit/v3.17
Update to v3.17.0
2025-01-30 22:38:26 -05:00
Trevor Buckner
8848c06b15 Rewording. Add more detailed examples. Add Table width syntax 2025-01-30 22:37:55 -05:00
Víctor Losada Hernández
37d56f7365 compile removed items 2025-01-30 23:35:42 +01:00
Víctor Losada Hernández
e2d6b5afc4 Merge branch 'v3.17' of https://github.com/naturalcrit/homebrewery into v3.17 2025-01-30 23:33:54 +01:00
Víctor Losada Hernández
e4df577a32 remove internal changes 2025-01-30 23:33:49 +01:00
Trevor Buckner
f005cb784f Update changelog.md 2025-01-30 14:18:26 -05:00
Víctor Losada Hernández
d733b1f8f8 reformat changelog 2025-01-30 20:02:34 +01:00
Víctor Losada Hernández
d8d403ffb8 Update to v3.17.0 2025-01-30 18:54:27 +01:00
Trevor Buckner
574d68f678 Merge pull request #4004 from naturalcrit/altpageattributes
Alternate \page{curlies}
2025-01-30 09:33:13 -05:00
Trevor Buckner
1b3d7b33c6 Merge branch 'master' into altpageattributes 2025-01-29 12:11:53 -05:00
Trevor Buckner
7f4a304f04 Fix custom CSS variables 2025-01-29 12:10:50 -05:00
Trevor Buckner
d0a06b5cf7 Fix class injection 2025-01-29 12:00:36 -05:00
Trevor Buckner
6dfd44e2f1 Allow spaces between \page and {}
Consistent behavior with other curly injections
2025-01-29 11:48:18 -05:00
Trevor Buckner
03e7699b8b Merge pull request #3977 from dbolack-ab/Ubuntu_Document_Upgrade
Ubuntu document upgrade
2025-01-28 10:01:17 -05:00
Trevor Buckner
11f4275e7b Merge branch 'master' into Ubuntu_Document_Upgrade 2025-01-28 10:01:00 -05:00
Trevor Buckner
07fe1c6f19 Merge pull request #3978 from dbolack-ab/issue_3231
Wrap titles in error messages with pre blocks to prevent rendering.
2025-01-28 09:59:10 -05:00
Trevor Buckner
3e78b03785 Remove lodash again 2025-01-28 00:28:46 -05:00
Trevor Buckner
6a31d612e6 Escape to HTML entities 2025-01-28 00:24:15 -05:00
Trevor Buckner
ecd8869097 Add a comment 2025-01-28 00:17:08 -05:00
Trevor Buckner
73c2be147c Custom escape function 2025-01-28 00:13:51 -05:00
Trevor Buckner
caa290f580 Merge branch 'master' into pr/3978 2025-01-27 23:34:59 -05:00
Trevor Buckner
d69288076a Change to _.escape() to escape HTML characters 2025-01-27 23:34:50 -05:00
Trevor Buckner
be18843b09 Allow empty braces: \page{} 2025-01-27 23:27:03 -05:00
Trevor Buckner
f1ff032e1e Extract repeated pagebreak regex into a constant 2025-01-27 23:24:25 -05:00
Trevor Buckner
36df121cf6 Lint 2025-01-27 23:10:37 -05:00
Trevor Buckner
c22bb7fb92 Merge branch 'master' into altpageattributes 2025-01-27 23:06:34 -05:00
Trevor Buckner
b94bb38922 Merge pull request #4012 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.26.7
Bump @babel/preset-env from 7.26.0 to 7.26.7
2025-01-27 13:12:28 -05:00
dependabot[bot]
1576a946b0 Bump @babel/preset-env from 7.26.0 to 7.26.7
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.0 to 7.26.7.
- [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.7/packages/babel-preset-env)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 18:09:34 +00:00
Trevor Buckner
4de0a11f1a Merge pull request #4009 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.7
Bump @babel/core from 7.26.0 to 7.26.7
2025-01-27 13:08:16 -05:00
dependabot[bot]
66fd9e188b Bump @babel/core from 7.26.0 to 7.26.7
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.0 to 7.26.7.
- [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.7/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-01-27 16:36:51 +00:00
Víctor Losada Hernández
a0f44a088f Merge pull request #4013 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.14.1
Bump stylelint from 16.13.2 to 16.14.1
2025-01-27 17:35:26 +01:00
dependabot[bot]
fb20be833c Bump stylelint from 16.13.2 to 16.14.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.13.2 to 16.14.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.13.2...16.14.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 16:32:07 +00:00
Víctor Losada Hernández
fc43f95ea5 Merge pull request #4011 from naturalcrit/dependabot/npm_and_yarn/eslint-9.19.0
Bump eslint from 9.18.0 to 9.19.0
2025-01-27 17:30:13 +01:00
dependabot[bot]
29d04fe57d Bump eslint from 9.18.0 to 9.19.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.18.0 to 9.19.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.18.0...v9.19.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-01-27 03:42:09 +00:00
Trevor Buckner
bd32f5a1b8 Merge branch 'master' into altpageattributes 2025-01-24 18:54:44 -05:00
David Bolack
98c353b9fe Merge branch 'master' into writeinBrewTheme 2025-01-24 14:31:28 -06:00
David Bolack
41b80422c5 Merge branch 'master' into Ubuntu_Document_Upgrade 2025-01-24 14:25:37 -06:00
David Bolack
c1f608d02f Merge branch 'master' into issue_3231 2025-01-24 14:12:50 -06:00
David Bolack
abc830eda2 Change backticks to <pre> literals. 2025-01-24 14:09:13 -06:00
Trevor Buckner
7610466ee4 Off by 1 error 2025-01-24 01:48:18 -05:00
Trevor Buckner
9f8831eed6 Adjust display and page count when first line has \page 2025-01-24 01:16:55 -05:00
Trevor Buckner
0ac981586f Clean up 2025-01-24 01:16:22 -05:00
Víctor Losada Hernández
fc085111db Merge pull request #4001 from naturalcrit/revert-react-frame
revert react frame update
2025-01-23 13:59:31 +01:00
Víctor Losada Hernández
5e03d97869 revert react frame update 2025-01-23 13:56:07 +01:00
Trevor Buckner
a11ae6655e Merge branch 'master' into altpageattributes 2025-01-23 01:01:56 -05:00
Trevor Buckner
2471de20a9 Merge pull request #4000 from naturalcrit/CurlyStylesAsKeyValues
Parse mustache "style" properties into object instead of string
2025-01-23 01:00:48 -05:00
Trevor Buckner
eebc9c2bfa commit changes so far 2025-01-22 15:04:33 -05:00
G.Ambatte
b7241f79cb Merge branch 'master' into headerNaxExtension 2025-01-22 08:46:42 +13:00
G.Ambatte
f6c95fb8b7 Extend header nav to exclude frontCover pages 2025-01-14 08:25:46 +13:00
David Bolack
9c197ea25a Merge branch 'master' into issue_3231 2025-01-12 13:52:37 -06:00
David Bolack
80003f6c57 Return overremoved backtick 2025-01-11 08:44:11 -06:00
David Bolack
9d67724da9 Wrap titles in error messages with pre blocks to prevent rendering. 2025-01-10 23:22:22 -06:00
David Bolack
3578a7e1e2 Updated for last three LTS releases 2025-01-10 22:52:18 -06:00
David Bolack
533586f516 Rough draft of update. 2025-01-10 21:09:50 -06:00
David Bolack
591ccf564c Working changes 2025-01-10 20:22:33 -06:00
David Bolack
2d47cd2a76 Formatting cleanup 2025-01-08 09:28:37 -06:00
David Bolack
6eb938bb37 Change theme button toggle to be a bit more obvious. 2025-01-08 09:25:16 -06:00
David Bolack
94a431eec8 Update tests. 2025-01-07 22:28:12 -06:00
David Bolack
4eb71b1220 Merge branch 'master' into writeinBrewTheme 2025-01-07 22:16:39 -06:00
David Bolack
74122d9057 Display name of write in theme next to write-in
Clear user's active ThemeBundle when an incomplete/broken/invalid writein.

Needs theming help.
2025-01-07 22:11:01 -06:00
David Bolack
e7f8cda6ae Merge branch 'master' into writeinBrewTheme 2025-01-07 20:36:53 -06:00
David Bolack
b9f7e820c7 Functionally? working. 2025-01-07 20:36:38 -06:00
David Bolack
fe2d02a24c Work in progress.
Still issues with saving the state  of the theme pulldowns and collecting the written in theme.
2024-12-30 12:28:47 -06:00
David Bolack
7c357a2aa1 Attempt to save state but seems to break brew. 2024-12-29 23:23:48 -06:00
David Bolack
5eb8432544 Merge branch 'master' into writeinBrewTheme 2024-12-28 16:02:36 -06:00
David Bolack
9745daf6e2 Merge branch 'master' into writeinBrewTheme 2024-12-20 15:19:06 -06:00
David Bolack
47d7c69d1b Merge branch 'master' into writeinBrewTheme 2024-12-10 23:20:48 -06:00
David Bolack
8492c63f62 Merge branch 'master' into writeinBrewTheme 2024-12-04 20:40:14 -06:00
David Bolack
73c68fd11c Functional first pass.
Needs:

 - [ ] opinions on UI placement
 - [ ] opinions on best choice for displaying a write-in based User Brew ( flip to writin box? Add to drop-down list? )
2024-11-27 21:35:29 -06:00
54 changed files with 1275 additions and 864 deletions

View File

@@ -73,9 +73,6 @@ jobs:
- run:
name: Test - Non-Breaking Spaces
command: npm run test:non-breaking-spaces
- run:
name: Test - Paragraph Justification
command: npm run test:paragraph-justification
- run:
name: Test - Variables
command: npm run test:variables

View File

@@ -1,48 +1,48 @@
{
"extends": [
"stylelint-config-recess-order",
"stylelint-config-recommended"],
"plugins": [
"@stylistic/stylelint-plugin",
"./stylelint_plugins/declaration-colon-align.js",
"./stylelint_plugins/declaration-colon-min-space-before",
"./stylelint_plugins/declaration-block-multi-line-min-declarations"
],
"customSyntax": "postcss-less",
"rules": {
"no-descending-specificity" : null,
"at-rule-no-unknown" : null,
"function-no-unknown" : null,
"font-family-no-missing-generic-family-keyword" : null,
"font-weight-notation" : "named-where-possible",
"font-family-name-quotes" : "always-unless-keyword",
"@stylistic/indentation" : "tab",
"no-duplicate-selectors" : true,
"@stylistic/color-hex-case" : "upper",
"color-hex-length" : "long",
"@stylistic/selector-combinator-space-after" : "always",
"@stylistic/selector-combinator-space-before" : "always",
"@stylistic/selector-attribute-operator-space-before" : "never",
"@stylistic/selector-attribute-operator-space-after" : "never",
"@stylistic/selector-attribute-brackets-space-inside" : "never",
"selector-attribute-quotes" : "always",
"selector-pseudo-element-colon-notation" : "double",
"@stylistic/selector-pseudo-class-parentheses-space-inside" : "never",
"@stylistic/block-opening-brace-space-before" : "always",
"naturalcrit/declaration-colon-min-space-before" : 1,
"@stylistic/declaration-block-trailing-semicolon" : "always",
"@stylistic/declaration-colon-space-after" : "always",
"@stylistic/number-leading-zero" : "always",
"function-url-quotes" : ["always", { "except": ["empty"] }],
"function-url-scheme-disallowed-list" : ["data","http"],
"comment-whitespace-inside" : "always",
"@stylistic/string-quotes" : "single",
"@stylistic/media-feature-range-operator-space-before" : "always",
"@stylistic/media-feature-range-operator-space-after" : "always",
"@stylistic/media-feature-parentheses-space-inside" : "never",
"@stylistic/media-feature-colon-space-before" : "always",
"@stylistic/media-feature-colon-space-after" : "always",
"naturalcrit/declaration-colon-align" : true,
"naturalcrit/declaration-block-multi-line-min-declarations": 1
}
"extends": [
"stylelint-config-recess-order",
"stylelint-config-recommended"],
"plugins": [
"@stylistic/stylelint-plugin",
"./stylelint_plugins/declaration-colon-align.js",
"./stylelint_plugins/declaration-colon-min-space-before",
"./stylelint_plugins/declaration-block-multi-line-min-declarations"
],
"customSyntax": "postcss-less",
"rules": {
"no-descending-specificity" : null,
"at-rule-no-unknown" : null,
"function-no-unknown" : null,
"font-family-no-missing-generic-family-keyword" : null,
"font-weight-notation" : "named-where-possible",
"font-family-name-quotes" : "always-unless-keyword",
"@stylistic/indentation" : "tab",
"no-duplicate-selectors" : true,
"@stylistic/color-hex-case" : "upper",
"color-hex-length" : "long",
"@stylistic/selector-combinator-space-after" : "always",
"@stylistic/selector-combinator-space-before" : "always",
"@stylistic/selector-attribute-operator-space-before" : "never",
"@stylistic/selector-attribute-operator-space-after" : "never",
"@stylistic/selector-attribute-brackets-space-inside" : "never",
"selector-attribute-quotes" : "always",
"selector-pseudo-element-colon-notation" : "double",
"@stylistic/selector-pseudo-class-parentheses-space-inside" : "never",
"@stylistic/block-opening-brace-space-before" : "always",
"naturalcrit/declaration-colon-min-space-before" : 1,
"@stylistic/declaration-block-trailing-semicolon" : "always",
"@stylistic/declaration-colon-space-after" : "always",
"@stylistic/number-leading-zero" : "always",
"function-url-quotes" : ["always", { "except": ["empty"] }],
"function-url-scheme-disallowed-list" : ["data","http"],
"comment-whitespace-inside" : "always",
"@stylistic/string-quotes" : "single",
"@stylistic/media-feature-range-operator-space-before" : "always",
"@stylistic/media-feature-range-operator-space-after" : "always",
"@stylistic/media-feature-parentheses-space-inside" : "never",
"@stylistic/media-feature-colon-space-before" : "always",
"@stylistic/media-feature-colon-space-after" : "always",
"naturalcrit/declaration-colon-align" : true,
"naturalcrit/declaration-block-multi-line-min-declarations" : 1
}
}

View File

@@ -77,14 +77,100 @@ pre {
}
.varSyntaxTable th:first-of-type {
width:6cm;
width:6cm;
}
.page .exampleTable td,th {
border:1px dashed #00000030;
}
```
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Thursday 01/30/2025 - v3.17.0
{{taskList
##### 5e-Cleric
* [x] Update FAQ
* [x] Fix styling for Vault buttons and checkboxes
* [x] Improve navigation bar styling
* [x] Add feature to change username at https://www.naturalcrit.com/account
* [x] Fix Reddit link crash when title has non-latin chars
##### dbolack
* [x] Fix page shadows toolbar option
Fixes issue [#3919](https://github.com/naturalcrit/homebrewery/issues/3919)
* [x] Add `:>>>` syntax for horizontal :>>>>> spaces
* [x] Update Docker install instructions
Fixes issue [#1930](https://github.com/naturalcrit/homebrewery/issues/1930)
* [x] Allow styling pages via `\page{myStyles}` (with calculuschild)
Fixes issue [#3901](https://github.com/naturalcrit/homebrewery/issues/3901)
* [x] Update Ubuntu install instructions
Fixes issue [#1952](https://github.com/naturalcrit/homebrewery/issues/1952)
* [x] Add `:-:` `:-` `-:` syntax for paragraph alignment, similar to table column alignment; for example:
-: -: Right-aligned
:-: :-: Centered
* [x] Add `:-- 50% --:` syntax to allow setting table column widths by percentage; for example:
```
| Narrow | Wide |
|:- 10% -:|:-90%--:|
| Cell | Cell |
```
| Narrow | Wide |
|:- 10% -:|:-90%--:|
|Cell | Cell |
{exampleTable}
##### G-Ambatte
* [x] Fix crash when opening brew Properties tab
Fixes issue [#3927](https://github.com/naturalcrit/homebrewery/issues/3927)
* [x] Update error pages with steps to refresh credentials
Fixes issue [#3955](https://github.com/naturalcrit/homebrewery/issues/3955)
* [x] Add {{openSans :fas_rectangle_list: **NAVIGATION**}} menu to the viewer toolbar
##### calculuschild
* [x] Reduce display lag on large brews
##### Gazook89
* [x] Smarter detection of current page number
Fixes issue [#3824](https://github.com/naturalcrit/homebrewery/issues/3824)
##### All
* [x] Update dependencies and scripts
* [x] Refactor components and fix various errors
}}
\column
### Wednesday 11/27/2024 - v3.16.1
{{taskList
@@ -131,6 +217,8 @@ Fixes issue [#3744](https://github.com/naturalcrit/homebrewery/issues/3744)
* [x] Multiple code refactors, cleanups, and security fixes
}}
\page
### Saturday 10/12/2024 - v3.16.0
{{taskList
@@ -2053,4 +2141,4 @@ Massive changelog incoming:
* Added `phb.standalone.css` plus a build system for creating it
* Added page numbers and footer text
* Page accent now flips each page
* Page accent now flips each page

View File

@@ -1,47 +1,48 @@
require('./admin.less');
const React = require('react');
const createClass = require('create-react-class');
import './admin.less';
import React, { useEffect, useState } from 'react';
const BrewUtils = require('./brewUtils/brewUtils.jsx');
const NotificationUtils = require('./notificationUtils/notificationUtils.jsx');
import AuthorUtils from './authorUtils/authorUtils.jsx';
const tabGroups = ['brew', 'notifications'];
const tabGroups = ['brew', 'notifications', 'authors'];
const Admin = createClass({
getDefaultProps : function() {
return {};
},
const Admin = ()=>{
const [currentTab, setCurrentTab] = useState('brew');
getInitialState : function(){
return ({
currentTab : 'brew'
});
},
useEffect(()=>{
setCurrentTab(localStorage.getItem('hbAdminTab'));
}, []);
handleClick : function(newTab){
if(this.state.currentTab === newTab) return;
this.setState({
currentTab : newTab
});
},
useEffect(()=>{
localStorage.setItem('hbAdminTab', currentTab);
}, [currentTab]);
render : function(){
return <div className='admin'>
return (
<div className='admin'>
<header>
<div className='container'>
<i className='fas fa-rocket' />
homebrewery admin
The Homebrewery Admin Page
<a href='/'>back to homepage</a>
</div>
</header>
<main className='container'>
<nav className='tabs'>
{tabGroups.map((tab, idx)=>{ return <button className={tab===this.state.currentTab ? 'active' : ''} key={idx} onClick={()=>{ return this.handleClick(tab); }}>{tab.toUpperCase()}</button>; })}
{tabGroups.map((tab, idx)=>(
<button
className={tab === currentTab ? 'active' : ''}
key={idx}
onClick={()=>setCurrentTab(tab)}>
{tab.toUpperCase()}
</button>
))}
</nav>
{this.state.currentTab==='brew' && <BrewUtils />}
{this.state.currentTab==='notifications' && <NotificationUtils />}
{currentTab === 'brew' && <BrewUtils />}
{currentTab === 'notifications' && <NotificationUtils />}
{currentTab === 'authors' && <AuthorUtils />}
</main>
</div>;
}
});
</div>
);
};
module.exports = Admin;

View File

@@ -22,7 +22,7 @@ body {
}
:where(.admin) {
padding-bottom : 50px;
header {
padding : 20px 0px;
margin-bottom : 30px;
@@ -30,6 +30,7 @@ body {
color : white;
background-color : @red;
i { margin-right : 30px; }
a { float : right; }
}
hr { margin : 30px 0px; }
@@ -48,21 +49,23 @@ body {
}
dl {
@maxItemWidth : 132px;
display : grid;
grid-template-columns : 120px 1fr;
row-gap : 10px;
align-items : center;
justify-items : start;
padding-top : 0.5em;
dt {
float : left;
width : @maxItemWidth;
clear : left;
text-align : right;
float : left;
clear : left;
height : fit-content;
font-weight : 900;
text-align : right;
&::after { content : ' : '; }
}
dd {
height : 1em;
padding : 0 0 0.5em 0;
margin-left : @maxItemWidth + 6px;
}
dd { height : fit-content; }
}
.tabs button {
margin-right : 3px;
margin-left : 3px;
@@ -90,11 +93,45 @@ body {
}
}
table {
padding : 10px;
tr {
border-bottom : 1px solid;
&:last-of-type { border : none; }
&:nth-child(even) { background : #DDDDDD; }
}
thead {
background : rgb(193,236,230);
border-bottom : 2px solid;
}
th, td {
padding : 5px 10px;
vertical-align : middle;
text-align : center;
border-right : 1px solid;
&:last-child { border-right : none; }
}
th { font-weight : 900; }
td {
&:first-child {
font-weight : 900;
text-align : left;
}
}
}
.error {
background: rgb(178, 54, 54);
color:white;
font-weight: 900;
margin-block:10px;
padding:10px;
float : right;
padding : 10px;
margin-block : 10px;
font-weight : 900;
color : white;
background : rgb(178, 54, 54);
}
}

View File

@@ -0,0 +1,87 @@
import './authorLookup.less';
import React from 'react';
import request from 'superagent';
const authorLookup = ()=>{
const [author, setAuthor] = React.useState('');
const [searching, setSearching] = React.useState(false);
const [results, setResults] = React.useState([]);
const lookup = async ()=>{
if(!author) return;
setSearching(true);
setResults([]);
const brews = await request.get(`/admin/user/list/${author}`);
setResults(brews.body);
setSearching(false);
};
const renderResults = ()=>{
if(results.length == 0) return <>
<h2>Results</h2>
<p>None found.</p>
</>;
return <>
<h2>{`Results - ${results.length} brews` }</h2>
<table className='resultsTable'>
<thead>
<tr>
<th>Title</th>
<th>Share</th>
<th>Edit</th>
<th>Last Update</th>
<th>Storage</th>
</tr>
</thead>
<tbody>
{results
.sort((a, b)=>{ // Sort brews from most recently updated
if(a.updatedAt > b.updatedAt) return -1;
return 1;
})
.map((brew, idx)=>{
return <tr key={idx}>
<td><strong>{brew.title}</strong></td>
<td><a href={`/share/${brew.shareId}`}>{brew.shareId}</a></td>
<td>{brew.editId}</td>
<td style={{ width: '200px' }}>{brew.updatedAt}</td>
<td>{brew.googleId ? 'Google' : 'Homebrewery'}</td>
</tr>;
})}
</tbody>
</table>
</>;
};
const handleKeyPress = (evt)=>{
if(evt.key === 'Enter') return lookup();
};
const handleChange = (evt)=>{
setAuthor(evt.target.value);
};
return (
<div className='authorLookup'>
<div className='authorLookupInputs'>
<h2>Author Lookup</h2>
<label className='field'>
Author Name:
<input className='fieldInput' value={author} onKeyDown={handleKeyPress} onChange={handleChange} />
<button onClick={lookup}>
<i className={`fas ${searching ? 'fa-spin fa-spinner' : 'fa-search'}`} />
</button>
</label>
</div>
<div className='authorLookupResults'>
{renderResults()}
</div>
</div>
);
};
module.exports = authorLookup;

View File

@@ -0,0 +1,29 @@
.authorLookup {
position : relative;
display : flex;
flex-direction : column;
.field {
display : flex;
gap : 5px;
align-items : center;
justify-items : stretch;
width : 100%;
margin-bottom : 20px;
input {
height : 33px;
padding : 0px 10px;
margin-bottom : unset;
font-family : monospace;
}
button {
width: 50px;
i { margin-right : 10px; }
}
}
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import AuthorLookup from './authorLookup/authorLookup.jsx';
const authorUtils = ()=>{
return (
<section className='authorUtils'>
<AuthorLookup />
</section>
);
};
module.exports = authorUtils;

View File

@@ -1,10 +1,8 @@
require('./brewCleanup.less');
const React = require('react');
const createClass = require('create-react-class');
const request = require('superagent');
const BrewCleanup = createClass({
displayName : 'BrewCleanup',
getDefaultProps(){
@@ -39,9 +37,9 @@ const BrewCleanup = createClass({
if(!this.state.primed) return;
if(!this.state.count){
return <div className='removeBox'>No Matching Brews found.</div>;
return <div className='result noBrews'>No Matching Brews found.</div>;
}
return <div className='removeBox'>
return <div className='result'>
<button onClick={this.cleanup} className='remove'>
{this.state.pending
? <i className='fas fa-spin fa-spinner' />
@@ -52,7 +50,7 @@ const BrewCleanup = createClass({
</div>;
},
render(){
return <div className='BrewCleanup'>
return <div className='brewUtil brewCleanup'>
<h2> Brew Cleanup </h2>
<p>Removes very short brews to tidy up the database</p>
@@ -65,7 +63,7 @@ const BrewCleanup = createClass({
{this.renderPrimed()}
{this.state.error
&& <div className='error'>{this.state.error.toString()}</div>
&& <div className='error noBrews'>{this.state.error.toString()}</div>
}
</div>;
}

View File

@@ -1,9 +0,0 @@
.BrewCleanup {
.removeBox {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
}

View File

@@ -1,10 +1,7 @@
require('./brewCompress.less');
const React = require('react');
const createClass = require('create-react-class');
const request = require('superagent');
const BrewCompress = createClass({
displayName : 'BrewCompress',
getDefaultProps(){
@@ -53,9 +50,9 @@ const BrewCompress = createClass({
if(!this.state.primed) return;
if(!this.state.count){
return <div className='removeBox'>No Matching Brews found.</div>;
return <div className='result noBrews'>No Matching Brews found.</div>;
}
return <div className='removeBox'>
return <div className='result'>
<button onClick={this.cleanup} className='remove'>
{this.state.pending
? <i className='fas fa-spin fa-spinner' />
@@ -69,7 +66,7 @@ const BrewCompress = createClass({
</div>;
},
render(){
return <div className='BrewCompress'>
return <div className='brewUtil brewCompress'>
<h2> Brew Compression </h2>
<p>Compresses the text in brews to binary</p>

View File

@@ -1,9 +0,0 @@
.BrewCompress {
.removeBox {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
}

View File

@@ -1,5 +1,3 @@
require('./brewLookup.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
@@ -55,7 +53,7 @@ const BrewLookup = createClass({
renderFoundBrew(){
const brew = this.state.foundBrew;
return <div className='foundBrew'>
return <div className='result'>
<dl>
<dt>Title</dt>
<dd>{brew.title}</dd>
@@ -90,7 +88,7 @@ const BrewLookup = createClass({
},
render(){
return <div className='brewLookup'>
return <div className='brewUtil brewLookup'>
<h2>Brew Lookup</h2>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' />
<button onClick={this.lookup}>
@@ -106,7 +104,7 @@ const BrewLookup = createClass({
{this.state.foundBrew
? this.renderFoundBrew()
: <div className='noBrew'>No brew found.</div>
: <div className='result noBrew'>No brew found.</div>
}
</div>;
}

View File

@@ -1,6 +0,0 @@
.brewLookup {
.cleanButton {
display : inline-block;
width : 100%;
}
}

View File

@@ -1,6 +1,6 @@
const React = require('react');
const createClass = require('create-react-class');
require('./brewUtils.less');
const BrewCleanup = require('./brewCleanup/brewCleanup.jsx');
const BrewLookup = require('./brewLookup/brewLookup.jsx');

View File

@@ -0,0 +1,29 @@
.brewUtil {
.result {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
.cleanButton {
display : inline-block;
width : 100%;
}
}
.stats {
position : relative;
.pending {
position : absolute;
top : 0.5em;
left : 100px;
width : 100%;
height : 100%;
}
&:has(.pending) { opacity : 0.5; }
dl { grid-template-columns : 200px 250px; }
}

View File

@@ -1,11 +1,8 @@
require('./stats.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
const request = require('superagent');
const Stats = createClass({
displayName : 'Stats',
getDefaultProps(){
@@ -14,7 +11,8 @@ const Stats = createClass({
getInitialState(){
return {
stats : {
totalBrews : 0
totalBrews : 0,
totalPublishedBrews : 0
},
fetching : false
};
@@ -29,11 +27,13 @@ const Stats = createClass({
.finally(()=>this.setState({ fetching: false }));
},
render(){
return <div className='Stats'>
return <div className='brewUtil stats'>
<h2> Stats </h2>
<dl>
<dt>Total Brew Count</dt>
<dd>{this.state.stats.totalBrews}</dd>
<dt>Total Brews Published</dt>
<dd>{this.state.stats.totalPublishedBrews}</dd>
</dl>
{this.state.fetching

View File

@@ -1,13 +0,0 @@
.Stats {
position : relative;
.pending {
position : absolute;
top : 0px;
left : 0px;
width : 100%;
height : 100%;
background-color : rgba(238,238,238, 0.5);
}
}

View File

@@ -6,18 +6,21 @@
.field {
display : grid;
grid-template-columns : 120px 150px;
grid-template-columns : 120px 200px;
align-items : center;
justify-items : stretch;
width : 100%;
margin-bottom : 20px;
input {
height : 33px;
padding : 0px 10px;
margin-bottom : unset;
font-family : monospace;
&[type="date"] {
width:14ch;
}
}
textarea {

View File

@@ -1,8 +1,8 @@
.notificationLookup {
width : 450px;
height : fit-content;
.noNotification { margin-block : 20px; }
.notificationList {
display : flex;
flex-direction : column;
@@ -30,11 +30,6 @@
font-size : 20px;
font-weight : 900;
}
dl dt{
font-weight: 900;
}
}
}
.noNotification { margin-block : 20px; }
}

View File

@@ -45,6 +45,7 @@ const Combobox = createClass({
},
handleDropdown : function(show){
this.setState({
value : show ? '' : this.props.default,
showDropdown : show,
inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false
});
@@ -58,10 +59,10 @@ const Combobox = createClass({
this.props.onEntry(e);
});
},
handleSelect : function(e){
handleSelect : function(value, data=value){
this.setState({
value : e.currentTarget.getAttribute('data-value')
}, ()=>{this.props.onSelect(this.state.value);});
value : value
}, ()=>{this.props.onSelect(data);});
;
},
renderTextInput : function(){
@@ -78,10 +79,11 @@ const Combobox = createClass({
if(!e.target.checkValidity()){
this.setState({
value : this.props.default
}, ()=>this.props.onEntry(e));
});
}
}}
/>
<i className='fas fa-caret-down'/>
</div>
);
},
@@ -92,11 +94,10 @@ const Combobox = createClass({
const filterOn = _.isString(this.props.autoSuggest.filterOn) ? [this.props.autoSuggest.filterOn] : this.props.autoSuggest.filterOn;
const filteredArrays = filterOn.map((attr)=>{
const children = dropdownChildren.filter((item)=>{
if(suggestMethod === 'includes'){
if(suggestMethod === 'includes')
return item.props[attr]?.toLowerCase().includes(this.state.value.toLowerCase());
} else if(suggestMethod === 'startsWith'){
if(suggestMethod === 'startsWith')
return item.props[attr]?.toLowerCase().startsWith(this.state.value.toLowerCase());
}
});
return children;
});
@@ -111,7 +112,7 @@ const Combobox = createClass({
},
render : function () {
const dropdownChildren = this.state.options.map((child, i)=>{
const clone = React.cloneElement(child, { onClick: (e)=>this.handleSelect(e) });
const clone = React.cloneElement(child, { onClick: ()=>this.handleSelect(child.props.value, child.props.data) });
return clone;
});
return (

View File

@@ -1,50 +1,46 @@
.dropdown-container {
position:relative;
input {
width: 100%;
}
.dropdown-options {
position:absolute;
background-color: white;
z-index: 100;
width: 100%;
border: 1px solid gray;
overflow-y: auto;
max-height: 200px;
position : relative;
input { width : 100%; }
.item i {
position : absolute;
right : 10px;
color : black;
}
.dropdown-options {
position : absolute;
z-index : 100;
width : 100%;
max-height : 200px;
overflow-y : auto;
background-color : white;
border : 1px solid gray;
&::-webkit-scrollbar {
width: 14px;
}
&::-webkit-scrollbar-track {
background: #ffffff;
}
&::-webkit-scrollbar-thumb {
background-color: #949494;
border-radius: 10px;
border: 3px solid #ffffff;
}
.item {
position:relative;
font-size: 11px;
font-family: Open Sans;
padding: 5px;
cursor: default;
margin: 0 3px;
//border-bottom: 1px solid darkgray;
&:hover {
filter: brightness(120%);
background-color: rgb(163, 163, 163);
}
.detail {
width:100%;
text-align: left;
color: rgb(124, 124, 124);
font-style:italic;
font-size: 9px;
}
}
}
&::-webkit-scrollbar { width : 14px; }
&::-webkit-scrollbar-track { background : #FFFFFF; }
&::-webkit-scrollbar-thumb {
background-color : #949494;
border : 3px solid #FFFFFF;
border-radius : 10px;
}
.item {
position : relative;
padding : 5px;
margin : 0 3px;
font-family : "Open Sans";
font-size : 11px;
cursor : default;
&:hover {
background-color : rgb(163, 163, 163);
filter : brightness(120%);
}
.detail {
width : 100%;
font-size : 9px;
font-style : italic;
color : rgb(124, 124, 124);
text-align : left;
}
}
}
}

View File

@@ -17,10 +17,9 @@ const dedent = require('dedent-tabs').default;
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 PAGE_HEIGHT = 1056;
const INITIAL_CONTENT = dedent`
@@ -78,7 +77,7 @@ const BrewPage = (props)=>{
};
}, []);
return <div className={props.className} id={`p${props.index + 1}`} data-index={props.index} ref={pageRef} style={props.style}>
return <div className={props.className} id={`p${props.index + 1}`} data-index={props.index} ref={pageRef} style={props.style} {...props.attributes}>
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
</div>;
};
@@ -126,7 +125,7 @@ const BrewRenderer = (props)=>{
if(props.renderer == 'legacy') {
rawPages = props.text.split('\\page');
} else {
rawPages = props.text.split(/^\\page$/gm);
rawPages = props.text.split(PAGEBREAK_REGEX_V3);
}
const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{
@@ -173,20 +172,34 @@ const BrewRenderer = (props)=>{
const renderPage = (pageText, index)=>{
const styles = {
let styles = {
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
// Add more conditions as needed
};
let classes = 'page';
let attributes = {};
if(props.renderer == 'legacy') {
const html = MarkdownLegacy.render(pageText);
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
} 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;
if(injectedTags) {
styles = { ...styles, ...injectedTags.styles };
styles = _.mapKeys(styles, (v, k) => k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
classes = [classes, injectedTags.classes].join(' ').trim();
attributes = injectedTags.attributes;
}
pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line
}
pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
const html = Markdown.render(pageText, index);
return <BrewPage className='page' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />;
}
};

View File

@@ -3,7 +3,6 @@ require('./headerNav.less');
import * as React from 'react';
import * as _ from 'lodash';
const MAX_TEXT_LENGTH = 40;
const HeaderNav = React.forwardRef(({}, pagesRef)=>{
@@ -11,11 +10,30 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
const renderHeaderLinks = ()=>{
if(!pagesRef.current) return;
// Top Level Pages
// Pages that contain an element with a specified class (e.g. cover pages, table of contents)
// will NOT have its content scanned for navigation headers, instead displaying a custom label
// ---
// The property name is class that will be used for detecting the page is a top level page
// The property value is a function that returns the text to be used
const topLevelPages = {
'.frontCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Cover: ${text}` : 'Cover Page'; },
'.insideCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Interior: ${text}` : 'Interior Cover Page'; },
'.partCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Section: ${text}` : 'Section Cover Page'; },
'.backCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Back: ${text}` : 'Rear Cover Page'; },
'.toc' : ()=>{ return 'Table of Contents'; },
};
const getHeaderContent = el => el.querySelector('h1')?.textContent;
const topLevelPageSelector = Object.keys(topLevelPages).join(',');
const selector = [
'.pages > .page', // All page elements, which by definition have IDs
'.page:not(:has(.toc)) > [id]', // All direct children of non-ToC .page with an ID (Legacy)
'.page:not(:has(.toc)) > .columnWrapper > [id]', // All direct children of non-ToC .page > .columnWrapper with an ID (V3)
'.page:not(:has(.toc)) h2', // All non-ToC H2 titles, like Monster frame titles
'.pages > .page', // All page elements, which by definition have IDs
`.page:not(:has(${topLevelPageSelector})) > [id]`, // All direct children of non-excluded .pages with an ID (Legacy)
`.page:not(:has(${topLevelPageSelector})) > .columnWrapper > [id]`, // All direct children of non-excluded .page > .columnWrapper with an ID (V3)
`.page:not(:has(${topLevelPageSelector})) h2`, // All non-excluded H2 titles, like Monster frame titles
];
const elements = pagesRef.current.querySelectorAll(selector.join(','));
if(!elements) return;
@@ -23,45 +41,37 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
// navList is a list of objects which have the following structure:
// {
// depth : how deeply indented the item should be
// text : the text to display in the nav link
// link : the hyperlink to navigate to when clicked
// className : [optional] the class to apply to the nav link for styling
// depth : how deeply indented the item should be
// text : the text to display in the nav link
// link : the hyperlink to navigate to when clicked
// className : [optional] the class to apply to the nav link for styling
// }
elements.forEach((el)=>{
if(el.className.match(/\bpage\b/)) {
let text = `Page ${el.id.slice(1)}`; // The ID of a page *should* always be equal to `p` followed by the page number
if(el.querySelector('.toc')){ // If the page contains a table of contents, add "- Contents" to the display text
text += ' - Contents';
};
navList.push({
depth : 0, // Pages are always at the least indented level
text : text,
link : el.id,
className : 'pageLink'
});
return;
}
if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
navList.push({
depth : el.localName[1], // Depth is set by the header level
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
link : el.id
});
return;
}
navList.push({
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'
const navEntry = { // Default structure of a navList entry
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
});
});
return _.map(navList, (navItem, index)=>{
return <HeaderNavItem {...navItem} key={index} />;
}
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
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
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 <nav className='headerNav'>
@@ -69,8 +79,7 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
{renderHeaderLinks()}
</ul>
</nav>;
}
);
});
const HeaderNavItem = ({ link, text, depth, className })=>{

View File

@@ -35,11 +35,11 @@
font-weight: 900;
}
@depths: 1,2,3,4,5,6,7;
@depths: 0,1,2,3,4,5,6,7;
each(@depths, {
&.depth-@{value} {
padding-left: ((@value - 1) * 0.5em);
padding-left: ((@value) * 0.5em);
}
});
}

View File

@@ -1,6 +1,7 @@
require('./notificationPopup.less');
import React, { useEffect, useState } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import Dialog from '../../../components/dialog.jsx';
@@ -40,11 +41,10 @@ const NotificationPopup = ()=>{
const renderNotificationsList = ()=>{
if(error) return <div className='error'>{error}</div>;
return notifications.map((notification)=>(
<li key={notification.dismissKey} >
<em>{notification.title}</em><br />
<p dangerouslySetInnerHTML={{ __html: notification.text }}></p>
<p dangerouslySetInnerHTML={{ __html: Markdown.render(notification.text) }}></p>
</li>
));
};

View File

@@ -48,17 +48,41 @@
}
ul {
margin-top : 15px;
font-size : 0.8em;
font-size : 0.9em;
list-style-position : outside;
list-style-type : disc;
li {
margin-top : 1.4em;
font-size : 0.8em;
line-height : 1.4em;
em {
text-transform:capitalize;
font-weight : 800;
padding-left : 1em;
margin-top : 1.5em;
font-size : 0.9em;
line-height : 1.5em;
em {
font-weight : 800;
text-transform : capitalize;
}
li {
margin-top : 0;
line-height : 1.2em;
list-style-type : square;
}
}
ul ul,ol ol,ul ol,ol ul {
margin-bottom : 0px;
margin-left : 1.5em;
}
}
}
/* Markdown styling */
code {
padding : 0.1em 0.5em;
font-family : 'Courier New', 'Courier', monospace;
overflow-wrap : break-word;
white-space : pre-wrap;
background : #08115A;
border-radius : 2px;
}
pre code {
display : inline-block;
width : 100%;
}
}

View File

@@ -12,7 +12,8 @@ const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
const SNIPPETBAR_HEIGHT = 25;
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m;
const SNIPPETBAR_HEIGHT = 25;
const DEFAULT_STYLE_TEXT = dedent`
/*=======--- Example CSS styling ---=======*/
/* Any CSS here will apply to your document! */
@@ -126,15 +127,15 @@ const Editor = createClass({
},
updateCurrentCursorPage : function(cursor) {
const lines = this.props.brew.text.split('\n').slice(0, cursor.line + 1);
const pageRegex = this.props.brew.renderer == 'V3' ? /^\\page$/ : /\\page/;
const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1);
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
this.props.onCursorPageChange(currentPage);
},
updateCurrentViewPage : function(topScrollLine) {
const lines = this.props.brew.text.split('\n').slice(0, topScrollLine + 1);
const pageRegex = this.props.brew.renderer == 'V3' ? /^\\page$/ : /\\page/;
const lines = this.props.brew.text.split('\n').slice(1, topScrollLine + 1);
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
this.props.onViewPageChange(currentPage);
},
@@ -174,7 +175,7 @@ const Editor = createClass({
for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear();
let editorPageCount = 2; // start page count from page 2
let editorPageCount = 1; // start page count from page 1
_.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{
@@ -190,7 +191,10 @@ const Editor = createClass({
// Styling for \page breaks
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
(this.props.renderer == 'V3' && line.match(/^\\page$/))) {
(this.props.renderer == 'V3' && line.match(PAGEBREAK_REGEX_V3))) {
if(lineNumber > 0) // Since \page is optional on first line of document,
editorPageCount += 1; // don't use it to increment page count; stay at 1
// add back the original class 'background' but also add the new class '.pageline'
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
@@ -199,8 +203,6 @@ const Editor = createClass({
textContent : editorPageCount
});
codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement);
editorPageCount += 1;
};
// New Codemirror styling for V3 renderer
@@ -358,7 +360,7 @@ const Editor = createClass({
if(!this.isText() || isJumping)
return;
const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/;
const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit);
const targetLine = textString.match('\n') ? textString.split('\n').length - 1 : -1;
@@ -454,6 +456,7 @@ const Editor = createClass({
rerenderParent={this.rerenderParent} />
<MetadataEditor
metadata={this.props.brew}
themeBundle={this.props.themeBundle}
onChange={this.props.onMetaChange}
reportError={this.props.reportError}
userThemes={this.props.userThemes}/>

View File

@@ -40,6 +40,7 @@ const MetadataEditor = createClass({
theme : '5ePHB',
lang : 'en'
},
onChange : ()=>{},
reportError : ()=>{}
};
@@ -47,7 +48,7 @@ const MetadataEditor = createClass({
getInitialState : function(){
return {
showThumbnail : true
showThumbnail : true
};
},
@@ -67,6 +68,11 @@ const MetadataEditor = createClass({
const inputRules = validations[name] ?? [];
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
const debouncedReportValidity = _.debounce((target, errMessage) => {
callIfExists(target, 'setCustomValidity', errMessage);
callIfExists(target, 'reportValidity');
}, 300); // 300ms debounce delay, adjust as needed
// if no validation rules, save to props
if(validationErr.length === 0){
callIfExists(e.target, 'setCustomValidity', '');
@@ -74,14 +80,16 @@ const MetadataEditor = createClass({
...this.props.metadata,
[name] : e.target.value
});
return true;
} else {
// if validation issues, display built-in browser error popup with each error.
const errMessage = validationErr.map((err)=>{
return `- ${err}`;
}).join('\n');
callIfExists(e.target, 'setCustomValidity', errMessage);
callIfExists(e.target, 'reportValidity');
debouncedReportValidity(e.target, errMessage);
return false;
}
},
@@ -112,6 +120,14 @@ const MetadataEditor = createClass({
handleTheme : function(theme){
this.props.metadata.renderer = theme.renderer;
this.props.metadata.theme = theme.path;
this.props.onChange(this.props.metadata, 'theme');
},
handleThemeWritein : function(e) {
const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in
this.props.metadata.theme = shareId;
this.props.onChange(this.props.metadata, 'theme');
},
@@ -200,7 +216,7 @@ const MetadataEditor = createClass({
if(theme.path == this.props.metadata.shareId) return;
const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`;
const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`;
return <div className='item' key={`${renderer}_${theme.name}`} onClick={()=>this.handleTheme(theme)} title={''}>
return <div className='item' key={`${renderer}_${theme.name}`} value={`${theme.author ?? renderer} : ${theme.name}`} data={theme} title={''}>
{theme.author ?? renderer} : {theme.name}
<div className='texture-container'>
<img src={texture}/>
@@ -210,26 +226,40 @@ const MetadataEditor = createClass({
<img src={preview}/>
</div>
</div>;
});
}).filter(Boolean);
};
const currentRenderer = this.props.metadata.renderer;
const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme]
?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` };
const currentThemeDisplay = this.props.themeBundle?.name ? `${this.props.themeBundle.author ?? currentRenderer} : ${this.props.themeBundle.name}` : 'No Theme Selected';
let dropdown;
if(currentRenderer == 'legacy') {
dropdown =
<Nav.dropdown className='disabled value' trigger='disabled'>
<div> {`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i> </div>
</Nav.dropdown>;
<div className='disabled value' trigger='disabled'>
<div> Themes are not supported in the Legacy Renderer </div>
</div>;
} else {
dropdown =
<Nav.dropdown className='value' trigger='click'>
<div> {currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name} <i className='fas fa-caret-down'></i> </div>
{listThemes(currentRenderer)}
</Nav.dropdown>;
<div className='value'>
<Combobox trigger='click'
className='themes-dropdown'
default={currentThemeDisplay}
placeholder='Select from below, or enter the Share URL or ID of a brew with the meta:theme tag'
onSelect={(value)=>this.handleTheme(value)}
onEntry={(e)=>{
e.target.setCustomValidity(''); //Clear the validation popup while typing
if(this.handleFieldChange('theme', e))
this.handleThemeWritein(e);
}}
options={listThemes(currentRenderer)}
autoSuggest={{
suggestMethod : 'includes',
clearAutoSuggestOnClick : true,
filterOn : ['value', 'title']
}}
/>
<small>Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew.</small>
</div>;
}
return <div className='field themes'>
@@ -244,15 +274,13 @@ const MetadataEditor = createClass({
return _.map(langCodes.sort(), (code, index)=>{
const localName = new Intl.DisplayNames([code], { type: 'language' });
const englishName = new Intl.DisplayNames('en', { type: 'language' });
return <div className='item' title={`${englishName.of(code)}`} key={`${index}`} data-value={`${code}`} data-detail={`${localName.of(code)}`}>
{`${code}`}
<div className='detail'>{`${localName.of(code)}`}</div>
return <div className='item' title={englishName.of(code)} key={`${index}`} value={code} detail={localName.of(code)}>
{code}
<div className='detail'>{localName.of(code)}</div>
</div>;
});
};
const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500);
return <div className='field language'>
<label>language</label>
<div className='value'>
@@ -263,16 +291,15 @@ const MetadataEditor = createClass({
onSelect={(value)=>this.handleLanguage(value)}
onEntry={(e)=>{
e.target.setCustomValidity(''); //Clear the validation popup while typing
debouncedHandleFieldChange('lang', e);
this.handleFieldChange('lang', e);
}}
options={listLanguages()}
autoSuggest={{
suggestMethod : 'startsWith',
clearAutoSuggestOnClick : true,
filterOn : ['data-value', 'data-detail', 'title']
filterOn : ['value', 'detail', 'title']
}}
>
</Combobox>
/>
<small>Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.</small>
</div>
@@ -345,7 +372,7 @@ const MetadataEditor = createClass({
placeholder='add tag' unique={true}
values={this.props.metadata.tags}
onChange={(e)=>this.handleFieldChange('tags', e)}
/>
/>
<div className='field systems'>
<label>systems</label>
@@ -370,7 +397,7 @@ const MetadataEditor = createClass({
values={this.props.metadata.invitedAuthors}
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}
/>
/>
<h2>Privacy</h2>

View File

@@ -1,9 +1,12 @@
@import 'naturalcrit/styles/colors.less';
.userThemeName {
padding-left: 10px;
padding-right: 10px;
}
.metadataEditor {
position : absolute;
z-index : 5;
box-sizing : border-box;
width : 100%;
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
@@ -71,8 +74,7 @@
border : 1px solid gray;
&:focus { outline : 1px solid #444444; }
}
&.thumbnail {
height : 1.4em;
&.thumbnail, &.themes{
label { line-height : 2.0em; }
.value {
overflow : hidden;
@@ -88,6 +90,17 @@
}
}
&.themes{
.value {
overflow : visible;
text-overflow : auto;
}
button {
padding-left: 5px;
padding-right: 5px;
}
}
&.description {
flex : 1;
textarea.value {
@@ -156,89 +169,73 @@
}
.themes.field {
.navDropdownContainer {
& .dropdown-container {
position : relative;
z-index : 100;
background-color : white;
&.disabled {
font-style : italic;
color : dimgray;
background-color : darkgray;
}
& > div:first-child {
padding : 3px 3px;
background-color : inherit;
border : 1px solid gray;
i { float : right; }
&:hover {
color : white;
background-color : @blue;
}
& .dropdown-options {
overflow-y : visible;
}
.disabled {
font-style : italic;
color : dimgray;
background-color : darkgray;
}
.item {
position : relative;
padding : 3px 3px;
overflow : visible;
background-color : white;
border-top : 1px solid rgb(118, 118, 118);
.preview {
position : absolute;
top : 0;
right : 0;
z-index : 1;
display : flex;
flex-direction : column;
width : 200px;
overflow : hidden;
color : black;
background : #CCCCCC;
border-radius : 5px;
box-shadow : 0 0 5px black;
opacity : 0;
transition : opacity 250ms ease;
h6 {
padding-block : 0.5em;
padding-inline : 1em;
font-weight : 900;
border-bottom : 2px solid hsl(0,0%,40%);
}
}
.navDropdown .item > p {
width : 45%;
height : 1.1em;
overflow : hidden;
text-overflow : ellipsis;
white-space : nowrap;
}
.navDropdown {
position : absolute;
width : 100%;
box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3);
.item {
position : relative;
padding : 3px 3px;
overflow : visible;
background-color : white;
border-top : 1px solid rgb(118, 118, 118);
.preview {
position : absolute;
top : 0;
right : 0;
z-index : 1;
display : flex;
flex-direction : column;
width : 200px;
overflow : hidden;
color : black;
background : #CCCCCC;
border-radius : 5px;
box-shadow : 0 0 5px black;
opacity : 0;
transition : opacity 250ms ease;
h6 {
padding-block : 0.5em;
padding-inline : 1em;
font-weight : 900;
border-bottom : 2px solid hsl(0,0%,40%);
}
}
&:hover {
color : white;
background-color : @blue;
}
&:hover > .preview { opacity : 1; }
.texture-container {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
min-height : 100%;
overflow : hidden;
> img {
position : absolute;
top : 0px;
right : 0;
width : 50%;
min-height : 100%;
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
mask-image : linear-gradient(90deg, transparent, black 20%);
}
}
.texture-container {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
min-height : 100%;
overflow : hidden;
> img {
position : absolute;
top : 0;
right : 0;
width : 50%;
min-height : 100%;
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
mask-image : linear-gradient(90deg, transparent, black 20%);
}
}
&:hover {
color : white;
background-color : @blue;
filter : unset;
}
&:hover > .preview { opacity : 1; }
}
}

View File

@@ -27,6 +27,19 @@ module.exports = {
(value)=>{
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) => {
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;
return 'Must be a valid Share URL or a 12-character ID.';
}
]
};

View File

@@ -116,6 +116,19 @@ const ErrorNavItem = createClass({
</Nav.item>;
}
if(HBErrorCode === '10') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like the brew you have selected
as a theme is not tagged for use as a
theme. Verify that
brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> has the <span className='lowercase'>meta:theme</span> tag!
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>

View File

@@ -59,11 +59,6 @@
padding-left : 1.25em;
list-style : square;
}
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
}
}

View File

@@ -379,7 +379,7 @@ const EditPage = createClass({
const title = `${this.props.brew.title} ${systems}`;
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](${global.config.publicUrl}/share/${shareLink})**`;
**[Homebrewery Link](${global.config.baseUrl}/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`;
},
@@ -410,7 +410,7 @@ const EditPage = createClass({
<Nav.item color='blue' href={`/share/${shareLink}`}>
view
</Nav.item>
<Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`${global.config.publicUrl}/share/${shareLink}`);}}>
<Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`${global.config.baseUrl}/share/${shareLink}`);}}>
copy url
</Nav.item>
<Nav.item color='blue' href={this.getRedditLink()} newTab={true} rel='noopener noreferrer'>
@@ -443,6 +443,7 @@ const EditPage = createClass({
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}

View File

@@ -2,6 +2,11 @@ const dedent = require('dedent-tabs').default;
const loginUrl = 'https://www.naturalcrit.com/login';
// Prevent parsing text (e.g. document titles) as markdown
const escape = (text = '')=>{
return text.split('').map((char)=>`&#${char.charCodeAt(0)};`).join('');
};
//001-050 : Brew errors
//050-100 : Other pages errors
@@ -89,7 +94,7 @@ const errorIndex = (props)=>{
:
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
**Brew Title:** ${escape(props.brew.brewTitle) || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}
@@ -104,7 +109,7 @@ const errorIndex = (props)=>{
:
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
**Brew Title:** ${escape(props.brew.brewTitle) || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}
@@ -162,6 +167,14 @@ 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.
The brew selected as a theme exists, but has not been marked for use as a theme with the \`theme:meta\` tag.
If the selected brew is your document, you may designate it as a theme by adding the \`theme:meta\` tag.`,
//account page when account is not defined
'50' : dedent`
@@ -181,7 +194,7 @@ const errorIndex = (props)=>{
**Brew ID:** ${props.brew.brewId}
**Brew Title:** ${props.brew.brewTitle}`,
**Brew Title:** ${escape(props.brew.brewTitle)}`,
// ####### Admin page error #######
'52' : dedent`

View File

@@ -233,6 +233,7 @@ const NewPage = createClass({
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}

View File

@@ -24,12 +24,16 @@ These instructions assume that you are installing to a completely new, fresh Ubu
These installation instructions have been tested on the following Ubuntu releases:
- *ubuntu-20.04.3-desktop-amd64*
- *ubuntu-24.04.1-desktop-amd64*
- *ubuntu-22.04.5-desktop-amd64*
- *ubuntu-20.04.6-desktop-amd64*
## Final Notes
While this installation process works successfully at the time of writing (December 19, 2021), it relies on all of the Node.JS packages used in the HomeBrewery project retaining their cross-platform capabilities to continue to function. This is one of the inherent advantages of Node.JS, but it is by no means guaranteed and as such, functionality or even installation may fail without warning at some point in the future.
Earlier versions of Ubuntu may requier an alternate Mongo setup, see https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/ for assistance.
Regards,
G
December 19, 2021

View File

@@ -3,7 +3,8 @@ Description=Homebrewery Web Server
[Service]
User=root
After=mongodb
BindsTo=mongod.service
After=mongod.service
Environment=NODE_ENV=local
WorkingDirectory=/usr/local/homebrewery
ExecStart=node server.js

View File

@@ -1,14 +1,60 @@
#!/bin/sh
# Detect Ubuntu Version
export DISTRO=$(grep "^NAME=" /etc/os-release | awk -F '=' '{print $2}' | sed 's/"//g')
export DISTRO_VER=$(grep "VERSION_ID=" /etc/os-release | awk -F '=' '{print $2}' | sed 's/"//g')
export MATCHED="Yes"
if [ "${DISTRO}" != "Ubuntu" ];
then
echo :: Ubuntu not detected. Are you using an alternate spin or derivative?
echo :: Detected - ${DISTRO}
read -p [y/N] YESNO
if [ "${YESNO}" != "Y" ] && [ ]"${YESNO}" != "y" ]; then
exit
fi
MATCHED="No"
fi
# Install CURL and add required NodeJS source to package repo
echo ::Install CURL
apt install -y curl
echo ::Add NodeJS source to package repo
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
# Add Mongo CE Source
if [ ${DISTRO} = "Ubuntu" ];
then
echo ::Add Mongo CE source to package repo
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg \
--dearmor
if [ "${DISTRO_VER}" == "24.04" ]; then
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
elif [ "${DISTRO_VER}" == "22.04" ]; then
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
elif [ "${DISTRO_VER}" == "20.04" ]; then
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
else
MATCHED="No"
fi
sudo apt-get update
fi
if [ ${MATCHED} == "No" ]; then
echo :: WARNING
echo :: Unable to determine Ubuntu version for Mongo installation purposes.
echo :: Please check your spin/distro documentation to install Mongo CE and enable it on startup.
fi
# Install required packages
echo ::Install Homebrewery requirements
apt satisfy -y git nodejs npm mongodb
apt satisfy -y git nodejs npm mongodb-org
# Enable and start Mongo
systemctl enable mongod
systemctl start mongod
# Clone Homebrewery repo
echo ::Get Homebrewery files

630
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.16.1",
"version": "3.17.0",
"type": "module",
"engines": {
"npm": "^10.2.x",
@@ -39,7 +39,6 @@
"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:paragraph-justification": "jest tests/markdown/paragraph-justification.test.js --verbose --noStackTrace",
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
"test:route": "jest tests/routes/static-pages.test.js --verbose",
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
@@ -85,9 +84,9 @@
]
},
"dependencies": {
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@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.14.0",
"body-parser": "^1.20.2",
@@ -98,7 +97,7 @@
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"dompurify": "^3.2.3",
"dompurify": "^3.2.4",
"expr-eval": "^2.0.2",
"express": "^4.21.2",
"express-async-handler": "^1.2.0",
@@ -109,36 +108,37 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "11.2.0",
"marked": "13.0.3",
"marked-emoji": "^1.4.3",
"marked-extended-tables": "^1.1.0",
"marked-gfm-heading-id": "^3.2.0",
"marked-smartypants-lite": "^1.0.2",
"marked-gfm-heading-id": "^4.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.9.5",
"nanoid": "5.0.9",
"mongoose": "^8.10.1",
"nanoid": "5.1.0",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^5.2.7",
"react-router": "^7.1.3",
"react-router": "^7.2.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"@stylistic/stylelint-plugin": "^3.1.2",
"babel-plugin-transform-import-meta": "^2.3.2",
"eslint": "^9.18.0",
"eslint": "^9.20.1",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0",
"globals": "^15.15.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.13.2",
"stylelint": "^16.14.1",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^15.0.0",
"supertest": "^7.0.0"

View File

@@ -93,7 +93,7 @@ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
/* Cleans `<script` and `</script>` from the "text" field of a brew */
router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{
console.log(`[ADMIN] Cleaning script tags from ShareID ${req.params.id}`);
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Cleaning script tags from ShareID ${req.params.id}`);
function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');};
@@ -114,6 +114,18 @@ router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin',
return await HomebrewAPI.updateBrew(req, res);
});
/* Get list of a user's documents */
router.get('/admin/user/list/:user', mw.adminOnly, async (req, res)=>{
const username = req.params.user;
const fields = { _id: 0, text: 0, textBin: 0 }; // Remove unnecessary fields from document lists
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Get brew list for ${username}`);
const brews = await HomebrewModel.getByUser(username, true, fields);
return res.json(brews);
});
/* Compresses the "text" field of a brew to binary */
router.put('/admin/compress/:id', (req, res)=>{
HomebrewModel.findOne({ _id: req.params.id })
@@ -135,7 +147,6 @@ router.put('/admin/compress/:id', (req, res)=>{
});
});
router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
try {
const totalBrewsCount = await HomebrewModel.countDocuments({});

View File

@@ -552,6 +552,7 @@ const renderPage = async (req, res)=>{
const configuration = {
local : isLocalEnvironment,
publicUrl : config.get('publicUrl') ?? '',
baseUrl : `${req.protocol}://${req.get('host')}`,
environment : nodeEnv,
deployment : config.get('heroku_app_name') ?? ''
};

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-lines */
import _ from 'lodash';
import {model as HomebrewModel} from './homebrew.model.js';
import { model as HomebrewModel } from './homebrew.model.js';
import express from 'express';
import zlib from 'zlib';
import GoogleActions from './googleActions.js';
@@ -279,6 +279,8 @@ const api = {
let currentTheme;
const completeStyles = [];
const completeSnippets = [];
let themeName;
let themeAuthor;
while (req.params.id) {
//=== User Themes ===//
@@ -292,6 +294,10 @@ const api = {
currentTheme = req.brew;
splitTextStyleAndMetadata(currentTheme);
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));
@@ -301,6 +307,7 @@ const api = {
req.params.renderer = currentTheme.renderer;
} else {
//=== Static Themes ===//
themeName ??= req.params.id;
const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client
const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`;
completeSnippets.push(localSnippets);
@@ -313,7 +320,9 @@ const api = {
const returnObj = {
// Reverse the order of the arrays so they are listed oldest parent to youngest child.
styles : completeStyles.reverse(),
snippets : completeSnippets.reverse()
snippets : completeSnippets.reverse(),
name : themeName,
author : themeAuthor
};
res.setHeader('Content-Type', 'application/json');

View File

@@ -576,7 +576,7 @@ brew`);
describe('Theme bundle', ()=>{
it('should return Theme Bundle for a User Theme', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' }
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -587,6 +587,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : ['/* From Brew: https://localhost/share/userThemeAID */\n\nUser Theme A Style'],
snippets : []
});
@@ -594,9 +596,9 @@ brew`);
it('should return Theme Bundle for nested User Themes', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style' }
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -607,6 +609,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : [
'/* From Brew: https://localhost/share/userThemeCID */\n\nUser Theme C Style',
'/* From Brew: https://localhost/share/userThemeBID */\n\nUser Theme B Style',
@@ -623,6 +627,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : '5ePHB',
author : undefined,
styles : [
`/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`,
`/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`
@@ -636,9 +642,9 @@ brew`);
it('should return Theme Bundle for nested User and Static Themes together', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style' }
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -649,6 +655,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : [
`/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`,
`/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`,
@@ -665,9 +673,9 @@ brew`);
});
});
it('should fail for an invalid Theme in the chain', async()=>{
it('should fail for a missing Theme in the chain', async()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style' },
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -686,6 +694,27 @@ brew`);
name : 'ThemeLoad Error',
status : 404 });
});
it('should fail for a User Theme not tagged with meta:theme', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
model.get = jest.fn((getParams)=>toBrewPromise(brews[getParams.shareId]));
const req = { params: { renderer: 'V3', id: 'userThemeAID' }, get: ()=>{ return 'localhost'; }, protocol: 'https' };
let err;
await api.getThemeBundle(req, res)
.catch((e)=>err = e);
expect(err).toEqual({
HBErrorCode : '10',
brewId : 'userThemeAID',
message : 'Selected theme does not have the meta:theme tag',
name : 'Invalid Theme Selected',
status : 422 });
});
});
describe('deleteBrew', ()=>{

View File

@@ -44,13 +44,19 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{
.catch((err)=>{
obj.setState({ error: err });
});
if(!res) return;
if(!res) {
obj.setState((prevState)=>({
...prevState,
themeBundle : {}
}));
return;
}
const themeBundle = res.body;
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
obj.setState((prevState)=>({
...prevState,
themeBundle : themeBundle
themeBundle : themeBundle,
error : null
}));
};

View File

@@ -7,6 +7,7 @@ import MarkedExtendedTables from 'marked-extended-tables';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
import MarkedSubSuperText from 'marked-subsuper-text';
//Icon fonts included so they can appear in emoji autosuggest dropdown
import diceFont from '../../themes/fonts/iconFonts/diceFont.js';
@@ -333,35 +334,6 @@ const mustacheInjectBlock = {
}
};
const superSubScripts = {
name : 'superSubScript',
level : 'inline',
start(src) { return src.match(/\^/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const superRegex = /^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/m;
const subRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m;
let isSuper = false;
let match = subRegex.exec(src);
if(!match){
match = superRegex.exec(src);
if(match)
isSuper = true;
}
if(match?.length) {
return {
type : 'superSubScript', // Should match "name" above
raw : match[0], // Text to consume from the source
tag : isSuper ? 'sup' : 'sub',
tokens : this.lexer.inlineTokens(match[1])
};
}
},
renderer(token) {
return `<${token.tag}>${this.parser.parseInline(token.tokens)}</${token.tag}>`;
}
};
const justifiedParagraphClasses = [];
justifiedParagraphClasses[2] = 'Left';
justifiedParagraphClasses[4] = 'Right';
@@ -415,7 +387,7 @@ const forcedParagraphBreaks = {
}
},
renderer(token) {
return `<div class='blank'></div>`.repeat(token.length).concat('\n');
return `<br>\n`.repeat(token.length);
}
};
@@ -796,8 +768,9 @@ const tableTerminators = [
Marked.use(MarkedVariables());
Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
nonbreakingSpaces, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] });
nonbreakingSpaces, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
Marked.use(MarkedSubSuperText());
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }),
MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));

View File

@@ -1,5 +1,5 @@
const stylelint = require('stylelint');
const { isNumber } = require('stylelint/lib/utils/validateTypes.cjs');
import stylelint from 'stylelint';
import { isNumber } from 'stylelint/lib/utils/validateTypes.mjs';
const { report, ruleMessages, validateOptions } = stylelint.utils;
const ruleName = 'naturalcrit/declaration-block-multi-line-min-declarations';
@@ -7,9 +7,8 @@ const messages = ruleMessages(ruleName, {
expected : (decls)=>`Rule with ${decls} declaration${decls == 1 ? '' : 's'} should be single line`,
});
module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
return function lint(postcssRoot, postcssResult) {
const ruleFunction = (primaryOption, secondaryOptionObject, context)=>{
return (postcssRoot, postcssResult)=>{
const validOptions = validateOptions(
postcssResult,
@@ -20,26 +19,23 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti
}
);
if(!validOptions) { //If the options are invalid, don't lint
if(!validOptions) //If the options are invalid, don't lint
return;
}
const isAutoFixing = Boolean(context.fix);
postcssRoot.walkRules((rule)=>{ //Iterate CSS rules
//Apply rule only if all children are decls (no further nested rules)
if(rule.nodes.length > primaryOption || !rule.nodes.every((node)=>node.type === 'decl')) {
if(rule.nodes.length > primaryOption || !rule.nodes.every((node)=>node.type === 'decl'))
return;
}
//Ignore if already one line
if(!rule.nodes.some((node)=>node.raws.before.includes('\n')) && !rule.raws.after.includes('\n'))
return;
if(isAutoFixing) { //We are in “fix” mode
rule.each((decl)=>{
decl.raws.before = ' ';
});
rule.each((decl)=>decl.raws.before = ' ');
rule.raws.after = ' ';
} else {
report({
@@ -52,7 +48,9 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti
}
});
};
});
};
module.exports.ruleName = ruleName;
module.exports.messages = messages;
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
export default stylelint.createPlugin(ruleName, ruleFunction);

View File

@@ -1,32 +1,29 @@
const stylelint = require('stylelint');
import stylelint from 'stylelint';
const { report, ruleMessages, validateOptions } = stylelint.utils;
const ruleName = 'naturalcrit/declaration-colon-align';
const messages = ruleMessages(ruleName, {
expected : (rule)=>`Expected colons aligned within rule "${rule}"`,
});
module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
return function lint(postcssRoot, postcssResult) {
const ruleFunction = (primaryOption, secondaryOptionObject, context)=>{
return (postcssRoot, postcssResult)=>{
const validOptions = validateOptions(
postcssResult,
ruleName,
{
actual : primaryOption,
possible : [
true,
false
]
possible : [true, false]
}
);
if(!validOptions) { //If the options are invalid, don't lint
if(!validOptions) // If the options are invalid, don't lint
return;
}
const isAutoFixing = Boolean(context.fix);
postcssRoot.walkRules((rule)=>{ //Iterate CSS rules
postcssRoot.walkRules((rule)=>{ // Iterate CSS rules
let maxColonPos = 0;
let misaligned = false;
@@ -36,33 +33,37 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti
return;
const colonPos = declaration.prop.length + declaration.raws.between.indexOf(':');
if(maxColonPos > 0 && colonPos != maxColonPos) {
if(maxColonPos > 0 && colonPos != maxColonPos)
misaligned = true;
}
maxColonPos = Math.max(maxColonPos, colonPos);
});
if(misaligned) {
if(isAutoFixing) { //We are in “fix” mode
rule.each((declaration)=>{
if(declaration.type != 'decl')
return;
if(!misaligned)
return;
declaration.raws.between = `${' '.repeat(maxColonPos - declaration.prop.length)}:${declaration.raws.between.split(':')[1]}`;
});
} else { //We are in “report only” mode
report({
ruleName,
result : postcssResult,
message : messages.expected(rule.selector), // Build the reported message
node : rule, // Specify the reported node
word : rule.selector, // Which exact word caused the error? This positions the error properly
});
}
if(isAutoFixing) { // We are in “fix” mode
rule.each((declaration)=>{
if(declaration.type != 'decl')
return;
declaration.raws.between = `${' '.repeat(maxColonPos - declaration.prop.length)}:${declaration.raws.between.split(':')[1]}`;
});
} else { // We are in “report only” mode
report({
ruleName,
result : postcssResult,
message : messages.expected(rule.selector), // Build the reported message
node : rule, // Specify the reported node
word : rule.selector, // Which exact word caused the error? This positions the error properly
});
}
});
};
});
};
module.exports.ruleName = ruleName;
module.exports.messages = messages;
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
export default stylelint.createPlugin(ruleName, ruleFunction);

View File

@@ -1,5 +1,5 @@
const stylelint = require('stylelint');
const { isNumber } = require('stylelint/lib/utils/validateTypes.cjs');
import stylelint from 'stylelint';
import { isNumber } from 'stylelint/lib/utils/validateTypes.mjs';
const { report, ruleMessages, validateOptions } = stylelint.utils;
const ruleName = 'naturalcrit/declaration-colon-min-space-before';
@@ -7,9 +7,8 @@ const messages = ruleMessages(ruleName, {
expected : (num)=>`Expected at least ${num} space${num == 1 ? '' : 's'} before ":"`
});
module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
return function lint(postcssRoot, postcssResult) {
const ruleFunction = (primaryOption, secondaryOptionObject, context)=>{
return (postcssRoot, postcssResult)=>{
const validOptions = validateOptions(
postcssResult,
@@ -30,9 +29,9 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti
const between = decl.raws.between;
const colonIndex = between.indexOf(':');
if(between.slice(0, colonIndex).length >= primaryOption) {
if(between.slice(0, colonIndex).length >= primaryOption)
return;
}
if(isAutoFixing) { //We are in “fix” mode
decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ' '.repeat(primaryOption)) + between.slice(colonIndex);
} else {
@@ -46,7 +45,9 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti
}
});
};
});
};
module.exports.ruleName = ruleName;
module.exports.messages = messages;
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
export default stylelint.createPlugin(ruleName, ruleFunction);

View File

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

View File

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

View File

@@ -17,7 +17,6 @@
.useSansSerif() {
font-family : 'ScalySansRemake';
font-size : 0.318cm;
line-height : 1.2em;
p,dl,ul,ol { line-height : 1.2em; }
ul, ol { padding-left : 1em; }
em { font-style : italic; }
@@ -679,6 +678,7 @@
padding : 2.25cm 1.3cm 2cm 1.3cm;
color : #FFFFFF;
columns : 1;
line-height : 1.4em;
&::after { display : none; }
.columnWrapper { width : 7.6cm; }
.backCover {
@@ -690,7 +690,6 @@
background-repeat : no-repeat;
background-size : contain;
}
.blank { height : 1.4em; }
h1 {
margin-bottom : 0.3cm;
font-family : 'NodestoCapsCondensed';

View File

@@ -427,17 +427,6 @@ body { counter-reset : page-numbers 0; }
}
}
//*****************************
// * BLANK LINE
// *****************************/
.page {
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
//*****************************
// * WIDE
// *****************************/