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

Compare commits

..

205 Commits

Author SHA1 Message Date
Trevor Buckner
6898425435 Merge branch 'master' into ImplementContentVisibility 2024-12-24 01:21:20 -05:00
Trevor Buckner
be2557611e Merge pull request #3964 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-react-7.37.3
Bump eslint-plugin-react from 7.37.2 to 7.37.3
2024-12-24 01:12:46 -05:00
dependabot[bot]
1a9a726263 Bump eslint-plugin-react from 7.37.2 to 7.37.3
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.2 to 7.37.3.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/v7.37.3/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.2...v7.37.3)

---
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>
2024-12-24 06:02:15 +00:00
Trevor Buckner
dbf82f69f1 Merge pull request #3963 from naturalcrit/dependabot/npm_and_yarn/react-router-7.1.1
Bump react-router from 7.0.2 to 7.1.1
2024-12-24 01:01:03 -05:00
Trevor Buckner
107e54688b Implement content-visibility on pages 2024-12-24 01:00:32 -05:00
Trevor Buckner
b99282a5a7 Merge pull request #3845 from Gazook89/Intersection-Observer
Intersection Observers for getting "Current Page(s)"
2024-12-24 00:44:08 -05:00
Trevor Buckner
1c0eb720ad Undo 2024-12-24 00:38:36 -05:00
Trevor Buckner
93482f9022 Only list one page when in single page mode 2024-12-24 00:37:03 -05:00
Trevor Buckner
8159c408c8 Move formatVisiblePages
After simplifying, this has become a single-line function used in only one place. Can just be placed directly in the one place it is used.
2024-12-24 00:24:52 -05:00
Trevor Buckner
0632d78f71 Remove toolbar checks for empty visiblePages list
With `centerPage`, ToolBar will never receive an empty visiblePages array. No need to check if visiblepages.length == 0
2024-12-24 00:18:37 -05:00
Trevor Buckner
c0155052ea Further simplifying 2024-12-24 00:06:30 -05:00
Trevor Buckner
628b2542a0 Simplify logic for previous/next buttons 2024-12-24 00:02:55 -05:00
Trevor Buckner
85f1da942f Restore looping over entries. Needed for very fast scrolling 2024-12-23 23:08:30 -05:00
Trevor Buckner
3909d5aef9 remove unused iFrameRef
iFrameRef is not used anywhere
2024-12-23 22:48:57 -05:00
Trevor Buckner
f0e047e7cc Remove loop on intersectionObserver entries
Guaranteed to only be one entry each time, since we are attaching each page to its own observers.
2024-12-23 22:43:37 -05:00
dependabot[bot]
a1237305d7 Bump react-router from 7.0.2 to 7.1.1
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.0.2 to 7.1.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.1.1/packages/react-router)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-24 03:11:17 +00:00
Trevor Buckner
d588a92147 Change page range to only display a single range
Having multiple page ranges visible is a weird edge case that only happens in two-page view. Simplifying logic to just group all page ranges together if a middle page is partly obscured.
2024-12-23 18:37:20 -05:00
Trevor Buckner
2b7a1e1cb2 Reduce overlapping observer handlers
Combine handlePageVisibilityChange and handleCenterPageChange to reduce some of the infrastructure burden for handling centerPage.
2024-12-23 18:35:36 -05:00
Trevor Buckner
c8efca3120 useCallBack is not needed here. 2024-12-23 17:22:50 -05:00
Trevor Buckner
a53eacf055 remove CenterPage from ToolBar props
centerPage is not used in the toolbar component.
2024-12-23 17:17:13 -05:00
Trevor Buckner
1b10a4001a Merge branch 'master' into pr/3845 2024-12-23 11:37:40 -05:00
Trevor Buckner
75e71dd6f5 Merge pull request #3960 from G-Ambatte/addGoogleRefreshInfoToErrorPage-#3955
Add google refresh info to error page #3955
2024-12-22 22:31:31 -05:00
Trevor Buckner
3f87b9f7d3 Merge branch 'master' into addGoogleRefreshInfoToErrorPage-#3955 2024-12-22 22:30:04 -05:00
Trevor Buckner
32561cf368 Moving to just HBErrorCode 01
02 is specifically for 404 errors when the file is actually missing. In that case, refreshing credentials probably won't work. (We should update the errorNav to make this distinction as well.)
2024-12-22 22:19:02 -05:00
Trevor Buckner
bf94cdcb6f Merge pull request #3961 from naturalcrit/toWellFormedPolyfill
Use project babel config for buildHomebrew script
2024-12-22 21:39:14 -05:00
Trevor Buckner
e8eedcf6d6 Import polyfill from core-js
Possible to have babel automatically detect and import polyfills as needed, but Browserify just can't handle it. Manually importing the one troublesome one into the root of our project.
2024-12-22 21:32:30 -05:00
Trevor Buckner
92d1238a46 Use project babel config for buildHomebrew script
Jest uses the babel.config file already. Might as well all use the same config.
2024-12-22 21:31:31 -05:00
G.Ambatte
fcfd3171bd Tweak start of instructions 2024-12-22 18:00:27 +13:00
G.Ambatte
9a6cf8c5d2 Linter fix 2024-12-22 17:53:14 +13:00
G.Ambatte
91d928fd8a End list properly 2024-12-22 17:52:58 +13:00
G.Ambatte
bca653bc4d Add instructions to HBErrorCode 01 & 02 2024-12-22 17:52:09 +13:00
Trevor Buckner
dceb5e516b Merge pull request #3909 from dbolack-ab/horizontalSpace
V4 proposed non-breaking space token
2024-12-20 15:42:58 -05:00
Trevor Buckner
adb1db1d3c Revert one more regex change 2024-12-20 15:39:57 -05:00
David Bolack
e8d1e632b4 Merge branch 'master' into horizontalSpace 2024-12-20 14:08:31 -06:00
David Bolack
50fcffb253 Revert exclusion on single definition list regex
This permits  `Term ::> Definition` to process as a single line definition list
2024-12-20 14:06:20 -06:00
Trevor Buckner
aae5367ad2 Add test case for single-line definition list 2024-12-20 11:01:55 -05:00
Trevor Buckner
40b0c1ce3a Merge pull request #3957 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-jest-28.10.0
Bump eslint-plugin-jest from 28.9.0 to 28.10.0
2024-12-20 10:38:26 -05:00
dependabot[bot]
ba83dfacd9 Bump eslint-plugin-jest from 28.9.0 to 28.10.0
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 28.9.0 to 28.10.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.9.0...v28.10.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>
2024-12-20 04:26:51 +00:00
Trevor Buckner
2717e6a9a4 Merge pull request #3956 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.2
Bump mongoose from 8.9.1 to 8.9.2
2024-12-19 23:25:40 -05:00
Trevor Buckner
d576bddd32 Merge pull request #3958 from naturalcrit/Add-missing-test-suites-to-circleci
Add emoji tests to circleci
2024-12-19 23:25:26 -05:00
Trevor Buckner
fde21868cd Add emoji tests to circleci 2024-12-19 23:20:01 -05:00
Trevor Buckner
ed8c4d0eef Add tests to circleCi 2024-12-19 23:14:32 -05:00
Trevor Buckner
6e9d293bbe Rename tests to "Non-breaking Spaces"
Hard Breaks name was leftover from copying the `::::` test file.
2024-12-19 23:09:43 -05:00
dependabot[bot]
7e1312805f Bump mongoose from 8.9.1 to 8.9.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.1 to 8.9.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.9.1...8.9.2)

---
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-20 03:38:26 +00:00
David Bolack
d629fa1731 Merge branch 'master' into horizontalSpace 2024-12-19 17:45:51 -06:00
David Bolack
6301a66fd3 Add additional tests 2024-12-19 17:44:48 -06:00
Trevor Buckner
980a7bd57e Merge pull request #3954 from naturalcrit/dependabot/npm_and_yarn/globals-15.14.0
Bump globals from 15.13.0 to 15.14.0
2024-12-18 23:52:01 -05:00
Trevor Buckner
6b0022ad00 Merge branch 'master' into dependabot/npm_and_yarn/globals-15.14.0 2024-12-18 23:22:22 -05:00
Trevor Buckner
0f33973e58 Merge pull request #3953 from G-Ambatte/fixAdminAddedAsAuthor-#3952
Fix admin added as author #3952
2024-12-18 23:22:06 -05:00
G.Ambatte
7a41a140fd Merge branch 'master' into fixAdminAddedAsAuthor-#3952 2024-12-19 17:00:26 +13:00
Trevor Buckner
57467701d0 Fetch Google Brew if only stub requested but nothing found
/update/ requests only the stub for updating. But if no stub exists, we should fetch the full brew so we return *something*.
2024-12-18 23:00:01 -05:00
dependabot[bot]
9dbfb26e6c Bump globals from 15.13.0 to 15.14.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.13.0 to 15.14.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.13.0...v15.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-19 03:47:25 +00:00
G.Ambatte
7a169cbd9e Linter clean up 2024-12-19 16:20:28 +13:00
G.Ambatte
2dc8a8fbe9 Remove req.account from update request 2024-12-19 16:20:04 +13:00
Trevor Buckner
5f14f656ef Logging 2024-12-18 17:23:38 -05:00
Trevor Buckner
6e8a0d7314 current user owns 0-author brew only if edit mode
Previous code was treating /share/ visits to google brews with no stub as visits by owner, thus using their own credentials to open the file instead of serviceaccount
2024-12-18 17:07:09 -05:00
Trevor Buckner
e61144beb8 Mark as owner if stub doesn't exist
Old Google Drive files without a stub have no author, so if no stub exists, consider the current user the owner.
2024-12-18 13:45:53 -05:00
Trevor Buckner
64b792c645 Fix case where no stub is found
When retrieving a Google Brew with no stub yet, if the user is not logged in or has expired credentials, we enter this error handler. However, the error message itself tries to send a list of authors.

If there was no stub, we crash here with a 500 error.

This adds conditional operator to any stub value so we can send the actual "not logged in" error in case of no stub.
2024-12-18 13:02:14 -05:00
Trevor Buckner
aee5b7a8cc Require user to be logged in to change name 2024-12-18 12:14:08 -05:00
David Bolack
912f9f0cf6 Remove extraneous linefeeds in horizontalbreaks 2024-12-17 21:40:56 -06:00
David Bolack
c63b6ffaf0 Add test for a pair of inline horizontal breaks 2024-12-17 21:38:32 -06:00
David Bolack
0c90d1a14d Merge branch 'master' into horizontalSpace 2024-12-17 21:35:26 -06:00
Trevor Buckner
0148eafce0 Merge pull request #3951 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.1
Bump mongoose from 8.9.0 to 8.9.1
2024-12-17 19:34:44 -05:00
dependabot[bot]
a3ec5b8d3b Bump mongoose from 8.9.0 to 8.9.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.0 to 8.9.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.9.0...8.9.1)

---
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-17 03:25:20 +00:00
Trevor Buckner
4ded48df1e Merge pull request #3905 from naturalcrit/fixContentNegotiationTestFail-#3904
Fixes #3904 - content negotiation test failure
2024-12-16 18:11:52 -05:00
Trevor Buckner
bc14246fe7 fix import 2024-12-16 17:58:08 -05:00
Trevor Buckner
fcf985a115 Restore content-negotiation test 2024-12-16 17:42:06 -05:00
Trevor Buckner
a060fd123c Merge branch 'master' into fixContentNegotiationTestFail-#3904 2024-12-16 17:39:34 -05:00
Víctor Losada Hernández
7c7e143365 Merge pull request #3950 from naturalcrit/quickfix-CORS
Remove app.js logs to avoid cluttering and buffer overflow errors in heroku
2024-12-16 22:16:12 +01:00
Víctor Losada Hernández
efa8f3fedf remove unnecessary, cluttering logs 2024-12-16 22:13:14 +01:00
Víctor Losada Hernández
972a93d292 Merge pull request #3949 from naturalcrit/quickfix-CORS
quickfix to the CORS policy
2024-12-16 16:47:09 +01:00
Víctor Losada Hernández
35be1e9b94 quickfix 2024-12-16 16:43:40 +01:00
Trevor Buckner
1a91c390f8 Merge pull request #3948 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.12.0
Bump stylelint from 16.11.0 to 16.12.0
2024-12-16 00:09:18 -05:00
dependabot[bot]
206e4fbda8 Bump stylelint from 16.11.0 to 16.12.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.11.0 to 16.12.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.11.0...16.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 05:01:24 +00:00
Trevor Buckner
af98cb3867 Merge pull request #3947 from naturalcrit/dependabot/npm_and_yarn/eslint-9.17.0
Bump eslint from 9.16.0 to 9.17.0
2024-12-16 00:00:08 -05:00
dependabot[bot]
f8fc6f7aa4 Bump eslint from 9.16.0 to 9.17.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.16.0 to 9.17.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.16.0...v9.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 04:59:23 +00:00
Trevor Buckner
eb0fa28a03 Merge pull request #3946 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.0
Bump mongoose from 8.8.4 to 8.9.0
2024-12-15 23:58:09 -05:00
Trevor Buckner
4ab1a22eb3 Merge pull request #3863 from 5e-Cleric/fix-reddit-link-generation-crashing-website-if-encodeURI-fails
Fix crashes if title is invalid as URI
2024-12-15 23:57:38 -05:00
Trevor Buckner
962a46a670 Merge branch 'master' into fix-reddit-link-generation-crashing-website-if-encodeURI-fails 2024-12-15 23:46:54 -05:00
Trevor Buckner
cb16b32016 tabs 2024-12-15 23:45:54 -05:00
Trevor Buckner
56f348f7ed Replace with toWellFormed() 2024-12-15 23:44:56 -05:00
dependabot[bot]
b7c99b2d52 Bump mongoose from 8.8.4 to 8.9.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.8.4 to 8.9.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.8.4...8.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:57:07 +00:00
Trevor Buckner
889f80f537 Replace react-router-dom with react-router
react-router-dom has been replaced by react-router, so changing packages
2024-12-14 21:48:13 -05:00
Trevor Buckner
c270a69bb9 Merge pull request #3933 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-7.0.2
Bump react-router-dom from 6.28.0 to 7.0.2
2024-12-14 21:36:20 -05:00
Trevor Buckner
db0df82202 Change imports 2024-12-14 21:31:49 -05:00
Trevor Buckner
1346361f80 Merge branch 'dependabot/npm_and_yarn/react-router-dom-7.0.2' of https://github.com/naturalcrit/homebrewery into dependabot/npm_and_yarn/react-router-dom-7.0.2 2024-12-14 19:13:44 -05:00
dependabot[bot]
fdaf9d4808 Bump react-router-dom from 6.28.0 to 7.0.2
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.28.0 to 7.0.2.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.0.2/packages/react-router-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-15 00:10:44 +00:00
Víctor Losada Hernández
3cdfae4270 Merge pull request #3942 from naturalcrit/re-author-brews-on-batch
Create backend batch re-author framework
2024-12-15 00:09:48 +01:00
Víctor Losada Hernández
a9275698fa add comment to tell future me to remove logs when feature comes 2024-12-14 23:41:05 +01:00
Víctor Losada Hernández
99f2972079 fixes as asked 2024-12-14 23:34:12 +01:00
Víctor Losada Hernández
afc92c4545 fix check client version middleware to stop checking outside calls 2024-12-14 22:30:24 +01:00
Víctor Losada Hernández
b26526a2f1 lint and logging pass prepared for in server auth from this end 2024-12-14 22:24:52 +01:00
Víctor Losada Hernández
4f57f006ce log cookies at auth middleware 2024-12-14 21:20:39 +01:00
Víctor Losada Hernández
666a94cd65 fix log 2024-12-14 21:15:16 +01:00
Víctor Losada Hernández
f0c094e9d8 logs to account middleware 2024-12-14 21:12:35 +01:00
Víctor Losada Hernández
a1c228b1d1 log req account 2024-12-14 21:01:38 +01:00
Víctor Losada Hernández
5e5c637c79 revert api catch on wrong route middleware 2024-12-14 20:59:51 +01:00
Víctor Losada Hernández
d573129f31 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into re-author-brews-on-batch 2024-12-14 20:34:41 +01:00
David Bolack
abd52f93d8 Merge branch 'master' into horizontalSpace 2024-12-10 23:21:26 -06:00
Trevor Buckner
57cb334c15 Update pull_request_template.md 2024-12-10 13:35:16 -05:00
Víctor Losada Hernández
c29e1905bf add localhost to allowed origins only if in local, also remake regex 2024-12-10 19:24:23 +01:00
Trevor Buckner
52d00b17a4 Merge branch 'master' into fixContentNegotiationTestFail-#3904 2024-12-10 11:58:05 -05:00
dependabot[bot]
35364c400a Bump react-router-dom from 6.28.0 to 7.0.2
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.28.0 to 7.0.2.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.0.2/packages/react-router-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-10 16:23:58 +00:00
Trevor Buckner
77f0c1bf56 Merge pull request #3944 from naturalcrit/dependabot/npm_and_yarn/dompurify-3.2.3
Bump dompurify from 3.2.2 to 3.2.3
2024-12-10 11:22:39 -05:00
dependabot[bot]
2d281072fa Bump dompurify from 3.2.2 to 3.2.3
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.2...3.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-10 03:53:51 +00:00
Trevor Buckner
870a4c3363 small cleanups 2024-12-09 17:06:26 -05:00
Trevor Buckner
aa951ff96c Small cleanups 2024-12-09 17:04:16 -05:00
Víctor Losada Hernández
83b8f9c3b7 Merge pull request #3809 from 5e-Cleric/adress-small-accessibility-concerns
Adress small accessibility concerns
2024-12-09 22:28:52 +01:00
Víctor Losada Hernández
3a20452214 hide unusable editors 2024-12-09 22:18:15 +01:00
Trevor Buckner
bae9fe939d Merge branch 'master' into Intersection-Observer 2024-12-09 16:16:11 -05:00
Víctor Losada Hernández
3e4ba89ed9 change div selector 2024-12-09 22:11:10 +01:00
Víctor Losada Hernández
2c5c3d40df revert toolbar changes 2024-12-09 22:08:39 +01:00
Víctor Losada Hernández
213240327d resolve issues 2024-12-09 22:05:04 +01:00
Víctor Losada Hernández
eca0f59b40 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into adress-small-accessibility-concerns 2024-12-09 21:52:25 +01:00
Trevor Buckner
51936a1b99 Merge pull request #3936 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.8.4
Bump mongoose from 8.8.3 to 8.8.4
2024-12-09 13:44:42 -05:00
Trevor Buckner
6136b78395 Merge pull request #3943 from naturalcrit/Fix_#2954
Get Google Brews with user auth if owner
2024-12-09 13:44:01 -05:00
Víctor Losada Hernández
81f56ec91d add heroku apps to cors 2024-12-09 18:59:48 +01:00
Trevor Buckner
c7d94b0779 Small cleanup 2024-12-08 23:59:27 -05:00
Trevor Buckner
9758797e2b If user is owner, fetch Google Brew with user auth
Fixes the case where a user can see a Google Brew under their account (`listBrew()` uses their personal auth) but can't actually delete it (`getBrew()`  only uses the serviceAccount). Occurs if a Google brew has lost its permissions somehow (set to "restricted", etc.) such that serviceAccount can no longer interact with it.
2024-12-08 23:42:14 -05:00
Trevor Buckner
74a7983757 Refactor and clean up "getBrew()"
Some redundant logic and sprawling formatting
2024-12-08 23:39:26 -05:00
Víctor Losada Hernández
4eb8abf1e7 Update CORS error message in app.js 2024-12-08 23:46:27 +01:00
Víctor Losada Hernández
23910cc94c add cors policy and rename route 2024-12-08 23:43:32 +01:00
Víctor Losada Hernández
ef0ee78758 revert check client version changes 2024-12-08 23:43:06 +01:00
Víctor Losada Hernández
1b20c00842 log headers 2024-12-08 20:46:02 +01:00
Víctor Losada Hernández
db9212bd12 log req 2024-12-08 20:45:43 +01:00
Víctor Losada Hernández
7348ecbb3d Merge branch 'master' of https://github.com/naturalcrit/homebrewery into re-author-brews-on-batch 2024-12-08 12:07:53 +01:00
Víctor Losada Hernández
31a22703c1 initial commit 2024-12-08 12:05:01 +01:00
Trevor Buckner
33f8f6bf38 Merge branch 'master' into dependabot/npm_and_yarn/mongoose-8.8.4 2024-12-07 21:32:53 -05:00
Trevor Buckner
a62d2bd457 Merge pull request #3937 from naturalcrit/dependabot/npm_and_yarn/express-4.21.2
Bump express from 4.21.1 to 4.21.2
2024-12-06 16:30:42 -05:00
dependabot[bot]
ffa9666bb9 Bump express from 4.21.1 to 4.21.2
Bumps [express](https://github.com/expressjs/express) from 4.21.1 to 4.21.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-06 21:29:54 +00:00
Trevor Buckner
406f5d4e14 Merge branch 'master' into dependabot/npm_and_yarn/mongoose-8.8.4 2024-12-06 16:29:02 -05:00
Trevor Buckner
ed404d3906 Merge pull request #3935 from naturalcrit/dependabot/npm_and_yarn/babel/preset-react-7.26.3
Bump @babel/preset-react from 7.25.9 to 7.26.3
2024-12-06 16:28:35 -05:00
dependabot[bot]
3178c8722e Bump mongoose from 8.8.3 to 8.8.4
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.8.3 to 8.8.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.8.3...8.8.4)

---
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-06 03:07:01 +00:00
David Bolack
596c4ad68d Add Tests 2024-12-04 21:24:48 -06:00
dependabot[bot]
14a0f79ac8 Bump @babel/preset-react from 7.25.9 to 7.26.3
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.25.9 to 7.26.3.
- [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.3/packages/babel-preset-react)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-05 03:16:54 +00:00
David Bolack
e7f4611a00 Merge branch 'master' into horizontalSpace 2024-12-04 20:40:55 -06:00
Trevor Buckner
136a6d4024 Merge pull request #3930 from naturalcrit/dependabot/npm_and_yarn/globals-15.13.0
Bump globals from 15.12.0 to 15.13.0
2024-12-03 20:43:13 -05:00
Trevor Buckner
737e27f062 Merge pull request #3934 from 5e-Cleric/fix-buttons
Fix Vault styles
2024-12-03 20:42:54 -05:00
Víctor Losada Hernández
ee9143fa35 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-buttons 2024-12-03 20:20:42 +01:00
Víctor Losada Hernández
c62bb53660 turn back checkboxes to default, fix button styles and filter bar to correct font. 2024-12-03 20:16:42 +01:00
dependabot[bot]
4e5a971f0a Bump globals from 15.12.0 to 15.13.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.12.0 to 15.13.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.12.0...v15.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 22:22:41 +00:00
Trevor Buckner
16184f1b8d Merge pull request #3916 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.0.9
Bump nanoid from 5.0.8 to 5.0.9
2024-12-02 17:21:22 -05:00
dependabot[bot]
23bd0309b9 Bump nanoid from 5.0.8 to 5.0.9
Bumps [nanoid](https://github.com/ai/nanoid) from 5.0.8 to 5.0.9.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.0.8...5.0.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 22:17:58 +00:00
Trevor Buckner
20dba6b7b3 Merge pull request #3929 from naturalcrit/dependabot/npm_and_yarn/eslint-9.16.0
Bump eslint from 9.15.0 to 9.16.0
2024-12-02 17:15:55 -05:00
dependabot[bot]
5177c9a64e Bump eslint from 9.15.0 to 9.16.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.15.0 to 9.16.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.15.0...v9.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 22:04:50 +00:00
Trevor Buckner
d179c18c35 Merge pull request #3928 from naturalcrit/dependabot/npm_and_yarn/dompurify-3.2.2
Bump dompurify from 3.2.1 to 3.2.2
2024-12-02 17:03:16 -05:00
dependabot[bot]
6e4e35c7ad Bump dompurify from 3.2.1 to 3.2.2
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.1...3.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 17:27:52 +00:00
Trevor Buckner
6c2721d49f Merge pull request #3924 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.11.0
Bump stylelint from 16.10.0 to 16.11.0
2024-12-02 12:26:19 -05:00
Trevor Buckner
029d61b6ad Merge branch 'master' into dependabot/npm_and_yarn/stylelint-16.11.0 2024-12-02 12:25:48 -05:00
Trevor Buckner
f58040e9a4 Merge pull request #3931 from G-Ambatte/tagsToArrayOnLoad-#3927
Convert any `tags` strings to arrays
2024-12-02 12:25:05 -05:00
Trevor Buckner
9f9948f531 Add comment 2024-12-02 12:23:54 -05:00
G.Ambatte
2743ab869a Merge branch 'master' into tagsToArrayOnLoad-#3927 2024-12-02 17:17:35 +13:00
G.Ambatte
4b21538e3e Add splitTextStyleAndMetadata tests 2024-12-02 17:14:45 +13:00
G.Ambatte
e17db0788c Convert any tags strings to arrays 2024-12-02 16:18:18 +13:00
Trevor Buckner
bea74c3b46 Merge pull request #3926 from dbolack-ab/Issue_3919
Implement Gazook89's suggested fix for missing Page Shadows
2024-12-01 20:57:57 -05:00
David Bolack
e252a39bd2 Implement Gazook89's suggested fix 2024-12-01 11:41:29 -06:00
dependabot[bot]
7ef259ddbe Bump stylelint from 16.10.0 to 16.11.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.10.0 to 16.11.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.10.0...16.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-30 17:40:38 +00:00
Trevor Buckner
d18005fad4 Merge pull request #3915 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.8.3
Bump mongoose from 8.8.2 to 8.8.3
2024-11-30 12:39:20 -05:00
Trevor Buckner
86402cdbc8 Merge pull request #3920 from 5e-Cleric/changelog-v3.17.0
v3.16.1 FaQ fix
2024-11-30 12:38:46 -05:00
Trevor Buckner
e28b4e8c20 Merge pull request #3921 from 5e-Cleric/add-vault-to-more-navs
add vault navitem to share and edit pages
2024-11-27 17:50:12 -05:00
Víctor Losada Hernández
7c09680939 add vault navitem to share and edit pages 2024-11-27 23:45:25 +01:00
Víctor Losada Hernández
3f0a6a577f faq new version 2024-11-27 23:28:08 +01:00
dependabot[bot]
6f4cc0d91b Bump mongoose from 8.8.2 to 8.8.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.8.2 to 8.8.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.8.2...8.8.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-11-27 21:29:38 +00:00
Trevor Buckner
e711a1c207 Merge pull request #3917 from naturalcrit/v3.16.1
Version 3.16.1
2024-11-27 16:28:20 -05:00
G.Ambatte
7a76c67038 Merge branch 'master' into fixContentNegotiationTestFail-#3904 2024-11-24 20:44:24 +13:00
David Bolack
b45686eb3b Create an element for serial non-breaking spaces as proposed in V4 discussion 2024-11-23 11:18:44 -06:00
G.Ambatte
108d368d45 Add content-negotiation test to CircleCI config 2024-11-21 18:54:23 +13:00
G.Ambatte
bd413cfc55 Add content negotiation test command 2024-11-21 18:53:20 +13:00
G.Ambatte
1af13b4e94 Fixes #3904 - content negotiation test failure 2024-11-21 18:46:59 +13:00
Gazook89
4bad047f93 Merge branch 'master' into Intersection-Observer 2024-11-10 15:56:03 -06:00
Gazook89
28a7f24989 add scrollToHash method back in
pretty much completely unchanged, was originally moved just to help with merging master in (ie it was erroneously removed)
2024-11-07 20:32:30 -06:00
Gazook89
28855d02a6 dynamic text input width to match characters 2024-11-07 19:46:07 -06:00
Gazook89
650ec04417 fix 'disabled' attribute on min/max of page range 2024-11-07 18:56:19 -06:00
Gazook89
9ef11bca99 lint and refactor 2024-11-07 10:40:44 -06:00
Gazook89
88b34a7ba3 Fix 'current page' input when zoomed in close
When the page is zoomed in very close, such that <30% of the page is in view, it doesn't register changes to the 'current page'.  This fixes that, passing in the 'centerPage' if 'visiblePages' is empty.

I don't love this fix, i think the visiblePages should always have *something* in it, but I can't quite figure out how to set that (since the normal update to visiblePages is happening in an observer that doesn't fire if nothing is in view).
2024-11-07 10:17:43 -06:00
Gazook89
9d86384032 refactor styles 2024-11-06 23:07:46 -06:00
Gazook89
a6bc87bcea apply displayOptions to legacy brews as well. 2024-11-06 23:07:03 -06:00
Gazook89
63add047b6 'fit page' zoom button fits two pages in "facing" spread
If the 'facing' spread is active, the 'fit to view' zoom button fits two pages side by side in the view, rather than setting only one page in view.
2024-11-06 23:03:24 -06:00
Gazook89
a0e88bb24f Add comment about future Popover API use 2024-11-06 21:55:30 -06:00
Gazook89
5b14e0e9b5 tweak alignment of spreads
the `safe` keyword for `justify-content`, in combo with `center`, means that the content will be centered in the viewport unless there is not enough space for it.  If there is not enough space, it aligns it to the *start*/left edge, rather than keeping it centered and clipping the left edge of the page.
2024-11-06 21:55:16 -06:00
Gazook89
274e734135 add displayOptions to dependency array for memo
The memoization of the renderPages() method prevents a re-render when something like pageShadows is updated, so displayOptions are added to the dependency array in the memo method.
2024-11-06 21:53:14 -06:00
Gazook89
3818424251 Adjust "next page" button
Prior to fix, the "next page" button in the toolbar wouldn't work well if there were multiple pages in view that were in a single 'row'.  This is because the logic is to take the pages that are "visible", take the max of those pages, and then scroll to that page.  But the issue is that if the 'max' page is in the same row as other pages, the range of visible pages doesn't change....the max will always be the same.

So the change here basically runs the scroll function twice-- if the first run results in the same 'max' page as before the scroll, it runs it again but with the target page being "max + 1", which will bump the target to the next row.
2024-11-06 21:24:18 -06:00
Gazook89
2222550669 Merge branch 'master' into Observer-Master-merge 2024-11-06 21:19:35 -06:00
Gazook89
93b9f1d1da Merge branch 'Intersection-Observer' into Observer-Master-merge 2024-11-05 14:03:09 -06:00
Víctor Losada Hernández
2abc2b13f0 wrap encodeURI in try catch 2024-10-30 22:47:17 +01:00
Gazook89
5ab867f21e adjust prev/next page buttons to meet expectations
i hope
2024-10-22 22:36:13 -05:00
Gazook89
4126188df1 linting 2024-10-21 22:29:58 -05:00
Gazook89
26050e2134 add comment 2024-10-21 22:20:52 -05:00
Gazook89
5c0d6e6012 move formatting of visible pages to toolbar
Doesn't need to be set in brewRenderer state and passed as a prop, when it can just do it's work directly in the toolbar.
2024-10-21 22:18:25 -05:00
Gazook89
de7b13bc15 Add some comments and cleanup
Little changes like removing console.logs and adding comments.
2024-10-21 22:13:12 -05:00
Gazook89
b6bd7ccf67 Merge branch 'master' into Intersection-Observer 2024-10-21 21:33:30 -05:00
Gazook89
822d0c7738 Fix NaN/undefined showing on first load
Removes currentPage as a variable since it's been replaced.
2024-10-21 21:27:06 -05:00
Gazook89
183dd63021 style change on page text input
Reduce the visual prominence of the page input by using a darker background and a text color that matches the rest of the toolbar icons.  Darker background still indicates this is an interactive item (is an input), hopefully.
2024-10-21 21:19:49 -05:00
Gazook89
0afc2ab2e6 modify effect to enable Jump Editor button
This fixes the "jump editor to preview position" button.
2024-10-21 20:43:32 -05:00
Gazook89
119755e23a Merge branch 'master' into Intersection-Observer 2024-10-21 00:33:56 -05:00
Gazook89
41fdf48ad3 Setup Intersection Observers & more...
Bad commit here with too much stuff.  I apologize.

This sets up two Intersection Observers: the first captures every page that is at least 30% visible inside the `.pages` container, and the second captures every page that has at least one pixel on the horizontal center line of `.pages`.  Both can be arrays of integers (page index).

The "visiblePages" array is duplicated and formatted into a "formattedPages" state, which gets displayed in the toolbar.

The toolbar displays that, unless the user clicks into the page input and enters their own integer (only a single integer, no range), which can then jump the preview to that page on Enter or blur().

The Arrow 'change page' buttons jump the preview back and forth by a 'full set'.
 If one page is viewed at a time, this is moved on page a time, and if 10 pages are viewed at a time it jumps the pages by 10.

Left to do:  adapt the "jump editor to match preview" divider button to work with new "centerPage".
2024-10-21 00:30:45 -05:00
Trevor Buckner
35856ad01e Merge branch 'master' into adress-small-accessibility-concerns 2024-10-11 23:50:39 -04:00
Víctor Losada Hernández
766fd40b72 set cursor in code less for disabled elements 2024-10-11 00:35:43 +02:00
Víctor Losada Hernández
3e6884b506 dynamic input width 2024-10-11 00:32:28 +02:00
Víctor Losada Hernández
2118142faa not allowed cursor 2024-10-11 00:16:36 +02:00
Víctor Losada Hernández
2b270ccdb7 linting 2024-10-11 00:14:12 +02:00
Víctor Losada Hernández
08eabf8102 proper snippetbar styles 2024-10-11 00:12:52 +02:00
Víctor Losada Hernández
c1d85bc216 remove annoying class 2024-10-11 00:12:39 +02:00
Víctor Losada Hernández
3a2c213cf8 linting codeeditor.les 2024-10-11 00:02:00 +02:00
Víctor Losada Hernández
99dc0deb08 foldgutter styles 2024-10-11 00:00:57 +02:00
33 changed files with 1381 additions and 1715 deletions

View File

@@ -70,9 +70,15 @@ jobs:
- run:
name: Test - Hard Breaks
command: npm run test:hard-breaks
- run:
name: Test - Non-Breaking Spaces
command: npm run test:non-breaking-spaces
- run:
name: Test - Variables
command: npm run test:variables
- run:
name: Test - Emojis
command: npm run test:emojis
- run:
name: Test - Routes
command: npm run test:route
@@ -82,6 +88,9 @@ jobs:
- run:
name: Test - Coverage
command: npm run test:coverage
- run:
name: Test - Content Negotiation
command: npm run test:content-negotiation
workflows:
build_and_test:

View File

@@ -1,26 +1,29 @@
<!--
Before submitting a Pull Request, please consider the following to speed up reviews:
- 👷‍♀️ Create small PRs. Large PRs can usually be broken down into incremental PRs.
- 🚩 Do you already have several open PRs? Consider finishing or asking for help with existing PRs first.
- 🔧 Does your PR reference a discussed and approved issue, especially for personal or edge-case requests?
- 💡 Is the solution agreed upon? Save rework time by discussing strategy before coding.
-->
> [!TIP]
> Before submitting a Pull Request, please consider the following to speed up reviews:
> - 👷‍♀️ Create small PRs. Large PRs can usually be broken down into incremental PRs.
> - 🚩 Do you already have several open PRs? Consider finishing or asking for help with existing PRs first.
> - 🔧 Does your PR reference a discussed and approved issue, especially for personal or edge-case requests?
> - 💡 Is the solution agreed upon? Save rework time by discussing strategy before coding.
## Description
_Describe what your PR accomplishes. Consider walking through the main changes to aid reviewers in following your code, especially if it covers multiple files._
## Related Issues or Discussions
> [!CAUTION]
> If no issue exists yet, create it, and get agreement on the approach (or paste in a previous agreement from chat, etc.) before moving forward. (Experimental PRs are OK without prior discussion, but do not expect to get merged.)
- Closes #
## QA Instructions, Screenshots, Recordings
_Please replace this line with instructions on how to test or view your changes, as well as any before/after
images for UI changes._
_Replace this line with instructions on how to test or view your changes, as well as any before/after
screenshots or recordings for UI changes._
### Reviewer Checklist
_Please replace the list below with specific features you want reviewers to look at._
_Replace the list below with specific features you want reviewers to look at._
*Reviewers, refer to this list when testing features, or suggest new items *
- [ ] Verify new features are functional
@@ -32,5 +35,3 @@ _Please replace the list below with specific features you want reviewers to look
- [ ] Feature A handles negative numbers
- [ ] Identify opportunities for simplification and refactoring
- [ ] Check for code legibility and appropriate comments
<details><summary>Copy this list</summary>

View File

@@ -7,6 +7,11 @@ import './Anchored.less';
// **The Anchor Positioning API is not available in Firefox yet**
// So in Firefox the positioning isn't perfect but is likely sufficient, and FF team seems to be working on the API quickly.
// When Anchor Positioning is added to Firefox, this can also be rewritten using the Popover API-- add the `popover` attribute
// to the container div, which will render the container in the *top level* and give it better interactions like
// click outside to dismiss. **Do not** add without Anchor, though, because positioning is very limited with the `popover`
// attribute.
const Anchored = ({ children })=>{
const [visible, setVisible] = useState(false);

View File

@@ -1,7 +1,7 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./brewRenderer.less');
const React = require('react');
const { useState, useRef, useCallback, useMemo } = React;
const { useState, useRef, useMemo, useEffect } = React;
const _ = require('lodash');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
@@ -36,8 +36,46 @@ const BrewPage = (props)=>{
index : 0,
...props
};
const pageRef = useRef(null);
const cleanText = safeHTML(props.contents);
return <div className={props.className} id={`p${props.index + 1}`} style={props.style}>
useEffect(()=>{
if(!pageRef.current) return;
// Observer for tracking pages within the `.pages` div
const visibleObserver = new IntersectionObserver(
(entries)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting)
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.
);
// Observer for tracking the page at the center of the iframe.
const centerObserver = new IntersectionObserver(
(entries)=>{
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
);
// attach observers to each `.page`
visibleObserver.observe(pageRef.current);
centerObserver.observe(pageRef.current);
return ()=>{
visibleObserver.disconnect();
centerObserver.disconnect();
};
}, []);
return <div className={props.className} id={`p${props.index + 1}`} data-index={props.index} ref={pageRef} style={props.style}>
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
</div>;
};
@@ -64,8 +102,10 @@ const BrewRenderer = (props)=>{
};
const [state, setState] = useState({
isMounted : false,
visibility : 'hidden'
isMounted : false,
visibility : 'hidden',
visiblePages : [],
centerPage : 1
});
const [displayOptions, setDisplayOptions] = useState({
@@ -83,34 +123,23 @@ const BrewRenderer = (props)=>{
rawPages = props.text.split(/^\\page$/gm);
}
const scrollToHash = (hash)=>{
if(!hash) return;
const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{
setState((prevState)=>{
const updatedVisiblePages = new Set(prevState.visiblePages);
if(!isCenter)
isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum);
const iframeDoc = document.getElementById('BrewRenderer').contentDocument;
let anchor = iframeDoc.querySelector(hash);
return {
...prevState,
visiblePages : [...updatedVisiblePages].sort((a, b)=>a - b),
centerPage : isCenter ? pageNum : prevState.centerPage
};
});
if(anchor) {
anchor.scrollIntoView({ behavior: 'smooth' });
} else {
// Use MutationObserver to wait for the element if it's not immediately available
new MutationObserver((mutations, obs)=>{
anchor = iframeDoc.querySelector(hash);
if(anchor) {
anchor.scrollIntoView({ behavior: 'smooth' });
obs.disconnect();
}
}).observe(iframeDoc, { childList: true, subtree: true });
}
if(isCenter)
props.onPageChange(pageNum);
};
const updateCurrentPage = useCallback(_.throttle((e)=>{
const { scrollTop, clientHeight, scrollHeight } = e.target;
const totalScrollableHeight = scrollHeight - clientHeight;
const currentPageNumber = Math.max(Math.ceil((scrollTop / totalScrollableHeight) * rawPages.length), 1);
props.onPageChange(currentPageNumber);
}, 200), []);
const isInView = (index)=>{
if(!state.isMounted)
return false;
@@ -137,19 +166,21 @@ const BrewRenderer = (props)=>{
};
const renderPage = (pageText, index)=>{
const styles = {
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
// Add more conditions as needed
};
if(props.renderer == 'legacy') {
const html = MarkdownLegacy.render(pageText);
return <BrewPage className='page phb' index={index} key={index} contents={html} />;
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
} else {
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);
const styles = {
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
// Add more conditions as needed
};
return <BrewPage className='page' index={index} key={index} contents={html} style={styles} />;
return <BrewPage className='page' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
}
};
@@ -182,6 +213,26 @@ const BrewRenderer = (props)=>{
}
};
const scrollToHash = (hash)=>{
if(!hash) return;
const iframeDoc = document.getElementById('BrewRenderer').contentDocument;
let anchor = iframeDoc.querySelector(hash);
if(anchor) {
anchor.scrollIntoView({ behavior: 'smooth' });
} else {
// Use MutationObserver to wait for the element if it's not immediately available
new MutationObserver((mutations, obs)=>{
anchor = iframeDoc.querySelector(hash);
if(anchor) {
anchor.scrollIntoView({ behavior: 'smooth' });
obs.disconnect();
}
}).observe(iframeDoc, { childList: true, subtree: true });
}
};
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
scrollToHash(window.location.hash);
@@ -217,13 +268,13 @@ const BrewRenderer = (props)=>{
}
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
renderedPages = useMemo(()=>renderPages(), [props.text]);
renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]);
return (
<>
{/*render dummy page while iFrame is mounting.*/}
{!state.isMounted
? <div className='brewRenderer' onScroll={updateCurrentPage}>
? <div className='brewRenderer'>
<div className='pages'>
{renderDummyPage(1)}
</div>
@@ -236,7 +287,7 @@ const BrewRenderer = (props)=>{
<NotificationPopup />
</div>
<ToolBar displayOptions={displayOptions} currentPage={props.currentBrewRendererPageNum} totalPages={rawPages.length} onDisplayOptionsChange={handleDisplayOptionsChange} />
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length}/>
{/*render in iFrame so broken code doesn't crash the site.*/}
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
@@ -245,18 +296,17 @@ const BrewRenderer = (props)=>{
onClick={()=>{emitClick();}}
>
<div className={`brewRenderer ${global.config.deployment && 'deployment'}`}
onScroll={updateCurrentPage}
onKeyDown={handleControlKeys}
tabIndex={-1}
style={ styleObject }>
style={ styleObject }
>
{/* Apply CSS from Style tab and render pages from Markdown tab */}
{state.isMounted
&&
<>
{renderedStyle}
<div lang={`${props.lang || 'en'}`} style={pagesStyle} className={
`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}` } >
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle}>
{renderedPages}
</div>
</>

View File

@@ -17,7 +17,7 @@
grid-template-columns: repeat(2, auto);
grid-template-rows: repeat(3, auto);
gap: 10px 10px;
justify-content: center;
justify-content: safe center;
&.recto .page:first-child {
// sets first page on 'right' ('recto') of the preview, as if for a Cover page.
// todo: add a checkbox to toggle this setting
@@ -33,7 +33,7 @@
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-start;
justify-content: safe center;
& :where(.page) {
flex: 0 0 auto;
margin-left: unset !important;

View File

@@ -1,29 +1,30 @@
/* eslint-disable max-lines */
require('./toolBar.less');
const React = require('react');
const { useState, useEffect } = React;
const _ = require('lodash');
import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anchored.jsx';
// import * as ZoomIcons from '../../../icons/icon-components/zoomIcons.jsx';
const MAX_ZOOM = 300;
const MIN_ZOOM = 10;
const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChange })=>{
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages })=>{
const [pageNum, setPageNum] = useState(currentPage);
const [pageNum, setPageNum] = useState(1);
const [toolsVisible, setToolsVisible] = useState(true);
useEffect(()=>{
setPageNum(currentPage);
}, [currentPage]);
// format multiple visible pages as a range (e.g. "150-153")
const pageRange = visiblePages.length === 1 ? `${visiblePages[0]}` : `${visiblePages[0]} - ${visiblePages.at(-1)}`;
setPageNum(pageRange);
}, [visiblePages]);
const handleZoomButton = (zoom)=>{
handleOptionChange('zoomLevel', _.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
};
const handleOptionChange = (optionKey, newValue)=>{
//setDisplayOptions(prevOptions => ({ ...prevOptions, [optionKey]: newValue }));
onDisplayOptionsChange({ ...displayOptions, [optionKey]: newValue });
};
@@ -32,16 +33,16 @@ const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChan
setPageNum(parseInt(pageInput)); // input type is 'text', so `page` comes in as a string, not number.
};
// scroll to a page, used in the Prev/Next Page buttons.
const scrollToPage = (pageNumber)=>{
if(typeof pageNumber !== 'number') return;
pageNumber = _.clamp(pageNumber, 1, totalPages);
const iframe = document.getElementById('BrewRenderer');
const brewRenderer = iframe?.contentWindow?.document.querySelector('.brewRenderer');
const page = brewRenderer?.querySelector(`#p${pageNumber}`);
page?.scrollIntoView({ block: 'start' });
setPageNum(pageNumber);
};
const calculateChange = (mode)=>{
const iframe = document.getElementById('BrewRenderer');
const iframeWidth = iframe.getBoundingClientRect().width;
@@ -57,8 +58,12 @@ const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChan
desiredZoom = (iframeWidth / widestPage) * 100;
} else if(mode == 'fit'){
let minDimRatio;
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
const minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
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
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
desiredZoom = minDimRatio * 100;
}
@@ -185,8 +190,8 @@ const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChan
className='previousPage tool'
type='button'
title='Previous Page(s)'
onClick={()=>scrollToPage(pageNum - 1)}
disabled={pageNum <= 1}
onClick={()=>scrollToPage(_.min(visiblePages) - visiblePages.length)}
disabled={visiblePages.includes(1)}
>
<i className='fas fa-arrow-left'></i>
</button>
@@ -205,6 +210,7 @@ const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChan
onChange={(e)=>handlePageInput(e.target.value)}
onBlur={()=>scrollToPage(pageNum)}
onKeyDown={(e)=>e.key == 'Enter' && scrollToPage(pageNum)}
style={{ width: `${pageNum.length}ch` }}
/>
<span id='page-count' title='Total Page Count'>/ {totalPages}</span>
</div>
@@ -214,8 +220,8 @@ const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChan
className='tool'
type='button'
title='Next Page(s)'
onClick={()=>scrollToPage(pageNum + 1)}
disabled={pageNum >= totalPages}
onClick={()=>scrollToPage(_.max(visiblePages) + 1)}
disabled={visiblePages.includes(totalPages)}
>
<i className='fas fa-arrow-right'></i>
</button>

View File

@@ -104,9 +104,9 @@
height : 1.5em;
padding : 2px 5px;
font-family : 'Open Sans', sans-serif;
color : #000000;
background : #EEEEEE;
border : 1px solid gray;
color : inherit;
background : #3B3B3B;
border : none;
&:focus { outline : 1px solid #D3D3D3; }
// `.range-input` if generic to all range inputs, or `#zoom-slider` if only for zoom slider
@@ -115,10 +115,10 @@
color : #D3D3D3;
accent-color : #D3D3D3;
&::-webkit-slider-thumb, &::-moz-slider-thumb {
&::-webkit-slider-thumb, &::-moz-range-thumb {
width : 5px;
height : 5px;
cursor : pointer;
cursor : ew-resize;
outline : none;
}
@@ -141,7 +141,7 @@
// `.text-input` if generic to all range inputs, or `#page-input` if only for current page input
&#page-input {
width : 4ch;
min-width : 5ch;
margin-right : 1ch;
text-align : center;
}

View File

@@ -207,19 +207,11 @@ const Snippetbar = createClass({
renderEditorButtons : function(){
if(!this.props.showEditButtons) return;
const foldButtons = <>
<div className={`editorTool foldAll ${this.props.view !== 'meta' && this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.view !== 'meta' && this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
</>;
return <div className='editors'>
<div className='historyTools'>
return (
<div className='editors'>
{this.props.view !== 'meta' && <><div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
@@ -235,14 +227,21 @@ const Snippetbar = createClass({
</div>
</div>
<div className='codeTools'>
{foldButtons}
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</div>
</div></>}
<div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })}
@@ -259,7 +258,8 @@ const Snippetbar = createClass({
</div>
</div>
</div>;
</div>
)
},
render : function(){

View File

@@ -22,7 +22,7 @@
justify-content : flex-end;
min-width : 225px;
&:only-child { margin-left : auto; }
&:only-child { margin-left : auto;min-width:unset;}
>div {
display : flex;
@@ -38,6 +38,11 @@
line-height : @menuHeight;
text-align : center;
cursor : pointer;
&.editorTool:not(.active) {
cursor:not-allowed;
}
&:hover,&.selected { background-color : #999999; }
&.text {
.tooltipLeft('Brew Editor');

View File

@@ -1,8 +1,12 @@
//╔===--------------- Polyfills --------------===╗//
import 'core-js/es/string/to-well-formed.js';
//╚===--------------- ---------------===╝//
require('./homebrew.less');
const React = require('react');
const createClass = require('create-react-class');
const { StaticRouter:Router } = require('react-router-dom/server');
const { Route, Routes, useParams, useSearchParams } = require('react-router-dom');
const { StaticRouter:Router } = require('react-router');
const { Route, Routes, useParams, useSearchParams } = require('react-router');
const HomePage = require('./pages/homePage/homePage.jsx');
const EditPage = require('./pages/editPage/editPage.jsx');

View File

@@ -16,6 +16,7 @@ const PrintNavItem = require('../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
@@ -380,7 +381,7 @@ const EditPage = createClass({
**[Homebrewery Link](${global.config.publicUrl}/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`;
},
renderNavbar : function(){
@@ -417,6 +418,7 @@ const EditPage = createClass({
</Nav.item>
</Nav.dropdown>
<PrintNavItem />
<VaultNavItem />
<RecentNavItem brew={this.state.brew} storageKey='edit' />
<Account />
</Nav.section>

View File

@@ -18,7 +18,18 @@ const errorIndex = (props)=>{
'01' : dedent`
## An error occurred while retrieving this brew from Google Drive!
Google reported an error while attempting to retrieve a brew from this link.`,
Google is able to see the brew at this link, but reported an error while attempting to retrieve it.
### Refreshing your Google Credentials
This issue is likely caused by an issue with your Google credentials; if you are the owner of this file, the following steps may resolve the issue:
- Go to https://www.naturalcrit.com/login and click logout if present (in small text at the bottom of the page).
- Click "Sign In with Google", which will refresh your Google credentials.
- After completing the sign in process, return to Homebrewery and refresh/reload the page so that it can pick up the updated credentials.
- If this was the source of the issue, it should now be resolved.
If following these steps does not resolve the issue, please let us know!`,
// Google Drive - 404 : brew deleted or access denied
'02' : dedent`
@@ -50,7 +61,7 @@ const errorIndex = (props)=>{
- **The Google Account may be closed.** Google may have removed the account
due to inactivity or violating a Google policy. Make sure the owner can
still access Google Drive normally and upload/download files to it.
:
If the file isn't found, Google Drive usually puts your file in your Trash folder for
30 days. Assuming the trash hasn't been emptied yet, it might be worth checking.
You can also find the Activity tab on the right side of the Google Drive page, which
@@ -172,8 +183,8 @@ const errorIndex = (props)=>{
**Brew Title:** ${props.brew.brewTitle}`,
// ####### Admin page error #######
'52': dedent`
// ####### Admin page error #######
'52' : dedent`
## Access Denied
You need to provide correct administrator credentials to access this page.`,

View File

@@ -8,6 +8,7 @@ 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');
@@ -110,6 +111,7 @@ const SharePage = createClass({
</Nav.item>
</Nav.dropdown>
</>}
<VaultNavItem/>
<RecentNavItem brew={this.props.brew} storageKey='view' />
<Account />
</Nav.section>

View File

@@ -92,49 +92,11 @@
&:invalid { background : rgb(255, 188, 181); }
&[type='checkbox'] {
position : relative;
display : inline-block;
width : 50px;
height : 30px;
font-family : 'WalterTurncoat';
font-size : 20px;
font-weight : 800;
color : white;
letter-spacing : 2px;
appearance : none;
background : red;
isolation : isolate;
border-radius : 5px;
&::before,&::after {
position : absolute;
inset : 0;
z-index : 5;
padding-top : 2px;
text-align : center;
}
&::before {
display : block;
content : 'No';
}
&::after {
display : none;
content : 'Yes';
}
&:checked {
background : green;
&::before { display : none; }
&::after { display : block; }
}
}
}
#searchButton {
.colorButton(@green);
position : absolute;
right : 20px;
bottom : 0;
@@ -152,7 +114,6 @@
flex-direction : column;
height : 100%;
overflow-y : auto;
font-family : 'BookInsanityRemake';
font-size : 0.34cm;
h3 {
@@ -356,6 +317,7 @@
}
button {
.colorButton(@green);
width : max-content;
&.previousPage { grid-area : previousPage; }

19
faq.md
View File

@@ -69,7 +69,6 @@ pre {
You can check the site status here: [Everyone or Just Me](https://downforeveryoneorjustme.com/homebrewery.naturalcrit.com)
### Why am I getting an error when trying to save, and my account is linked to Google?
A sign-in with Google only lasts a year until the authentication expires. You must go [here](https://www.naturalcrit.com/login), click the *Log-out* button, and then sign back in using your Google account.
@@ -82,12 +81,17 @@ If you have linked your account with a Google account, you would change your pas
### Is there a way to restore a previous version of my brew?
Currently, there is no way to do this through the site yourself. This would take too much of a toll on the amount of storage the homebrewery requires. However, we do have daily backups of our database that we keep for 8 days, and you can contact the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username, the name of the lost brew, and the last known time it was working properly. We can manually look through our backups and restore it if it exists.
In your brew, there is an icon, :fas_clock_rotate_left:, that button opens up a menu with versions of your brew, stored in order from newer to older, up to a week old. Because of the amount of duplicates this function creates, this information is stored in **your browser**, so if you were to uninstall it or clear your cookies and site data, or change computers, the info will not be there.
Also, we do have daily backups of our database that we keep for 8 days, and you can contact the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username, the name of the lost brew, and the last known time it was working properly. We can manually look through our backups and restore it if it exists.
### I worked on a brew for X hours, and suddenly all the text disappeared!
This usually happens if you accidentally drag-select all of your text and then start typing which overwrites the selection. Do not panic, and do not refresh the page or reload your brew quite yet as it is probably auto-saved in this state already. Simply press CTRL+Z as many times as needed to undo your last few changes and you will be back to where you were, then make sure to save your brew in the "good" state.
You can also load a history version old enough to have all the text, using the :fas_clock_rotate_left: history versions button.
\column
### Why is only Chrome supported?
@@ -112,10 +116,7 @@ Once you have an image you would like to use, it is recommended to host it somew
\page
### A particular font does not work for my language, what do I do?
The fonts used were originally created for use with the English language, though revisions since then have added more support for other languages. They are still not complete sets and may be missing a glyph/character you need. Unfortunately, the volunteer group as it stands at the time of this writing does not have a font guru, so it would be difficult to add more glyphs (especially complicated glyphs). Let us know which glyph is missing on the subreddit, but you may need to search [Google Fonts](https://fonts.google.com) for an alternative font if you need something fast.
### Whenever I click on the "Get PDF" button, instead of getting a download, it opens Print Preview in another tab.
Yes, this is by design. In the print preview, select "Save as PDF" as the Destination, and then click "Save". There will be a normal download dialog where you can save your brew as a PDF.
The fonts used were originally created for use with the English language, though revisions since then have added more support for other languages. They are still not complete sets and may be missing a glyph/character you need. Unfortunately, the volunteer group as it stands at the time of this writing does not have a font guru, so it would be difficult to add more glyphs (especially complicated glyphs). Let us know which glyph is missing on the subreddit, but you may need to search [Google Fonts](https://fonts.google.com) for an alternative font if you need something fast.
### I have white borders on the bottom/sides of the print preview.
@@ -126,4 +127,8 @@ The Homebrewery defaults to creating US Letter page sizes. If you are printing
### Typing `#### Adhesion` in the text editor doesn't show the header at all in the completed page?
Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.
Your ad-blocking software is mistakenly assuming your text to be an ad. We recommend whitelisting homebrewery.naturalcrit.com in your ad-blocking software, as we have no ads.
### My username appears as _hidden_ when checking my brews in the Vault, why is that?
Your username is most likely your e-mail adress, and our code is picking that up and protecting your identity. This will remain as is, but you can ask for a name change by contacting the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username, and your desired new name. You will also be asked to provide details about some of your unpublished brews, to verify your identity. No information will be leaked or shared.

2173
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,7 @@
"test:api-unit:themes": "jest \"server/.*.spec.js\" -t \"theme bundle\" --verbose",
"test:api-unit:css": "jest \"server/.*.spec.js\" -t \"Get CSS\" --verbose",
"test:api-unit:notifications": "jest \"server/.*.spec.js\" -t \"Notifications\" --verbose",
"test:content-negotiation": "jest \"server/middleware/.*.spec.js\" --verbose",
"test:coverage": "jest --coverage --silent --runInBand",
"test:dev": "jest --verbose --watch",
"test:basic": "jest tests/markdown/basic.test.js --verbose",
@@ -37,6 +38,7 @@
"test:mustache-syntax:injection": "jest \".*(mustache-syntax).*\" -t '^Injection:.*' --verbose --noStackTrace",
"test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace",
"test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace",
"test:non-breaking-spaces": "jest tests/markdown/non-breaking-spaces.test.js --verbose --noStackTrace",
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
"test:route": "jest tests/routes/static-pages.test.js --verbose",
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
@@ -85,17 +87,19 @@
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.25.9",
"@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",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"dompurify": "^3.2.1",
"dompurify": "^3.2.3",
"expr-eval": "^2.0.2",
"express": "^4.21.1",
"express": "^4.21.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
"fs-extra": "11.2.0",
@@ -111,13 +115,13 @@
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.8.2",
"nanoid": "5.0.8",
"mongoose": "^8.9.2",
"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-dom": "6.28.0",
"react-router": "^7.1.1",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -125,15 +129,15 @@
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"babel-plugin-transform-import-meta": "^2.2.1",
"eslint": "^9.15.0",
"eslint-plugin-jest": "^28.9.0",
"eslint-plugin-react": "^7.37.2",
"globals": "^15.12.0",
"eslint": "^9.17.0",
"eslint-plugin-jest": "^28.10.0",
"eslint-plugin-react": "^7.37.3",
"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.10.0",
"stylelint": "^16.12.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0"

View File

@@ -7,11 +7,12 @@ const { pack, watchFile, livereload } = vitreum;
import lessTransform from 'vitreum/transforms/less.js';
import assetTransform from 'vitreum/transforms/asset.js';
import babel from '@babel/core';
import babelConfig from '../babel.config.json' with { type : 'json' };
import less from 'less';
const isDev = !!process.argv.find((arg) => arg === '--dev');
const babelify = async (code)=>(await babel.transformAsync(code, { presets: [['@babel/preset-env', { 'exclude': ['proposal-dynamic-import'] }], '@babel/preset-react'], plugins: ['@babel/plugin-transform-runtime'] })).code;
const babelify = async (code)=>(await babel.transformAsync(code, babelConfig)).code;
const transforms = {
'.js' : (code, filename, opts)=>babelify(code),

View File

@@ -1,5 +1,5 @@
import {model as HomebrewModel } from './homebrew.model.js';
import {model as NotificationModel } from './notifications.model.js';
import { model as HomebrewModel } from './homebrew.model.js';
import { model as NotificationModel } from './notifications.model.js';
import express from 'express';
import Moment from 'moment';
import zlib from 'zlib';
@@ -108,6 +108,9 @@ router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin',
req.body = brew;
// Remove Account from request to prevent Admin user from being added to brew as an Author
req.account = undefined;
return await HomebrewAPI.updateBrew(req, res);
});

View File

@@ -2,7 +2,7 @@
// Set working directory to project root
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import packageJSON from './../package.json' with { type: "json" };
import packageJSON from './../package.json' with { type: 'json' };
const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(`${__dirname}/..`);
@@ -26,7 +26,7 @@ import serveCompressedStaticAssets from './static-assets.mv.js';
import sanitizeFilename from 'sanitize-filename';
import asyncHandler from 'express-async-handler';
import templateFn from '../client/template.js';
import {model as HomebrewModel } from './homebrew.model.js';
import { model as HomebrewModel } from './homebrew.model.js';
import { DEFAULT_BREW } from './brewDefaults.js';
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
@@ -47,7 +47,7 @@ const sanitizeBrew = (brew, accessType)=>{
return brew;
};
app.set('trust proxy', 1 /* number of proxies between user and server */)
app.set('trust proxy', 1 /* number of proxies between user and server */);
app.use('/', serveCompressedStaticAssets(`build`));
app.use(contentNegotiation);
@@ -55,6 +55,40 @@ app.use(bodyParser.json({ limit: '25mb' }));
app.use(cookieParser());
app.use(forceSSL);
import cors from 'cors';
const nodeEnv = config.get('node_env');
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
const corsOptions = {
origin : (origin, callback)=>{
const allowedOrigins = [
'https://homebrewery.naturalcrit.com',
'https://www.naturalcrit.com',
'https://naturalcrit-stage.herokuapp.com',
'https://homebrewery-stage.herokuapp.com',
];
if(isLocalEnvironment) {
allowedOrigins.push('http://localhost:8000', 'http://localhost:8010');
}
const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app
if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin)) {
callback(null, true);
} else {
console.log(origin, 'not allowed');
callback(new Error('Not allowed by CORS, if you think this is an error, please contact us'));
}
},
methods : ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
credentials : true,
};
app.use(cors(corsOptions));
//Account Middleware
app.use((req, res, next)=>{
if(req.cookies && req.cookies.nc_session){
@@ -62,7 +96,9 @@ app.use((req, res, next)=>{
req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
//console.log("Just loaded up JWT from cookie:");
//console.log(req.account);
} catch (e){}
} catch (e){
console.log(e);
}
}
req.config = {
@@ -273,7 +309,7 @@ app.get('/user/:username', async (req, res, next)=>{
console.log(err);
});
brews.forEach(brew => brew.stubbed = true); //All brews from MongoDB are "stubbed"
brews.forEach((brew)=>brew.stubbed = true); //All brews from MongoDB are "stubbed"
if(ownAccount && req?.account?.googleId){
const auth = await GoogleActions.authCheck(req.account, res);
@@ -312,6 +348,34 @@ app.get('/user/:username', async (req, res, next)=>{
return next();
});
//Change author name on brews
app.put('/api/user/rename', async (req, res)=>{
const { username, newUsername } = req.body;
const ownAccount = req.account && (req.account.username == newUsername);
if(!username || !newUsername)
return res.status(400).json({ error: 'Username and newUsername are required.' });
if(!ownAccount)
return res.status(403).json({ error: 'Must be logged in to change your username' });
try {
const brews = await HomebrewModel.getByUser(username, true, ['authors']);
const renamePromises = brews.map(async (brew)=>{
const updatedAuthors = brew.authors.map((author)=>author === username ? newUsername : author
);
return HomebrewModel.updateOne(
{ _id: brew._id },
{ $set: { authors: updatedAuthors } }
);
});
await Promise.all(renamePromises);
return res.json({ success: true, message: `Brews for ${username} renamed to ${newUsername}.` });
} catch (error) {
console.error('Error renaming brews:', error);
return res.status(500).json({ error: 'Failed to rename brews.' });
}
});
//Edit Page
app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{
req.brew = req.brew.toObject ? req.brew.toObject() : req.brew;
@@ -399,7 +463,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r
app.get('/account', asyncHandler(async (req, res, next)=>{
const data = {};
data.title = 'Account Information Page';
if(!req.account) {
res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"');
const error = new Error('No valid account');
@@ -413,7 +477,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
let googleCount = [];
if(req.account) {
if(req.account.googleId) {
auth = await GoogleActions.authCheck(req.account, res, false)
auth = await GoogleActions.authCheck(req.account, res, false);
googleCount = await GoogleActions.listGoogleBrews(auth)
.catch((err)=>{
@@ -448,8 +512,6 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
return next();
}));
const nodeEnv = config.get('node_env');
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
// Local only
if(isLocalEnvironment){
// Login
@@ -477,8 +539,8 @@ app.get('/vault', asyncHandler(async(req, res, next)=>{
//Send rendered page
app.use(asyncHandler(async (req, res, next)=>{
if (!req.route) return res.redirect('/'); // Catch-all for invalid routes
if(!req.route) return res.redirect('/'); // Catch-all for invalid routes
const page = await renderPage(req, res);
if(!page) return;
res.send(page);

View File

@@ -241,8 +241,8 @@ const GoogleActions = {
return obj.data.id;
},
getGoogleBrew : async (id, accessId, accessType)=>{
const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
getGoogleBrew : async (auth = defaultAuth, id, accessId, accessType)=>{
const drive = googleDrive.drive({ version: 'v3', auth: auth });
const obj = await drive.files.get({
fileId : id,

View File

@@ -87,76 +87,68 @@ const api = {
// Create middleware with the accessType passed in as part of the scope
return async (req, res, next)=>{
// Get relevant IDs for the brew
const { id, googleId } = api.getId(req);
let { id, googleId } = api.getId(req);
const accessMap = {
edit : { editId: id },
share : { shareId: id },
admin : {
$or : [
{ editId: id },
{ shareId: id },
] }
admin : { $or : [{ editId: id }, { shareId: id }] }
};
// Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine.
let stub = await HomebrewModel.get(accessMap[accessType])
.catch((err)=>{
if(googleId) {
if(googleId)
console.warn(`Unable to find document stub for ${accessType}Id ${id}`);
} else {
else
console.warn(err);
}
});
stub = stub?.toObject();
googleId ??= stub?.googleId;
if(stub?.lock?.locked && accessType != 'edit') {
throw { HBErrorCode: '51', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title };
const isOwner = (accessType == 'edit' && (!stub || stub?.authors?.length === 0)) || stub?.authors?.[0] === req.account?.username;
const isAuthor = stub?.authors?.includes(req.account?.username);
const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
if(accessType === 'edit' && !(isOwner || isAuthor || isInvited)) {
const accessError = { name: 'Access Error', status: 401, authors: stub?.authors, brewTitle: stub?.title, shareId: stub?.shareId };
if(req.account)
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03' };
else
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04' };
}
// If there is a google id, try to find the google brew
if(!stubOnly && (googleId || stub?.googleId)) {
let googleError;
const googleBrew = await GoogleActions.getGoogleBrew(googleId || stub?.googleId, id, accessType)
.catch((err)=>{
googleError = err;
if(stub?.lock?.locked && accessType != 'edit') {
throw { HBErrorCode: '51', code: stub?.lock.code, message: stub?.lock.shareMessage, brewId: stub?.shareId, brewTitle: stub?.title };
}
// If there's a google id, get it if requesting the full brew or if no stub found yet
if(googleId && (!stubOnly || !stub)) {
const oAuth2Client = isOwner ? GoogleActions.authCheck(req.account, res) : undefined;
const googleBrew = await GoogleActions.getGoogleBrew(oAuth2Client, googleId, id, accessType)
.catch((googleError)=>{
const reason = googleError.errors?.[0].reason;
if(reason == 'notFound')
throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
else
throw { ...googleError, HBErrorCode: '01' };
});
// Throw any error caught while attempting to retrieve Google brew.
if(googleError) {
const reason = googleError.errors?.[0].reason;
if(reason == 'notFound') {
throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
} else {
throw { ...googleError, HBErrorCode: '01' };
}
}
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
stub = stub ? _.assign({ ...api.excludeStubProps(stub), stubbed: true }, api.excludeGoogleProps(googleBrew)) : googleBrew;
}
const authorsExist = stub?.authors?.length > 0;
const isAuthor = stub?.authors?.includes(req.account?.username);
const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
const accessError = { name: 'Access Error', status: 401 };
if(req.account){
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
}
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
}
// If after all of that we still don't have a brew, throw an exception
if(!stub && !stubOnly) {
if(!stub)
throw { name: 'BrewLoad Error', message: 'Brew not found', status: 404, HBErrorCode: '05', accessType: accessType, brewId: id };
}
// Clean up brew: fill in missing fields with defaults / fix old invalid values
if(stub) {
stub.tags = stub.tags || undefined; // Clear empty strings
stub.renderer = stub.renderer || undefined; // Clear empty strings
stub = _.defaults(stub, DEFAULT_BREW_LOAD); // Fill in blank fields
}
stub.tags = stub.tags || undefined; // Clear empty strings
stub.renderer = stub.renderer || undefined; // Clear empty strings
stub = _.defaults(stub, DEFAULT_BREW_LOAD); // Fill in blank fields
req.brew = stub ?? {};
req.brew = stub;
next();
};
},
@@ -475,12 +467,11 @@ const api = {
}
};
router.use('/api', checkClientVersion);
router.post('/api', asyncHandler(api.newBrew));
router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.delete('/api/:id', asyncHandler(api.deleteBrew));
router.get('/api/remove/:id', asyncHandler(api.deleteBrew));
router.post('/api', checkClientVersion, asyncHandler(api.newBrew));
router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew));
router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew));
router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));
export default api;

View File

@@ -1,5 +1,7 @@
/* eslint-disable max-lines */
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
describe('Tests for api', ()=>{
let api;
let google;
@@ -296,7 +298,7 @@ describe('Tests for api', ()=>{
expect(next).toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share');
expect(google.getGoogleBrew).toHaveBeenCalledWith(undefined, '2', '1', 'share');
});
it('access is denied to a locked brew', async()=>{
@@ -968,4 +970,57 @@ brew`);
expect(res.send).toHaveBeenCalledWith('');
});
});
describe('Split Text, Style, and Metadata', ()=>{
it('basic splitting', async ()=>{
const testBrew = {
text : '```metadata\n' +
'title: title\n' +
'description: description\n' +
'tags: [ \'tag a\' , \'tag b\' ]\n' +
'systems: [ test system ]\n' +
'renderer: legacy\n' +
'theme: 5ePHB\n' +
'lang: en\n' +
'\n' +
'```\n' +
'\n' +
'```css\n' +
'style\n' +
'style\n' +
'style\n' +
'```\n' +
'\n' +
'text\n'
};
splitTextStyleAndMetadata(testBrew);
// Metadata
expect(testBrew.title).toEqual('title');
expect(testBrew.description).toEqual('description');
expect(testBrew.tags).toEqual(['tag a', 'tag b']);
expect(testBrew.systems).toEqual(['test system']);
expect(testBrew.renderer).toEqual('legacy');
expect(testBrew.theme).toEqual('5ePHB');
expect(testBrew.lang).toEqual('en');
// Style
expect(testBrew.style).toEqual('style\nstyle\nstyle');
// Text
expect(testBrew.text).toEqual('text\n');
});
it('convert tags string to array', async ()=>{
const testBrew = {
text : '```metadata\n' +
'tags: tag a\n' +
'```\n\n'
};
splitTextStyleAndMetadata(testBrew);
// Metadata
expect(testBrew.tags).toEqual(['tag a']);
});
});
});

View File

@@ -1,10 +1,10 @@
import packageJSON from '../../package.json' with { type: "json" };
const version = packageJSON.version;
import packageJSON from '../../package.json' with { type: 'json' };
export default (req, res, next)=>{
const userVersion = req.get('Homebrewery-Version');
const version = packageJSON.version;
if(userVersion != version) {
if(userVersion !== version) {
return res.status(412).send({
message : `Client version ${userVersion} is out of date. Please save your changes elsewhere and refresh to pick up client version ${version}.`
});
@@ -12,3 +12,4 @@ export default (req, res, next)=>{
next();
};

View File

@@ -6,7 +6,7 @@ export default (req, res, next)=>{
const isImageRequest = req.get('Accept')?.split(',')
?.filter((h)=>!h.includes('q='))
?.every((h)=>/image\/.*/.test(h));
if(isImageRequest && !isLocalEnvironment && !req.url?.startsWith('/staticImages')) {
if(isImageRequest && !(isLocalEnvironment && req.url?.startsWith('/staticImages'))) {
return res.status(406).send({
message : 'Request for image at this URL is not supported'
});

View File

@@ -0,0 +1,41 @@
import contentNegotiationMiddleware from './content-negotiation.js';
describe('content-negotiation-middleware', ()=>{
let request;
let response;
let next;
beforeEach(()=>{
request = {
get : function(key) {
return this[key];
}
};
response = {
status : jest.fn(()=>response),
send : jest.fn(()=>{})
};
next = jest.fn();
});
it('should return 406 on image request', ()=>{
contentNegotiationMiddleware({
Accept : 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
...request
}, response);
expect(response.status).toHaveBeenLastCalledWith(406);
expect(response.send).toHaveBeenCalledWith({
message : 'Request for image at this URL is not supported'
});
});
it('should call next on non-image request', ()=>{
contentNegotiationMiddleware({
Accept : 'text,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
...request
}, response, next);
expect(next).toHaveBeenCalled();
});
});

View File

@@ -21,6 +21,9 @@ const splitTextStyleAndMetadata = (brew)=>{
brew.snippets = brew.text.slice(11, index - 1);
brew.text = brew.text.slice(index + 5);
}
// Handle old brews that still have empty strings in the tags metadata
if(typeof brew.tags === 'string') brew.tags = brew.tags ? [brew.tags] : [];
};
const printCurrentBrew = ()=>{

View File

@@ -11,49 +11,54 @@
@import (less) './themes/fonts/iconFonts/fontAwesome.less';
@keyframes sourceMoveAnimation {
50% {background-color: red; color: white;}
100% {background-color: unset; color: unset;}
50% { color : white;background-color : red;}
100% { color : unset;background-color : unset;}
}
.codeEditor{
@media screen and (pointer : coarse) {
font-size : 16px;
}
.CodeMirror-foldmarker {
font-family: inherit;
text-shadow: none;
font-weight: 600;
color: grey;
}
.codeEditor {
@media screen and (pointer : coarse) {
font-size : 16px;
}
.CodeMirror-foldmarker {
font-family : inherit;
font-weight : 600;
color : grey;
text-shadow : none;
}
.sourceMoveFlash .CodeMirror-line{
animation-name: sourceMoveAnimation;
animation-duration: 0.4s;
}
.CodeMirror-foldgutter {
cursor : pointer;
border-left : 1px solid #EEEEEE;
transition : background 0.1s;
&:hover { background : #DDDDDD; }
}
.CodeMirror-vscrollbar {
&::-webkit-scrollbar {
width: 20px;
}
&::-webkit-scrollbar-thumb {
width: 20px;
background: linear-gradient(90deg, #858585 15px, #808080 15px);
}
}
.sourceMoveFlash .CodeMirror-line {
animation-name : sourceMoveAnimation;
animation-duration : 0.4s;
}
.CodeMirror-vscrollbar {
&::-webkit-scrollbar { width : 20px; }
&::-webkit-scrollbar-thumb {
width : 20px;
background : linear-gradient(90deg, #858585 15px, #808080 15px);
}
}
//.cm-tab {
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
//}
//.cm-tab {
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
//}
//.cm-trailingspace {
// .cm-space {
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
// }
//}
//.cm-trailingspace {
// .cm-space {
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
// }
//}
}
.emojiPreview {
font-size: 1.5em;
line-height: 1.2em;
font-size : 1.5em;
line-height : 1.2em;
}

View File

@@ -391,10 +391,31 @@ const forcedParagraphBreaks = {
}
};
const nonbreakingSpaces = {
name : 'nonbreakingSpaces',
level : 'inline',
start(src) { return src.match(/:>+/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /:(>+)/ym;
const match = regex.exec(src);
if(match?.length) {
return {
type : 'nonbreakingSpaces', // Should match "name" above
raw : match[0], // Text to consume from the source
length : match[1].length,
text : ''
};
}
},
renderer(token) {
return `&nbsp;`.repeat(token.length).concat('');
}
};
const definitionListsSingleLine = {
name : 'definitionListsSingleLine',
level : 'block',
start(src) { return src.match(/\n[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
start(src) { return src.match(/\n[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
let match;
@@ -748,11 +769,12 @@ const tableTerminators = [
];
Marked.use(MarkedVariables());
Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, superSubScripts,
mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
nonbreakingSpaces, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }),
MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
function cleanUrl(href) {
try {

View File

@@ -43,5 +43,6 @@ html,body, #reactRoot{
}
&:disabled{
background-color : @silver !important;
cursor:not-allowed;
}
}

View File

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

View File

@@ -42,23 +42,25 @@ body {
}
.phb, .page{
.useColumns();
counter-increment : phb-page-numbers;
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
height : 279.4mm;
width : 215.9mm;
padding : 1.0cm 1.7cm;
padding-bottom : 1.5cm;
background-color : @background;
background-image : @backgroundImage;
font-family : BookSanity;
font-size : 0.317cm;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size;
counter-increment : phb-page-numbers;
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
height : 279.4mm;
width : 215.9mm;
padding : 1.0cm 1.7cm;
padding-bottom : 1.5cm;
background-color : @background;
background-image : @backgroundImage;
font-family : BookSanity;
font-size : 0.317cm;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : strict;
content-visibility : auto;
contain-intrinsic-size : auto none;
}
.phb{

View File

@@ -45,19 +45,21 @@ body { counter-reset : page-numbers 0; }
}
.page {
.useColumns();
position : relative;
z-index : 15;
box-sizing : border-box;
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden;
background-color : var(--HB_Color_Background);
text-rendering : optimizeLegibility;
contain : size;
position : relative;
z-index : 15;
box-sizing : border-box;
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden;
background-color : var(--HB_Color_Background);
text-rendering : optimizeLegibility;
contain : strict;
content-visibility : auto;
contain-intrinsic-size : auto none;
}
//*****************************
// * BASE
//*****************************
// * BASE
// *****************************/
.page {
p {