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

Compare commits

...

198 Commits

Author SHA1 Message Date
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
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
8e99d47869 Parse mustache "style" properties into object instead of string 2025-01-23 00:54:07 -05:00
Trevor Buckner
eebc9c2bfa commit changes so far 2025-01-22 15:04:33 -05:00
Víctor Losada Hernández
bd5c85147d Merge pull request #3996 from naturalcrit/dependabot/npm_and_yarn/react-router-7.1.3
Bump react-router from 7.1.2 to 7.1.3
2025-01-21 23:27:17 +01:00
Víctor Losada Hernández
7f7a8338ff Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.1.3 2025-01-21 23:24:38 +01:00
Trevor Buckner
2a9945f09f Extract common function to merge HTML tags 2025-01-21 16:14:36 -05:00
dependabot[bot]
76ccbfbf20 Bump react-router from 7.1.2 to 7.1.3
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.2 to 7.1.3.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.1.3/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-20 09:12:11 +00:00
Víctor Losada Hernández
77c58eae2e Merge pull request #3997 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recess-order-6.0.0
Bump stylelint-config-recess-order from 5.1.1 to 6.0.0
2025-01-20 10:10:54 +01:00
dependabot[bot]
4a2b8dc261 Bump stylelint-config-recess-order from 5.1.1 to 6.0.0
Bumps [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order) from 5.1.1 to 6.0.0.
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v5.1.1...v6.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 03:44:58 +00:00
Víctor Losada Hernández
fa1a0e2351 Merge pull request #3908 from dbolack-ab/justifiedParagraphs
V4 proposed aligned paragraph tokens
2025-01-17 19:05:09 +01:00
Víctor Losada Hernández
f7b36a9b05 Merge branch 'master' into justifiedParagraphs 2025-01-17 19:04:48 +01:00
Víctor Losada Hernández
f4ce2437a7 Merge pull request #3995 from naturalcrit/dependabot/npm_and_yarn/react-router-7.1.2
Bump react-router from 7.1.1 to 7.1.2
2025-01-17 19:03:59 +01:00
dependabot[bot]
aa34bb44c9 Bump react-router from 7.1.1 to 7.1.2
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.1 to 7.1.2.
- [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.2/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-17 18:00:50 +01:00
Víctor Losada Hernández
e3c90ace73 Merge pull request #3994 from naturalcrit/dependabot/npm_and_yarn/fs-extra-11.3.0
Bump fs-extra from 11.2.0 to 11.3.0
2025-01-17 18:00:37 +01:00
dependabot[bot]
7c1545a07d Bump fs-extra from 11.2.0 to 11.3.0
Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) from 11.2.0 to 11.3.0.
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jprichardson/node-fs-extra/compare/11.2.0...11.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-17 16:52:32 +00:00
Trevor Buckner
953c612830 Merge pull request #3992 from dbolack-ab/issue_3448
Implement suggested fix for 3448
2025-01-17 11:25:08 -05:00
David Bolack
5dbb5499c6 fix test 2025-01-17 10:02:32 -06:00
David Bolack
d4f6c329b8 Add a test! 2025-01-15 17:36:18 -06:00
David Bolack
a574ec0777 Merge branch 'master' into issue_3448 2025-01-15 16:48:01 -06:00
David Bolack
3e5a72fa96 Merge branch 'master' into justifiedParagraphs 2025-01-15 07:06:37 -06:00
Víctor Losada Hernández
4df2a73800 Merge pull request #3991 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.13.2
Bump stylelint from 16.13.1 to 16.13.2
2025-01-15 09:30:52 +01:00
dependabot[bot]
aea9296908 Bump stylelint from 16.13.1 to 16.13.2
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.13.1 to 16.13.2.
- [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.1...16.13.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-15 08:29:16 +00:00
Víctor Losada Hernández
08eeb57cb0 Merge pull request #3990 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-jest-28.11.0
Bump eslint-plugin-jest from 28.10.0 to 28.11.0
2025-01-15 09:28:05 +01:00
dependabot[bot]
e5e9a9efe1 Bump eslint-plugin-jest from 28.10.0 to 28.11.0
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 28.10.0 to 28.11.0.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v28.10.0...v28.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-15 03:42:56 +00:00
David Bolack
aafc6fad7d Implement suggested fix for 3488
Per issue
2025-01-14 21:40:15 -06:00
David Bolack
b91f18a8a0 Merge branch 'master' into justifiedParagraphs 2025-01-14 07:02:53 -06:00
David Bolack
20bfff5157 Remove it back? Meh. Just trying to revert to last 2025-01-14 07:00:24 -06:00
David Bolack
3c735e599f Add a CR 2025-01-14 06:59:46 -06:00
David Bolack
4958ade937 Remove V4 cruft that should never have been merged 2025-01-14 06:58:48 -06:00
Víctor Losada Hernández
57dc5d4923 Merge pull request #3986 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.5
Bump mongoose from 8.9.4 to 8.9.5
2025-01-14 08:27:37 +01:00
dependabot[bot]
3c5ad74e38 Bump mongoose from 8.9.4 to 8.9.5
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.4 to 8.9.5.
- [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.4...8.9.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-14 08:13:17 +01:00
Víctor Losada Hernández
e988e20f5b Merge pull request #3987 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-react-7.37.4
Bump eslint-plugin-react from 7.37.3 to 7.37.4
2025-01-14 08:13:00 +01:00
dependabot[bot]
cac6dbd40c Bump eslint-plugin-react from 7.37.3 to 7.37.4
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.3 to 7.37.4.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.3...v7.37.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-14 08:09:57 +01:00
Víctor Losada Hernández
2461b4ab6a Merge pull request #3985 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.13.1
Bump stylelint from 16.13.0 to 16.13.1
2025-01-14 08:09:03 +01:00
dependabot[bot]
7c4f163042 Bump stylelint from 16.13.0 to 16.13.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.13.0 to 16.13.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.0...16.13.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-14 03:57:47 +00:00
Víctor Losada Hernández
2fee37239f Merge pull request #3982 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recommended-15.0.0
Bump stylelint-config-recommended from 14.0.1 to 15.0.0
2025-01-13 13:56:52 +01:00
dependabot[bot]
2cb19848aa Bump stylelint-config-recommended from 14.0.1 to 15.0.0
Bumps [stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended) from 14.0.1 to 15.0.0.
- [Release notes](https://github.com/stylelint/stylelint-config-recommended/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-recommended/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-recommended/compare/14.0.1...15.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 12:54:21 +00:00
Víctor Losada Hernández
913cde44ff Merge pull request #3983 from naturalcrit/dependabot/npm_and_yarn/eslint-9.18.0
Bump eslint from 9.17.0 to 9.18.0
2025-01-13 13:53:12 +01:00
Víctor Losada Hernández
c7ff1fc07f Merge branch 'master' into dependabot/npm_and_yarn/eslint-9.18.0 2025-01-13 13:50:38 +01:00
Víctor Losada Hernández
da42e835c5 Merge pull request #3981 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.13.0
Bump stylelint from 16.12.0 to 16.13.0
2025-01-13 13:50:18 +01:00
dependabot[bot]
7a071496f3 Bump eslint from 9.17.0 to 9.18.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.17.0 to 9.18.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.17.0...v9.18.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-13 03:36:04 +00:00
dependabot[bot]
b8d65f2f56 Bump stylelint from 16.12.0 to 16.13.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.12.0 to 16.13.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.12.0...16.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 03:35:40 +00:00
David Bolack
9c197ea25a Merge branch 'master' into issue_3231 2025-01-12 13:52:37 -06:00
David Bolack
d75db5d378 Merge branch 'master' into justifiedParagraphs 2025-01-12 13:51:28 -06:00
Víctor Losada Hernández
a2538bed20 Merge pull request #3503 from naturalcrit/dependabot/npm_and_yarn/react-frame-component-5.2.7
Bump react-frame-component from 4.1.3 to 5.2.7
2025-01-11 16:50:46 +01:00
dependabot[bot]
69c45d63a4 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-01-11 15:46:07 +00: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
Trevor Buckner
ecc91af1d6 Merge pull request #3973 from naturalcrit/dependabot/npm_and_yarn/core-js-3.40.0
Bump core-js from 3.39.0 to 3.40.0
2025-01-10 15:53:33 -05:00
Trevor Buckner
4ff043f759 Merge branch 'master' into dependabot/npm_and_yarn/core-js-3.40.0 2025-01-10 15:09:51 -05:00
Trevor Buckner
84e18aae5a Merge pull request #3976 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.4
Bump mongoose from 8.9.3 to 8.9.4
2025-01-10 15:09:05 -05:00
Trevor Buckner
b53bda937a Merge pull request #3975 from dbolack-ab/issue_3974
Apply fix to clear error.
2025-01-10 15:08:52 -05:00
David Bolack
4db4bba73f Merge branch 'master' into issue_3974 2025-01-10 11:19:13 -06:00
Víctor Losada Hernández
2c2e6d6027 Merge pull request #3589 from G-Ambatte/experimentalHeaderNavigation
Experimental Header Navigation
2025-01-10 11:37:54 +01:00
David Bolack
1aeea034d2 Requested corrections? 2025-01-09 22:34:35 -06:00
dependabot[bot]
63bd483b3e Bump mongoose from 8.9.3 to 8.9.4
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.3 to 8.9.4.
- [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.3...8.9.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-10 03:51:06 +00:00
G.Ambatte
19cb24d8db Add explanatory comments to HeaderNav.jsx 2025-01-10 16:48:02 +13:00
David Bolack
96ebe0f617 Apply fix to clear error. 2025-01-09 16:42:55 -06:00
G.Ambatte
eb3178bf80 Update nav item selector 2025-01-09 14:17:41 +13:00
G.Ambatte
a72f47df46 Add identifier to ToC pages in header nav 2025-01-09 12:53:52 +13:00
G.Ambatte
a9823d39e2 Update selection query 2025-01-09 12:29:18 +13:00
G.Ambatte
6ec65eee23 Skip Table of Contents pages 2025-01-09 08:13:59 +13:00
G.Ambatte
9c2610ff40 Add guard clause to prevent empty nav lines 2025-01-09 08:07:59 +13:00
Trevor Buckner
914521cada Merge branch 'master' into experimentalHeaderNavigation 2025-01-07 22:49:05 -05:00
Trevor Buckner
70bda94033 Lint Toolbar.jsx 2025-01-07 22:47:18 -05:00
Trevor Buckner
915137af5e Lint BrewRenderer.jsx 2025-01-07 22:47:04 -05:00
dependabot[bot]
7516c0cbd3 Bump core-js from 3.39.0 to 3.40.0
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.39.0 to 3.40.0.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.40.0/packages/core-js)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-08 03:46:18 +00:00
Trevor Buckner
fdfae9a771 Merge branch 'master' into experimentalHeaderNavigation 2025-01-07 22:43:29 -05:00
Trevor Buckner
8cc693461d Lint EditPage.jsx 2025-01-07 22:43:06 -05:00
Víctor Losada Hernández
26cc272b37 Merge pull request #3972 from G-Ambatte/pr/3841
Error Bar refactor - fixes
2025-01-07 21:53:04 +01:00
G.Ambatte
bffa6eb0c9 Limit max-height to prevent overflow
Co-authored-by: Víctor Losada Hernández <5ecleric.naturalcrit@gmail.com>
2025-01-08 07:22:17 +13:00
G.Ambatte
2779055e50 Stop error bar from blocking menus 2025-01-07 18:02:17 +13:00
G.Ambatte
37d00f1255 Remove dismissKeys check before displaying dialog 2025-01-07 18:01:58 +13:00
G.Ambatte
d9b599e814 Fix error listing 2025-01-07 18:01:17 +13:00
G.Ambatte
40d453bc7c Return if no notifications 2025-01-07 18:00:45 +13:00
Trevor Buckner
6ff0cfe383 Merge branch 'master' into refactor-errorBar-to-functional-and-using-dialog 2025-01-06 12:03:34 -05:00
G.Ambatte
a6b7ed4dd2 Improve link text generation 2025-01-06 23:54:05 +13:00
G.Ambatte
bf0614026d Use classes rather than inline styling for indentation 2025-01-06 23:53:33 +13:00
G.Ambatte
06005009e4 HeaderList now in nav > ul > li 2025-01-06 23:20:52 +13:00
G.Ambatte
cf16566da8 Move Header Navigation button to Toolbar 2025-01-06 22:30:03 +13:00
G.Ambatte
34f104b406 Remove showHeaderNav prop from Edit and Share page BrewRenderer 2025-01-06 22:29:22 +13:00
Trevor Buckner
766ab8f10a Lint 2025-01-05 23:07:53 -05:00
Trevor Buckner
aa4276a50e Move exit condition to start 2025-01-05 23:06:56 -05:00
Trevor Buckner
fbedafb204 typo 2025-01-05 23:04:57 -05:00
Trevor Buckner
85cd7c7336 Move calculation of error states outside of render
Our previous approach was technically bad practice to calculate side-effects inside of the render step. We can separate that out as part of this refactor.

Also use native javascript map instead of lodash.
2025-01-05 23:04:48 -05:00
Trevor Buckner
c137d40037 More alignment 2025-01-05 22:58:48 -05:00
Trevor Buckner
5a9e7850c2 space to tabs 2025-01-05 22:53:30 -05:00
G.Ambatte
6e7342d6f0 Merge branch 'master' into experimentalHeaderNavigation 2025-01-06 16:41:21 +13:00
Trevor Buckner
1598adfa67 Merge pull request #3971 from naturalcrit/dependabot/npm_and_yarn/babel-plugin-transform-import-meta-2.3.2
Bump babel-plugin-transform-import-meta from 2.2.1 to 2.3.2
2025-01-05 22:36:47 -05:00
dependabot[bot]
b49936c24b Bump babel-plugin-transform-import-meta from 2.2.1 to 2.3.2
Bumps [babel-plugin-transform-import-meta](https://github.com/javiertury/babel-plugin-transform-import-meta) from 2.2.1 to 2.3.2.
- [Changelog](https://github.com/javiertury/babel-plugin-transform-import-meta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/javiertury/babel-plugin-transform-import-meta/compare/v2.2.1...v2.3.2)

---
updated-dependencies:
- dependency-name: babel-plugin-transform-import-meta
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 03:13:48 +00:00
Trevor Buckner
816f4f75f6 Merge pull request #3970 from naturalcrit/dependabot/npm_and_yarn/marked-extended-tables-1.1.0
Bump marked-extended-tables from 1.0.10 to 1.1.0
2025-01-05 22:12:36 -05:00
dependabot[bot]
a091a18604 Bump marked-extended-tables from 1.0.10 to 1.1.0
Bumps [marked-extended-tables](https://github.com/calculuschild/marked-extended-tables) from 1.0.10 to 1.1.0.
- [Release notes](https://github.com/calculuschild/marked-extended-tables/releases)
- [Changelog](https://github.com/calculuschild/marked-extended-tables/blob/main/release.config.cjs)
- [Commits](https://github.com/calculuschild/marked-extended-tables/commits/v1.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 03:06:16 +00:00
Trevor Buckner
edadb3cb77 Merge pull request #3959 from dbolack-ab/updateDockerInstructions
Update Docker instructions in support of #1930
2025-01-05 15:06:12 -05:00
Trevor Buckner
3749a5c2b1 Merge branch 'master' into updateDockerInstructions 2025-01-05 15:06:03 -05:00
Trevor Buckner
e9b5e4ab0c indent 2025-01-05 15:04:55 -05:00
G.Ambatte
28109d28dc Merge branch 'master' into experimentalHeaderNavigation 2025-01-04 20:22:37 +13:00
Trevor Buckner
7f56797779 Merge pull request #3967 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.3
Bump mongoose from 8.9.2 to 8.9.3
2025-01-03 17:51:38 -05:00
G.Ambatte
a95eef0545 Add maximum length, use span for spacing 2025-01-03 13:34:52 +13:00
G.Ambatte
bbf6c3589a Switch from innerText to textContent 2025-01-03 11:53:29 +13:00
G.Ambatte
4a4a14b2ab Add ref correctly, fix typo 2025-01-03 11:37:51 +13:00
G.Ambatte
6b0c3b65b4 Merge branch 'master' into experimentalHeaderNavigation 2025-01-03 11:20:16 +13:00
dependabot[bot]
59006d354f Bump mongoose from 8.9.2 to 8.9.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.2 to 8.9.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.9.2...8.9.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-31 03:43:42 +00:00
David Bolack
26c9406211 Merge branch 'master' into justifiedParagraphs 2024-12-28 16:02:53 -06:00
David Bolack
fb13a1c98d Merge branch 'master' into updateDockerInstructions 2024-12-28 16:01:46 -06:00
Trevor Buckner
b20eb28a37 Merge pull request #3922 from 5e-Cleric/refactor-share-page-as-functional-comp
Refactor sharepage as functional comp
2024-12-26 19:21:55 -05:00
Trevor Buckner
d84f071c62 Other small cleanup 2024-12-26 19:20:25 -05:00
Trevor Buckner
bc7297de2e Mirror editId logic from shareId 2024-12-26 19:15:33 -05:00
Trevor Buckner
a2c4f73e7d processShareId does not need useCallback() 2024-12-26 19:12:34 -05:00
Trevor Buckner
9804c3933f Remove unneeded dependencies for useEffect
UseEffect is only intended to be called once.

Similarly, handleControlKeys doesn't need "useCallBack" because it will never be passed to a child or trigger any re-render by changing.
2024-12-26 19:09:23 -05:00
Trevor Buckner
e2b0da7830 Merge branch 'master' into refactor-share-page-as-functional-comp 2024-12-26 18:50:48 -05:00
Víctor Losada Hernández
5a5119a367 Merge pull request #3852 from 5e-Cleric/refactor-brewItem-component
Refactor brewItem into functional component
2024-12-26 21:18:48 +01:00
Víctor Losada Hernández
c310a8c1c2 Merge branch 'master' into refactor-brewItem-component 2024-12-26 21:07:44 +01:00
Trevor Buckner
11bfdd89b8 Merge pull request #3965 from naturalcrit/ImplementContentVisibility
Implement content-visibility on pages
2024-12-26 14:59:22 -05:00
David Bolack
2bedc6d7d4 Updates to docker files and cooresponding documentation. 2024-12-20 21:30:19 -06:00
David Bolack
674fb6ff57 Update Docker instructions in support of #1930
Updates README.DOCKER.md and Dockerfile
2024-12-20 20:33:12 -06:00
David Bolack
79c8309291 Add circleci 2024-12-20 19:54:25 -06:00
David Bolack
90632b78ce Add direct tests for paragraph justification 2024-12-20 14:58:56 -06:00
David Bolack
f71850d8b1 Merge branch 'master' into justifiedParagraphs 2024-12-20 14:55:30 -06:00
David Bolack
99d3d28754 Correct end of match criteria for justified paragraph to account for end of stream 2024-12-17 21:48:11 -06:00
David Bolack
08b0f47ea2 Fix Regex for Justified paragraphs 2024-12-17 21:33:33 -06:00
David Bolack
f9b42a30f7 Merge branch 'master' into justifiedParagraphs 2024-12-17 20:34:24 -06:00
David Bolack
7c69d2a74d Update tests to match 2024-12-10 23:37:48 -06:00
David Bolack
89bd082967 Shift alignment assignment from CSS to HTML 2024-12-10 23:28:06 -06:00
David Bolack
f4c26053c0 Merge branch 'master' into justifiedParagraphs 2024-12-10 23:22:10 -06:00
David Bolack
b7cb6dc444 Merge branch 'master' into justifiedParagraphs 2024-12-04 21:26:03 -06:00
Víctor Losada Hernández
8c986bb97d initial commit 2024-11-28 00:21:35 +01:00
David Bolack
deb9c6651f Add Styles for Forced Justifcation Tokens 2024-11-22 20:45:58 -06:00
David Bolack
440ad516df Add justification token testing 2024-11-22 20:39:31 -06:00
David Bolack
929469d0c0 Working feature. 2024-11-22 20:11:14 -06:00
Víctor Losada Hernández
49db31426c Merge branch 'master' into refactor-errorBar-to-functional-and-using-dialog 2024-10-23 12:48:42 +02:00
Víctor Losada Hernández
ce31d30ed7 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into refactor-brewItem-component 2024-10-23 12:48:01 +02:00
Víctor Losada Hernández
68831c759f initial commit 2024-10-23 12:45:13 +02:00
Víctor Losada Hernández
ebdbb39f24 linting 2024-10-20 22:29:14 +02:00
Víctor Losada Hernández
976740dc8b make more concise 2024-10-20 22:21:07 +02:00
Víctor Losada Hernández
cac87b14c7 refactor to func comp and using dialog 2024-10-20 22:07:33 +02:00
G.Ambatte
df5ed5190a Merge branch 'master' into experimentalHeaderNavigation 2024-08-27 09:59:18 +12:00
G.Ambatte
30dac3a73c Revert toolBar.less change 2024-08-26 21:53:15 +12:00
G.Ambatte
ba4c9745a2 Tweak styling for recent changes 2024-08-26 21:27:41 +12:00
G.Ambatte
a1c275479f Change toolbar to relative positioning 2024-08-26 21:27:09 +12:00
G.Ambatte
708cbdc9e5 Change to list items 2024-08-26 21:26:30 +12:00
G.Ambatte
b0585e28ad Merge branch 'experimentalHeaderNavigation' of https://github.com/G-Ambatte/homebrewery into experimentalHeaderNavigation 2024-08-26 16:14:13 +12:00
G.Ambatte
575aa447e0 Merge branch 'master' into experimentalHeaderNavigation 2024-08-26 16:12:50 +12:00
G.Ambatte
e57b88a019 Limit max width of header navigation 2024-08-26 16:06:11 +12:00
G.Ambatte
380c1444ca Tweak position to account for new toolbar 2024-08-26 16:01:12 +12:00
G.Ambatte
a59135430c Fix missing comma 2024-08-26 15:30:58 +12:00
G.Ambatte
bdf2c97942 Merge branch 'master' into experimentalHeaderNavigation 2024-08-26 15:28:46 +12:00
G.Ambatte
177c90c8e9 Merge branch 'master' into experimentalHeaderNavigation 2024-08-02 18:30:28 +12:00
G.Ambatte
933451b1ec Merge branch 'master' into experimentalHeaderNavigation 2024-08-01 12:45:29 +12:00
G.Ambatte
effeffd906 Add styling to page links 2024-07-22 19:07:58 +12:00
G.Ambatte
c269d32247 Move headerNav to separate component 2024-07-22 18:45:36 +12:00
G.Ambatte
17b081b18b Added showHeaderNav prop to make nav menu conditional 2024-07-22 17:30:29 +12:00
G.Ambatte
7fc0cadb81 Initial functionality pass 2024-07-21 23:11:21 +12:00
30 changed files with 1197 additions and 729 deletions

View File

@@ -73,6 +73,9 @@ 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,4 +1,4 @@
FROM node:20-alpine
FROM node:22-alpine
RUN apk --no-cache add git
ENV NODE_ENV=docker
@@ -9,7 +9,10 @@ WORKDIR /usr/src/app
# Copy package.json into the image, then run yarn install
# This improves caching so we don't have to download the dependencies every time the code changes
COPY package.json ./
COPY config/docker.json usr/src/app/config
# --ignore-scripts tells yarn not to run postbuild. We run it explicitly later
RUN node --version
RUN npm --version
RUN npm install --ignore-scripts
# Bundle app source and build application

View File

@@ -1,12 +1,119 @@
# Running Homebrewery via Docker
# Offline Install Instructions: Docker
The repo includes a Dockerfile and a docker-compose.yml file.
These instructions are for setting up a persistent instance of the Homebrewery application locally using Docker.
To run the application via docker-compose.yml:
`docker-compose up -d`
If you intend to develop with Homebrewery, following the Homebrewery application section of this guide is not recommended. Using docker to deploy MongoDB locally for development is not a bad idea at all, however.
To stop the application:
`docker-compose down`
# Install Docker
## Docker Desktop (MacOS/Windows)
Windows and Mac installs use Docker Desktop. Current install instructions are below.
* [Mac](https://docs.docker.com/desktop/mac/install/)
* [Windows](https://docs.docker.com/desktop/windows/install/)
You can set up the docker engine to start on boot via the Docker desktop UI.
## Docker Engine
Linux installs use Docker Engine. Docker provides installers and instructions for several of the most common distrubutions. If you do not see yours listed, it is very likely supported indirectly by your distribution.
* [Arch](https://docs.docker.com/desktop/setup/install/linux/archlinux/)
* [CentOS](https://docs.docker.com/engine/install/centos/)
* [Debian](https://docs.docker.com/engine/install/debian/)
* [Fedora](https://docs.docker.com/engine/install/fedora/)
* [RHEL](https://docs.docker.com/engine/install/rhel/)
* [Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
### Post installation steps
[Manage Docker as a non-root user (highly recommended)](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user)
[Enable Docker to start on boot (highly recommended)](https://docs.docker.com/engine/install/linux-postinstall/#configure-docker-to-start-on-boot)
# Build Homebrewery Image
Next we build the homebrewery docker image. Start by cloning the repository.
```shell
git clone https://github.com/naturalcrit/homebrewery.git
cd homebrewery
```
Make an changes you need to `config/docker.json` then build the image. If it does not exist,the below as a template.
```
{
"host" : "localhost:8000",
"naturalcrit_url" : "local.naturalcrit.com:8010",
"secret" : "secret",
"web_port" : 8000,
"enable_v3" : true,
"mongodb_uri": "mongodb://172.17.0.2/homebrewery",
"enable_themes" : true,
}
```
```shell
docker-compose build homebrewery
```
# Add Mongo container
Once docker is installed and running, it is time to set up the containers. First up, Mongo.
```shell
docker run --name homebrewery-mongodb -d --restart unless-stopped -v mongodata:/data/db -p 27017:27017 mongo:latest
```
Older CPUs may run into an issue with AVX support.
```
WARNING: MongoDB 5.0+ requires a CPU with AVX support, and your current system does not appear to have that!
see https://jira.mongodb.org/browse/SERVER-54407
see also https://www.mongodb.com/community/forums/t/mongodb-5-0-cpu-intel-g4650-compatibility/116610/2
see also https://github.com/docker-library/mongo/issues/485#issuecomment-891991814
```
If you see a message similar to this, try using the bitnami mongo instead.
```shell
docker run --name homebrewery-mongodb -d --restart unless-stopped -v mongodata:/data/db -p 27017:27017 bitnami/mongo:latest
```
If your distribution is running on an arm device such as a Raspberry Pi, you will need to run the arm-built MongoDB v4.4.
```shell
docker run --name homebrewery-mongodb -d --restart unless-stopped -v mongodata:/data/db -p 27017:27017 arm64v8/mongo:4.4
```
## Run the Homebrewery Image
```shell
# Make sure you run this in the homebrewery directory
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```
## Updating the Image
When Homebrewery code updates, your docker container will not automatically follow the changes. To do so you will need to rebuild your homebrewery image.
First, return to your homebrewery clone (from Build Homebrewery Image above) or recreate the clone if you deleted your copy of the code.
First, delete the existing image.
```shell
docker rm -f homebrewery-app
```
Next, update the clone's code to the latest version.
```shell
cd homebrewery
git checkout master
git pull upstream master
```
Finally, rebuild and restart the homebrewery image.
```shell
docker-compose build homebrewery
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```
To stop the application and remove all data:
`docker-compose down -v`

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/2024 - 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

@@ -6,10 +6,8 @@ function Dialog({ dismisskeys = [], closeText = 'Close', blocking = false, ...re
const dialogRef = useRef(null);
useEffect(()=>{
if(dismisskeys.length !== 0) {
blocking ? dialogRef.current?.showModal() : dialogRef.current?.show();
}
}, [dialogRef.current, dismisskeys]);
blocking ? dialogRef.current?.showModal() : dialogRef.current?.show();
}, []);
const dismiss = ()=>{
dismisskeys.forEach((key)=>{

View File

@@ -16,8 +16,10 @@ const Frame = require('react-frame-component').default;
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`
@@ -50,8 +52,8 @@ const BrewPage = (props)=>{
props.onVisibilityChange(props.index + 1, true, false); // add page to array of visible pages.
else
props.onVisibilityChange(props.index + 1, false, false);
}
)},
});
},
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
);
@@ -61,8 +63,8 @@ const BrewPage = (props)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting)
props.onVisibilityChange(props.index + 1, true, true); // Set this page as the center page
}
)},
});
},
{ threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center
);
@@ -75,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>;
};
@@ -115,12 +117,15 @@ const BrewRenderer = (props)=>{
pageShadows : true
});
const [headerState, setHeaderState] = useState(false);
const mainRef = useRef(null);
const pagesRef = useRef(null);
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)=>{
@@ -167,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} />;
}
};
@@ -287,7 +306,7 @@ const BrewRenderer = (props)=>{
<NotificationPopup />
</div>
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length}/>
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
{/*render in iFrame so broken code doesn't crash the site.*/}
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
@@ -306,12 +325,13 @@ const BrewRenderer = (props)=>{
&&
<>
{renderedStyle}
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle}>
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle} ref={pagesRef}>
{renderedPages}
</div>
</>
}
</div>
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
</Frame>
</>
);

View File

@@ -70,6 +70,7 @@
.pane { position : relative; }
@media print {
.toolBar { display : none; }
.brewRenderer {
@@ -82,4 +83,7 @@
& > .page { box-shadow : unset; }
}
}
.headerNav {
visibility: hidden;
}
}

View File

@@ -1,75 +1,53 @@
require('./errorBar.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const ErrorBar = createClass({
displayName : 'ErrorBar',
getDefaultProps : function() {
return {
errors : []
};
},
import Dialog from '../../../components/dialog.jsx';
hasOpenError : false,
hasCloseError : false,
hasMatchError : false,
const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
renderErrors : function(){
this.hasOpenError = false;
this.hasCloseError = false;
this.hasMatchError = false;
const ErrorBar = (props)=>{
if(!props.errors.length) return null;
let hasOpenError = false, hasCloseError = false, hasMatchError = false;
props.errors.map((err)=>{
if(err.id === 'OPEN') hasOpenError = true;
if(err.id === 'CLOSE') hasCloseError = true;
if(err.id === 'MISMATCH') hasMatchError = true;
});
const errors = _.map(this.props.errors, (err, idx)=>{
if(err.id == 'OPEN') this.hasOpenError = true;
if(err.id == 'CLOSE') this.hasCloseError = true;
if(err.id == 'MISMATCH') this.hasMatchError = true;
return <li key={idx}>
Line {err.line} : {err.text}, '{err.type}' tag
</li>;
});
const renderErrors = ()=>(
<ul>
{props.errors.map((err, idx)=>{
return <li key={idx}>
Line {err.line} : {err.text}, '{err.type}' tag
</li>;
})}
</ul>
);
return <ul>{errors}</ul>;
},
renderProtip : function(){
const msg = [];
if(this.hasOpenError){
msg.push(<div>
An unmatched opening tag means there's an opened tag that isn't closed. You need to close your tags, like this {'</div>'}. Make sure to match types!
</div>);
}
if(this.hasCloseError){
msg.push(<div>
An unmatched closing tag means you closed a tag without opening it. Either remove it, or check to where you think you opened it.
</div>);
}
if(this.hasMatchError){
msg.push(<div>
A type mismatch means you closed a tag, but the last open tag was a different type.
</div>);
}
return <div className='protips'>
const renderProtip = ()=>(
<div className='protips'>
<h4>Protips!</h4>
{msg}
</div>;
},
{hasOpenError && <div>Unmatched opening tag. Close your tags, like this {'</div>'}. Match types!</div>}
{hasCloseError && <div>Unmatched closing tag. Either remove it or check where it was opened.</div>}
{hasMatchError && <div>Type mismatch. Closed a tag with a different type.</div>}
</div>
);
render : function(){
if(!this.props.errors.length) return null;
return <div className='errorBar'>
<i className='fas fa-exclamation-triangle' />
<h3> There are HTML errors in your markup</h3>
<small>If these aren't fixed your brew will not render properly when you print it to PDF or share it</small>
{this.renderErrors()}
return (
<Dialog className='errorBar' closeText={DISMISS_BUTTON} >
<div>
<i className='fas fa-exclamation-triangle' />
<h2> There are HTML errors in your markup</h2>
<small>
If these aren't fixed your brew will not render properly when you print it to PDF or share it
</small>
{renderErrors()}
</div>
<hr />
{this.renderProtip()}
</div>;
}
});
{renderProtip()}
</Dialog>
);
};
module.exports = ErrorBar;

View File

@@ -1,60 +1,58 @@
.errorBar{
.errorBar {
position : absolute;
z-index : 10000;
box-sizing : border-box;
top : 32px;
z-index : 1;
width : 100%;
margin-right : 13px;
padding : 20px;
padding-bottom : 10px;
padding-left : 100px;
background-color : @red;
color : white;
i{
position : absolute;
left : 30px;
opacity : 0.8;
font-size : 3em;
}
h3{
font-size : 1.1em;
font-weight : 800;
}
ul{
margin-top : 15px;
font-size : 0.8em;
list-style-position : inside;
list-style-type : disc;
li{
line-height : 1.6em;
background-color : @red;
border : unset;
div {
> i {
float : left;
margin-right : 10px;
margin-bottom : 20px;
font-size : 3em;
opacity : 0.8;
}
h2 { font-weight : 800; }
ul {
margin-top : 15px;
font-size : 0.8em;
list-style-position : inside;
list-style-type : disc;
li { line-height : 1.6em; }
}
}
hr{
box-sizing : border-box;
hr {
height : 2px;
width : 150%;
margin-top : 25px;
margin-bottom : 15px;
margin-left : -100px;
background-color : darken(@red, 8%);
border : none;
}
small{
font-size: 0.6em;
opacity: 0.7;
small {
font-size : 0.6em;
opacity : 0.7;
}
.protips{
margin-left : -80px;
font-size : 0.6em;
&>div{
margin-bottom : 10px;
line-height : 1.2em;
}
h4{
opacity : 0.8;
.protips {
font-size : 0.6em;
line-height : 1.2em;
h4 {
font-weight : 800;
line-height : 1.5em;
text-transform : uppercase;
}
}
button.dismiss {
position : absolute;
top : 20px;
right : 30px;
padding : unset;
font-size : 40px;
background-color : transparent;
opacity : 0.6;
&:hover { opacity : 1; }
}
}

View File

@@ -0,0 +1,106 @@
require('./headerNav.less');
import * as React from 'react';
import * as _ from 'lodash';
const MAX_TEXT_LENGTH = 40;
const HeaderNav = React.forwardRef(({}, pagesRef)=>{
const renderHeaderLinks = ()=>{
if(!pagesRef.current) return;
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
];
const elements = pagesRef.current.querySelectorAll(selector.join(','));
if(!elements) return;
const navList = [];
// 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
// }
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'
link : el.id
});
});
return _.map(navList, (navItem, index)=>{
return <HeaderNavItem {...navItem} key={index} />;
});
};
return <nav className='headerNav'>
<ul>
{renderHeaderLinks()}
</ul>
</nav>;
}
);
const HeaderNavItem = ({ link, text, depth, className })=>{
const trimString = (text, prefixLength = 0)=>{
// Sanity check nav link strings
let output = text;
// If the string has a line break, only use the first line
if(text.indexOf('\n')){
output = text.split('\n')[0];
}
// Trim unecessary spaces from string
output = output.trim();
// Reduce excessively long strings
const maxLength = MAX_TEXT_LENGTH - prefixLength;
if(output.length > maxLength){
return `${output.slice(0, maxLength).trim()}...`;
}
return output;
};
if(!link || !text) return;
return <li>
<a href={`#${link}`} target='_self' className={`depth-${depth} ${className ?? ''}`}>
{trimString(text, depth)}
</a>
</li>;
};
export default HeaderNav;

View File

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

View File

@@ -49,6 +49,7 @@ const NotificationPopup = ()=>{
));
};
if(!notifications.length) return;
return <Dialog className='notificationPopup' dismisskeys={dissmissKeyList} closeText={DISMISS_BUTTON} >
<div className='header'>
<i className='fas fa-info-circle info'></i>

View File

@@ -9,7 +9,7 @@ import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anch
const MAX_ZOOM = 300;
const MIN_ZOOM = 10;
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages })=>{
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages, headerState, setHeaderState })=>{
const [pageNum, setPageNum] = useState(1);
const [toolsVisible, setToolsVisible] = useState(true);
@@ -62,7 +62,7 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
if(displayOptions.spread === 'facing')
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth / 2), Infinity); // if 'facing' spread, fit two pages in view
else
else
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
desiredZoom = minDimRatio * 100;
@@ -76,7 +76,10 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
return (
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
<button className='toggleButton' title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
<div className='toggleButton'>
<button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
<button title={`${headerState ? 'Hide' : 'Show'} Header Navigation`} onClick={()=>{setHeaderState(!headerState);}}><i className='fas fa-rectangle-list' /></button>
</div>
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
<div className='group' role='group' aria-label='Zoom' aria-hidden={!toolsVisible}>
<button

View File

@@ -166,7 +166,7 @@
&.hidden {
flex-wrap : nowrap;
width : 32px;
width : 92px;
overflow : hidden;
background-color : unset;
opacity : 0.5;
@@ -178,10 +178,12 @@
}
}
button.toggleButton {
.toggleButton {
position : absolute;
left : 0;
z-index : 5;
width : 32px;
min-width : unset;
height : 100%;
display : flex;
}

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;

View File

@@ -1,6 +1,6 @@
require('./brewItem.less');
const React = require('react');
const createClass = require('create-react-class');
const { useCallback } = React;
const moment = require('moment');
import request from '../../../../utils/request-middleware.js';
@@ -8,176 +8,172 @@ const googleDriveIcon = require('../../../../googleDrive.svg');
const homebreweryIcon = require('../../../../thumbnail.png');
const dedent = require('dedent-tabs').default;
const BrewItem = createClass({
displayName : 'BrewItem',
getDefaultProps : function() {
return {
brew : {
title : '',
description : '',
authors : [],
stubbed : true
},
updateListFilter : ()=>{},
reportError : ()=>{},
renderStorage : true
};
const BrewItem = ({
brew = {
title : '',
description : '',
authors : [],
stubbed : true,
},
updateListFilter = ()=>{},
reportError = ()=>{},
renderStorage = true,
})=>{
deleteBrew : function(){
if(this.props.brew.authors.length <= 1){
if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return;
const deleteBrew = useCallback(()=>{
if(brew.authors.length <= 1) {
if(!window.confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
if(!window.confirm('Are you REALLY sure? You will not be able to recover the document.')) return;
} else {
if(!confirm('Are you sure you want to remove this brew from your collection? This will remove you as an editor, but other owners will still be able to access the document.')) return;
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
if(!window.confirm('Are you sure you want to remove this brew from your collection? This will remove you as an editor, but other owners will still be able to access the document.')) return;
if(!window.confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
}
request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`)
.send()
.end((err, res)=>{
if(err) {
this.props.reportError(err);
} else {
location.reload();
}
});
},
request.delete(`/api/${brew.googleId ?? ''}${brew.editId}`).send().end((err, res)=>{
if (err) reportError(err); else window.location.reload();
});
}, [brew, reportError]);
updateFilter : function(type, term){
this.props.updateListFilter(type, term);
},
const updateFilter = useCallback((type, term)=> updateListFilter(type, term), [updateListFilter]);
renderDeleteBrewLink : function(){
if(!this.props.brew.editId) return;
const renderDeleteBrewLink = ()=>{
if(!brew.editId) return null;
return <a className='deleteLink' onClick={this.deleteBrew}>
<i className='fas fa-trash-alt' title='Delete' />
</a>;
},
return (
<a className='deleteLink' onClick={deleteBrew}>
<i className='fas fa-trash-alt' title='Delete' />
</a>
);
};
renderEditLink : function(){
if(!this.props.brew.editId) return;
const renderEditLink = ()=>{
if(!brew.editId) return null;
let editLink = this.props.brew.editId;
if(this.props.brew.googleId && !this.props.brew.stubbed) {
editLink = this.props.brew.googleId + editLink;
let editLink = brew.editId;
if(brew.googleId && !brew.stubbed) editLink = brew.googleId + editLink;
return (
<a className='editLink' href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fas fa-pencil-alt' title='Edit' />
</a>
);
};
const renderShareLink = ()=>{
if(!brew.shareId) return null;
let shareLink = brew.shareId;
if(brew.googleId && !brew.stubbed) {
shareLink = brew.googleId + shareLink;
}
return <a className='editLink' href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fas fa-pencil-alt' title='Edit' />
</a>;
},
return (
<a className='shareLink' href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fas fa-share-alt' title='Share' />
</a>
);
};
renderShareLink : function(){
if(!this.props.brew.shareId) return;
const renderDownloadLink = ()=>{
if(!brew.shareId) return null;
let shareLink = this.props.brew.shareId;
if(this.props.brew.googleId && !this.props.brew.stubbed) {
shareLink = this.props.brew.googleId + shareLink;
let shareLink = brew.shareId;
if(brew.googleId && !brew.stubbed) {
shareLink = brew.googleId + shareLink;
}
return <a className='shareLink' href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fas fa-share-alt' title='Share' />
</a>;
},
return (
<a className='downloadLink' href={`/download/${shareLink}`}>
<i className='fas fa-download' title='Download' />
</a>
);
};
renderDownloadLink : function(){
if(!this.props.brew.shareId) return;
let shareLink = this.props.brew.shareId;
if(this.props.brew.googleId && !this.props.brew.stubbed) {
shareLink = this.props.brew.googleId + shareLink;
const renderStorageIcon = ()=>{
if(!renderStorage) return null;
if(brew.googleId) {
return (
<span title={brew.webViewLink ? 'Your Google Drive Storage' : 'Another User\'s Google Drive Storage'}>
<a href={brew.webViewLink} target='_blank'>
<img className='googleDriveIcon' src={googleDriveIcon} alt='googleDriveIcon' />
</a>
</span>
);
}
return <a className='downloadLink' href={`/download/${shareLink}`}>
<i className='fas fa-download' title='Download' />
</a>;
},
return (
<span title='Homebrewery Storage'>
<img className='homebreweryIcon' src={homebreweryIcon} alt='homebreweryIcon' />
</span>
);
};
renderStorageIcon : function(){
if(!this.props.renderStorage) return;
if(this.props.brew.googleId) {
return <span title={this.props.brew.webViewLink ? 'Your Google Drive Storage': 'Another User\'s Google Drive Storage'}>
<a href={this.props.brew.webViewLink} target='_blank'>
<img className='googleDriveIcon' src={googleDriveIcon} alt='googleDriveIcon' />
</a>
</span>;
}
if(Array.isArray(brew.tags)) {
brew.tags = brew.tags?.filter((tag)=>tag); // remove tags that are empty strings
brew.tags.sort((a, b)=>{
return a.indexOf(':') - b.indexOf(':') !== 0 ? a.indexOf(':') - b.indexOf(':') : a.toLowerCase().localeCompare(b.toLowerCase());
});
}
return <span title='Homebrewery Storage'>
<img className='homebreweryIcon' src={homebreweryIcon} alt='homebreweryIcon' />
</span>;
},
const dateFormatString = 'YYYY-MM-DD HH:mm:ss';
render : function(){
const brew = this.props.brew;
if(Array.isArray(brew.tags)) { // temporary fix until dud tags are cleaned
brew.tags = brew.tags?.filter((tag)=>tag); //remove tags that are empty strings
brew.tags.sort((a, b)=>{
return a.indexOf(':') - b.indexOf(':') != 0 ? a.indexOf(':') - b.indexOf(':') : a.toLowerCase().localeCompare(b.toLowerCase());
});
}
const dateFormatString = 'YYYY-MM-DD HH:mm:ss';
return <div className='brewItem'>
{brew.thumbnail &&
<div className='thumbnail' style={{ backgroundImage: `url(${brew.thumbnail})` }} >
</div>
}
return (
<div className='brewItem'>
{brew.thumbnail && <div className='thumbnail' style={{ backgroundImage: `url(${brew.thumbnail})` }}></div>}
<div className='text'>
<h2>{brew.title}</h2>
<p className='description'>{brew.description}</p>
</div>
<hr />
<div className='info'>
{brew.tags?.length ? <>
{brew.tags?.length ? (
<div className='brewTags' title={`${brew.tags.length} tags:\n${brew.tags.join('\n')}`}>
<i className='fas fa-tags'/>
<i className='fas fa-tags' />
{brew.tags.map((tag, idx)=>{
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
return <span key={idx} className={matches[1]} onClick={()=>{this.updateFilter(tag);}}>{matches[2]}</span>;
return <span key={idx} className={matches[1]} onClick={()=>updateFilter(tag)}>{matches[2]}</span>;
})}
</div>
</> : <></>
}
) : null}
<span title={`Authors:\n${brew.authors?.join('\n')}`}>
<i className='fas fa-user'/> {brew.authors?.map((author, index)=>(
<i className='fas fa-user' />{' '}
{brew.authors?.map((author, index)=>(
<React.Fragment key={index}>
{author === 'hidden'
? <span title="Username contained an email address; hidden to protect user's privacy">{author}</span>
: <a href={`/user/${author}`}>{author}</a>
}
{author === 'hidden' ? (
<span title="Username contained an email address; hidden to protect user's privacy">
{author}
</span>
) : (<a href={`/user/${author}`}>{author}</a>)}
{index < brew.authors.length - 1 && ', '}
</React.Fragment>
))}
</span>
<br />
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
<i className='fas fa-eye'/> {brew.views}
<i className='fas fa-eye' /> {brew.views}
</span>
{brew.pageCount &&
{brew.pageCount && (
<span title={`Page count: ${brew.pageCount}`}>
<i className='far fa-file' /> {brew.pageCount}
</span>
}
<span title={dedent`
Created: ${moment(brew.createdAt).local().format(dateFormatString)}
Last updated: ${moment(brew.updatedAt).local().format(dateFormatString)}`}>
)}
<span
title={dedent` Created: ${moment(brew.createdAt).local().format(dateFormatString)}
Last updated: ${moment(brew.updatedAt).local().format(dateFormatString)}`}
>
<i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()}
</span>
{this.renderStorageIcon()}
{renderStorageIcon()}
</div>
<div className='links'>
{this.renderShareLink()}
{this.renderEditLink()}
{this.renderDownloadLink()}
{this.renderDeleteBrewLink()}
{renderShareLink()}
{renderEditLink()}
{renderDownloadLink()}
{renderDeleteBrewLink()}
</div>
</div>;
}
});
</div>
);
};
module.exports = BrewItem;

View File

@@ -432,40 +432,40 @@ const EditPage = createClass({
{this.renderNavbar()}
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
updateBrew={this.updateBrew}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
updateBrew={this.updateBrew}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
</div>
</div>;
}

View File

@@ -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'}
@@ -181,7 +186,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

@@ -1,6 +1,6 @@
require('./sharePage.less');
const React = require('react');
const createClass = require('create-react-class');
const { useState, useEffect, useCallback } = React;
const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
@@ -8,130 +8,120 @@ const Navbar = require('../../navbar/navbar.jsx');
const MetadataNav = require('../../navbar/metadata.navitem.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
const SharePage = createClass({
displayName : 'SharePage',
getDefaultProps : function() {
return {
brew : DEFAULT_BREW_LOAD,
disableMeta : false
};
},
const SharePage = (props)=>{
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
getInitialState : function() {
return {
themeBundle : {},
currentBrewRendererPageNum : 1
};
},
const [state, setState] = useState({
themeBundle : {},
currentBrewRendererPageNum : 1,
});
componentDidMount : function() {
document.addEventListener('keydown', this.handleControlKeys);
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
setState((prevState)=>({
currentBrewRendererPageNum : pageNumber,
...prevState }));
}, []);
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
},
componentWillUnmount : function() {
document.removeEventListener('keydown', this.handleControlKeys);
},
handleBrewRendererPageChange : function(pageNumber){
this.setState({ currentBrewRendererPageNum: pageNumber });
},
handleControlKeys : function(e){
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
const P_KEY = 80;
if(e.keyCode == P_KEY){
if(e.keyCode == P_KEY) printCurrentBrew();
if(e.keyCode === P_KEY) {
printCurrentBrew();
e.stopPropagation();
e.preventDefault();
}
},
};
processShareId : function() {
return this.props.brew.googleId && !this.props.brew.stubbed ?
this.props.brew.googleId + this.props.brew.shareId :
this.props.brew.shareId;
},
useEffect(()=>{
document.addEventListener('keydown', handleControlKeys);
fetchThemeBundle(
{ setState },
brew.renderer,
brew.theme
);
renderEditLink : function(){
if(!this.props.brew.editId) return;
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
};
}, []);
let editLink = this.props.brew.editId;
if(this.props.brew.googleId && !this.props.brew.stubbed) {
editLink = this.props.brew.googleId + editLink;
}
const processShareId = ()=>{
return brew.googleId && !brew.stubbed ? brew.googleId + brew.shareId : brew.shareId;
};
return <Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}>
edit
</Nav.item>;
},
const renderEditLink = ()=>{
if(!brew.editId) return null;
render : function(){
const titleStyle = this.props.disableMeta ? { cursor: 'default' } : {};
const titleEl = <Nav.item className='brewTitle' style={titleStyle}>{this.props.brew.title}</Nav.item>;
const editLink = brew.googleId && ! brew.stubbed ? brew.googleId + brew.editId : brew.editId;
return <div className='sharePage sitePage'>
return (
<Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}>
edit
</Nav.item>
);
};
const titleEl = (
<Nav.item className='brewTitle' style={disableMeta ? { cursor: 'default' } : {}}>
{brew.title}
</Nav.item>
);
return (
<div className='sharePage sitePage'>
<Meta name='robots' content='noindex, nofollow' />
<Navbar>
<Nav.section className='titleSection'>
{
this.props.disableMeta ?
titleEl
:
<MetadataNav brew={this.props.brew}>
{titleEl}
</MetadataNav>
}
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
</Nav.section>
<Nav.section>
{this.props.brew.shareId && <>
<PrintNavItem/>
<Nav.dropdown>
<Nav.item color='red' icon='fas fa-code'>
source
</Nav.item>
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${this.processShareId()}`}>
view
</Nav.item>
{this.renderEditLink()}
<Nav.item color='blue' icon='fas fa-download' href={`/download/${this.processShareId()}`}>
download
</Nav.item>
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${this.processShareId()}`}>
clone to new
</Nav.item>
</Nav.dropdown>
</>}
<VaultNavItem/>
<RecentNavItem brew={this.props.brew} storageKey='view' />
{brew.shareId && (
<>
<PrintNavItem />
<Nav.dropdown>
<Nav.item color='red' icon='fas fa-code'>
source
</Nav.item>
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${processShareId()}`}>
view
</Nav.item>
{renderEditLink()}
<Nav.item color='blue' icon='fas fa-download' href={`/download/${processShareId()}`}>
download
</Nav.item>
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${processShareId()}`}>
clone to new
</Nav.item>
</Nav.dropdown>
</>
)}
<RecentNavItem brew={brew} storageKey='view' />
<Account />
</Nav.section>
</Navbar>
<div className='content'>
<BrewRenderer
text={this.props.brew.text}
style={this.props.brew.style}
lang={this.props.brew.lang}
renderer={this.props.brew.renderer}
theme={this.props.brew.theme}
themeBundle={this.state.themeBundle}
onPageChange={this.handleBrewRendererPageChange}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
text={brew.text}
style={brew.style}
lang={brew.lang}
renderer={brew.renderer}
theme={brew.theme}
themeBundle={state.themeBundle}
onPageChange={handleBrewRendererPageChange}
currentBrewRendererPageNum={state.currentBrewRendererPageNum}
allowPrint={true}
/>
</div>
</div>;
}
});
</div>
);
};
module.exports = SharePage;

View File

@@ -1,4 +1,3 @@
version: '2'
services:
mongodb:
image: mongo:latest

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

549
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,6 +39,7 @@
"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",
@@ -84,16 +85,16 @@
]
},
"dependencies": {
"@babel/core": "^7.26.0",
"@babel/core": "^7.26.7",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@babel/preset-env": "^7.26.7",
"@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^8.14.0",
"body-parser": "^1.20.2",
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.7",
"core-js": "^3.39.0",
"core-js": "^3.40.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
@@ -102,7 +103,7 @@
"express": "^4.21.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
"fs-extra": "11.2.0",
"fs-extra": "11.3.0",
"idb-keyval": "^6.2.1",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
@@ -110,36 +111,36 @@
"lodash": "^4.17.21",
"marked": "11.2.0",
"marked-emoji": "^1.4.3",
"marked-extended-tables": "^1.0.10",
"marked-extended-tables": "^1.1.0",
"marked-gfm-heading-id": "^3.2.0",
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.9.2",
"mongoose": "^8.9.5",
"nanoid": "5.0.9",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
"react-router": "^7.1.1",
"react-router": "^7.1.3",
"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",
"babel-plugin-transform-import-meta": "^2.2.1",
"eslint": "^9.17.0",
"eslint-plugin-jest": "^28.10.0",
"eslint-plugin-react": "^7.37.3",
"babel-plugin-transform-import-meta": "^2.3.2",
"eslint": "^9.19.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.12.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"stylelint": "^16.14.1",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^15.0.0",
"supertest": "^7.0.0"
}
}

View File

@@ -1,7 +1,8 @@
/* eslint-disable max-depth */
/* eslint-disable max-lines */
import _ from 'lodash';
import { Parser as MathParser } from 'expr-eval';
import { marked as Marked } from 'marked';
import { marked as Marked } from 'marked';
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';
@@ -172,8 +173,8 @@ const mustacheSpans = {
return `<span` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${tags.styles ? ` style="${tags.styles}"` : ''}` +
`${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`>${this.parser.parseInline(token.tokens)}</span>`; // parseInline to turn child tokens into HTML
}
};
@@ -228,7 +229,7 @@ const mustacheDivs = {
return `<div` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${tags.styles ? ` style="${tags.styles}"` : ''}` +
`${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`>${this.parser.parse(token.tokens)}</div>`; // parse to turn child tokens into HTML
}
@@ -265,18 +266,13 @@ const mustacheInjectInline = {
const text = this.parser.parseInline([token]);
const originalTags = extractHTMLStyleTags(text);
const injectedTags = token.injectedTags;
const tags = {
id : injectedTags.id || originalTags.id || null,
classes : [originalTags.classes, injectedTags.classes].join(' ').trim() || null,
styles : [originalTags.styles, injectedTags.styles].join(' ').trim() || null,
attributes : Object.assign(originalTags.attributes ?? {}, injectedTags.attributes ?? {})
};
const tags = mergeHTMLTags(originalTags, injectedTags);
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
if(openingTag) {
return `${openingTag[1]}` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${tags.styles ? ` style="${tags.styles}"` : ''}` +
`${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`${openingTag[2]}`; // parse to turn child tokens into HTML
}
@@ -314,18 +310,13 @@ const mustacheInjectBlock = {
const text = this.parser.parse([token]);
const originalTags = extractHTMLStyleTags(text);
const injectedTags = token.injectedTags;
const tags = {
id : injectedTags.id || originalTags.id || null,
classes : [originalTags.classes, injectedTags.classes].join(' ').trim() || null,
styles : [originalTags.styles, injectedTags.styles].join(' ').trim() || null,
attributes : Object.assign(originalTags.attributes ?? {}, injectedTags.attributes ?? {})
};
const tags = mergeHTMLTags(originalTags, injectedTags);
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
if(openingTag) {
return `${openingTag[1]}` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${tags.styles ? ` style="${tags.styles}"` : ''}` +
`${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`${openingTag[2]}`; // parse to turn child tokens into HTML
}
@@ -370,6 +361,43 @@ const superSubScripts = {
}
};
const justifiedParagraphClasses = [];
justifiedParagraphClasses[2] = 'Left';
justifiedParagraphClasses[4] = 'Right';
justifiedParagraphClasses[6] = 'Center';
const justifiedParagraphs = {
name : 'justifiedParagraphs',
level : 'block',
start(src) {
return src.match(/\n(?:-:|:-|-:) {1}/m)?.index;
}, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /^(((:-))|((-:))|((:-:))) .+(\n(([^\n].*\n)*(\n|$))|$)/ygm;
const match = regex.exec(src);
if(match?.length) {
let whichJustify;
if(match[2]?.length) whichJustify = 2;
if(match[4]?.length) whichJustify = 4;
if(match[6]?.length) whichJustify = 6;
return {
type : 'justifiedParagraphs', // Should match "name" above
raw : match[0], // Text to consume from the source
length : match[whichJustify].length,
text : match[0].slice(match[whichJustify].length),
class : justifiedParagraphClasses[whichJustify],
tokens : this.lexer.inlineTokens(match[0].slice(match[whichJustify].length + 1))
};
}
},
renderer(token) {
return `<p align="${token.class}">${this.parser.parseInline(token.tokens)}</p>`;
}
};
const forcedParagraphBreaks = {
name : 'hardBreaks',
level : 'block',
@@ -680,7 +708,7 @@ function MarkedVariables() {
}
if(match[8]) { // Inline Definition
const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
let content = match[11] ? match[11].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
let content = match[11] || null;
// In case of nested (), find the correct matching end )
let level = 0;
@@ -696,10 +724,8 @@ function MarkedVariables() {
break;
}
}
if(i > -1) {
combinedRegex.lastIndex = combinedRegex.lastIndex - (content.length - i);
content = content.slice(0, i).trim().replace(/\s+/g, ' ');
}
combinedRegex.lastIndex = combinedRegex.lastIndex - (content.length - i);
content = content.slice(0, i).trim().replace(/\s+/g, ' ');
varsQueue.push(
{ type : 'varDefBlock',
@@ -769,7 +795,7 @@ const tableTerminators = [
];
Marked.use(MarkedVariables());
Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
nonbreakingSpaces, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
@@ -835,15 +861,20 @@ const processStyleTags = (string)=>{
const index = attr.indexOf('=');
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
value = value.replace(/"/g, '');
obj[key] = value;
obj[key.trim()] = value.trim();
return obj;
}, {}) || null;
const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()).join(' ') : null;
const styles = tags?.length ? tags.reduce((styleObj, style) => {
const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim();
return styleObj;
}, {}) : null;
return {
id : id,
classes : classes,
styles : styles,
styles : _.isEmpty(styles) ? null : styles,
attributes : _.isEmpty(attributes) ? null : attributes
};
};
@@ -853,25 +884,40 @@ const extractHTMLStyleTags = (htmlString)=>{
const firstElementOnly = htmlString.split('>')[0];
const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null;
const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null;
const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1] || null;
const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1]
?.split(';').reduce((styleObj, style) => {
if (style.trim() === '') return styleObj;
const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.trim();
return styleObj;
}, {}) || null;
const attributes = firstElementOnly.match(/[a-zA-Z]+="[^"]*"/g)
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{
const index = attr.indexOf('=');
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
value = value.replace(/"/g, '');
obj[key] = value;
obj[key.trim()] = value.replace(/"/g, '');
return obj;
}, {}) || null;
return {
id : id,
classes : classes,
styles : styles,
styles : _.isEmpty(styles) ? null : styles,
attributes : _.isEmpty(attributes) ? null : attributes
};
};
const mergeHTMLTags = (originalTags, newTags) => {
return {
id : newTags.id || originalTags.id || null,
classes : [originalTags.classes, newTags.classes].join(' ').trim() || null,
styles : Object.assign(originalTags.styles ?? {}, newTags.styles ?? {}),
attributes : Object.assign(originalTags.attributes ?? {}, newTags.attributes ?? {})
};
};
const globalVarsList = {};
let varsQueue = [];
let globalPageNumber = 0;

View File

@@ -300,7 +300,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
it('Renders a span "text" with its own styles, appended with injected styles', function() {
const source = '{{color:blue,height:10px text}}{width:10px,color:red}';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:blue; height:10px; width:10px; color:red;">text</span>');
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; height:10px; width:10px;">text</span>');
});
it('Renders a span "text" with its own classes, appended with injected classes', function() {
@@ -429,7 +429,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
}}
{width:10px,color:red}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:blue; height:10px; width:10px; color:red;"><p>text</p></div>');
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; height:10px; width:10px;"><p>text</p></div>');
});
it('Renders a span "text" with its own classes, appended with injected classes', function() {

View File

@@ -0,0 +1,27 @@
/* eslint-disable max-lines */
import Markdown from 'naturalcrit/markdown.js';
describe('Justification', ()=>{
test('Left Justify', function() {
const source = ':- Hello';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Left\">Hello</p>`);
});
test('Right Justify', function() {
const source = '-: Hello';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Right\">Hello</p>`);
});
test('Center Justify', function() {
const source = ':-: Hello';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Center\">Hello</p>`);
});
test('Ignored inside a code block', function() {
const source = '```\n\n:- Hello\n\n```\n';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:- Hello\n</code></pre>\n`);
});
});

View File

@@ -402,4 +402,12 @@ describe('Variable names that are subsets of other names', ()=>{
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<p>14</p>');
});
});
describe('Regression Tests', ()=>{
it('Don\'t Eat all the parentheticals!', function() {
const source='\n| title 1 | title 2 | title 3 | title 4|\n|-----------|---------|---------|--------|\n|[foo](bar) | Ipsum | ) | ) |\n';
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>');
});
});

View File

@@ -505,4 +505,4 @@ body { counter-reset : page-numbers 0; }
counter-increment : page-numbers;
}
}
}