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

Compare commits

...

368 Commits

Author SHA1 Message Date
Trevor Buckner
add088c2a9 Up version to 3.16.1 2024-11-27 16:26:26 -05:00
Trevor Buckner
6d8415bfeb Merge pull request #3911 from dbolack-ab/autoESM
Fix regression in emoji auto-complete due to ESM update
2024-11-25 12:11:38 -05:00
David Bolack
decb334808 Fix regression in emoji auto-complete due to ESM update 2024-11-25 10:41:11 -06:00
Trevor Buckner
66f71972eb Merge pull request #3860 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.0.8
Bump nanoid from 3.3.4 to 5.0.8
2024-11-21 13:36:36 -05:00
Trevor Buckner
ebe8e1067c Move babel config to separate file
Jest struggles to read all babel configurations if directly inside package.json.

This now allows us to install nanoid 5 and pass all tests with it.
2024-11-21 13:33:32 -05:00
dependabot[bot]
9807cf762b Bump nanoid from 3.3.4 to 5.0.8
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.4 to 5.0.8.
- [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/3.3.4...5.0.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-21 16:51:39 +00:00
Trevor Buckner
b58563cb42 Merge pull request #3897 from naturalcrit/dependabot/npm_and_yarn/eslint-9.15.0
Bump eslint from 9.14.0 to 9.15.0
2024-11-21 11:37:22 -05:00
dependabot[bot]
7c3f3b87af Bump eslint from 9.14.0 to 9.15.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.14.0 to 9.15.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.14.0...v9.15.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-11-21 16:32:02 +00:00
Trevor Buckner
e7daad592c Merge pull request #3859 from naturalcrit/dependabot/npm_and_yarn/elliptic-6.6.0
Bump elliptic from 6.5.7 to 6.6.0
2024-11-21 11:30:40 -05:00
Trevor Buckner
992359e239 Merge branch 'master' into dependabot/npm_and_yarn/elliptic-6.6.0 2024-11-21 11:27:47 -05:00
Trevor Buckner
b2546f3458 Merge pull request #3903 from naturalcrit/dependabot/npm_and_yarn/dompurify-3.2.1
Bump dompurify from 3.2.0 to 3.2.1
2024-11-21 11:27:32 -05:00
dependabot[bot]
6f016bf5b6 Bump dompurify from 3.2.0 to 3.2.1
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.0...3.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-21 16:25:09 +00:00
Trevor Buckner
7cd3c69fbd Merge pull request #3898 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.8.2
Bump mongoose from 8.8.1 to 8.8.2
2024-11-21 11:23:50 -05:00
Trevor Buckner
9b1507d4f5 Merge branch 'master' into dependabot/npm_and_yarn/mongoose-8.8.2 2024-11-21 11:21:55 -05:00
Trevor Buckner
2e49bf4fa8 Merge pull request #3902 from naturalcrit/updateToES6Modules
Upgrade server-side code to ESM (import vs require)
2024-11-21 11:21:21 -05:00
Trevor Buckner
e5624434d6 Update buildAdmin.js 2024-11-20 17:04:23 -05:00
Trevor Buckner
1850173f87 Remove unused dependency 2024-11-20 16:22:48 -05:00
Trevor Buckner
fb9148ada5 Site runs and all tests pass 2024-11-20 16:21:35 -05:00
dependabot[bot]
b857a91ab8 Bump mongoose from 8.8.1 to 8.8.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.8.1 to 8.8.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.8.1...8.8.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-11-19 03:21:50 +00:00
Trevor Buckner
b7c49218ae Merge pull request #3735 from Gazook89/Functional-Tag-Editor
TagInput - Functional component for tag-like inputs
2024-11-13 13:58:42 -05:00
Trevor Buckner
f4d4334a75 Merge branch 'master' into Functional-Tag-Editor 2024-11-13 13:52:01 -05:00
Víctor Losada Hernández
38b4c285a3 Merge pull request #3890 from Gazook89/Update-core-and-reset
Update Core.less and reset.less
2024-11-12 22:09:16 +01:00
Víctor Losada Hernández
cf46a975aa Merge branch 'master' into Update-core-and-reset 2024-11-12 22:05:27 +01:00
Trevor Buckner
9f693547f7 Merge pull request #3889 from naturalcrit/dependabot/npm_and_yarn/express-static-gzip-2.2.0
Bump express-static-gzip from 2.1.8 to 2.2.0
2024-11-12 13:27:28 -05:00
Trevor Buckner
a69dd998f5 Merge branch 'master' into dependabot/npm_and_yarn/express-static-gzip-2.2.0 2024-11-12 13:16:34 -05:00
Trevor Buckner
f141515446 Merge pull request #3888 from naturalcrit/dependabot/npm_and_yarn/marked-emoji-1.4.3
Bump marked-emoji from 1.4.2 to 1.4.3
2024-11-12 13:16:23 -05:00
dependabot[bot]
f749706cb3 Bump marked-emoji from 1.4.2 to 1.4.3
Bumps [marked-emoji](https://github.com/UziTech/marked-emoji) from 1.4.2 to 1.4.3.
- [Release notes](https://github.com/UziTech/marked-emoji/releases)
- [Changelog](https://github.com/UziTech/marked-emoji/blob/main/release.config.cjs)
- [Commits](https://github.com/UziTech/marked-emoji/compare/v1.4.2...v1.4.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 18:15:37 +00:00
dependabot[bot]
b22f3d041c Bump express-static-gzip from 2.1.8 to 2.2.0
Bumps [express-static-gzip](https://github.com/tkoenig89/express-static-gzip) from 2.1.8 to 2.2.0.
- [Release notes](https://github.com/tkoenig89/express-static-gzip/releases)
- [Commits](https://github.com/tkoenig89/express-static-gzip/compare/v2.1.8...v2.2.0)

---
updated-dependencies:
- dependency-name: express-static-gzip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 18:15:31 +00:00
Trevor Buckner
dd8692d82b Merge pull request #3887 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.8.1
Bump mongoose from 8.7.3 to 8.8.1
2024-11-12 13:14:11 -05:00
dependabot[bot]
0d2dfe66bc Bump mongoose from 8.7.3 to 8.8.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.7.3 to 8.8.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.7.3...8.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 05:36:16 +00:00
Trevor Buckner
0437635861 Merge pull request #3892 from naturalcrit/dependabot/npm_and_yarn/dompurify-3.2.0
Bump dompurify from 3.1.7 to 3.2.0
2024-11-12 00:35:00 -05:00
Trevor Buckner
a5f12ca0b4 Merge branch 'master' into dependabot/npm_and_yarn/dompurify-3.2.0 2024-11-12 00:27:07 -05:00
Trevor Buckner
07e0a7c1b5 Merge pull request #3820 from G-Ambatte/addAdminFixScriptTool-#3801
Add script fix tool to Admin page #3801
2024-11-12 00:21:05 -05:00
Trevor Buckner
2e9c7b1d9b Shorten label 2024-11-12 00:20:37 -05:00
Trevor Buckner
0ddc3ae5b9 Merge branch 'master' into addAdminFixScriptTool-#3801 2024-11-12 00:16:07 -05:00
Trevor Buckner
8c6c8f861d Automatically re-check for scripts
Adding a separate `keepText` field for the `updateBrew()` API might be a bandaid for something that should be looked at more deeply as a separate refactor, considering `updateBrew()` is configured to just return the stub and not the whole document.

For now, re-scanning for script tags after updating can be as simple as just re-looking up the brew.
2024-11-12 00:08:56 -05:00
dependabot[bot]
107aa34ee4 Bump dompurify from 3.1.7 to 3.2.0
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.1.7 to 3.2.0.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.1.7...3.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 03:12:02 +00:00
Trevor Buckner
e006826e3e Merge pull request #3891 from dbolack-ab/Issue_3718
Fix Issue 3718 by bounds checking prerender.
2024-11-11 22:04:36 -05:00
David Bolack
4e4463fe4d Fix Issue 3718 by bounds checking prerender. 2024-11-11 11:17:00 -06:00
Gazook89
1a56c393ab Merge branch 'master' into Update-core-and-reset 2024-11-10 21:50:03 -06:00
Gazook89
9bc4b1fb56 Changes to core.less, reset.less, and toolbar
Making some changes to the reset.less so that some default UA button styling is removed.

Then, changing core.less so that the classic "HB" button styling is scoped to a certain class `.colorButton`.  This will make it easier to use the button element in other places.
2024-11-10 21:48:01 -06:00
Gazook89
234d484a74 Merge remote-tracking branch 'upstream/master' 2024-11-10 15:55:54 -06:00
G.Ambatte
dc1d40512b Reinstate length check 2024-11-10 21:45:17 +13:00
G.Ambatte
2dafbf2080 Simplify Admin brew lookup function 2024-11-10 20:19:30 +13:00
G.Ambatte
033b7fa44f Lint fix 2024-11-10 19:35:57 +13:00
G.Ambatte
2c4f705072 Merge branch 'addAdminFixScriptTool-#3801' of https://github.com/G-Ambatte/homebrewery into addAdminFixScriptTool-#3801 2024-11-10 19:34:25 +13:00
G.Ambatte
ee811e94e1 Remove error handling that can never trigger 2024-11-10 19:34:19 +13:00
G.Ambatte
fcae147723 Merge branch 'master' into addAdminFixScriptTool-#3801 2024-11-10 19:34:17 +13:00
G.Ambatte
b3793a3330 Simplify scriptCount logic 2024-11-10 19:30:57 +13:00
G.Ambatte
952b67aed3 Remove checkForScript state 2024-11-10 19:29:28 +13:00
G.Ambatte
27f14b042b Remove comment about irrelevant tag cleaning 2024-11-10 19:24:54 +13:00
G.Ambatte
49d30007d3 Merge branch 'addAdminFixScriptTool-#3801' of https://github.com/G-Ambatte/homebrewery into addAdminFixScriptTool-#3801 2024-11-10 19:23:47 +13:00
G.Ambatte
bd26f02ddb Remove getBrew admin regex search 2024-11-10 19:23:42 +13:00
Trevor Buckner
ccc37fc0d5 Merge pull request #3873 from naturalcrit/dependabot/npm_and_yarn/globals-15.12.0
Bump globals from 15.11.0 to 15.12.0
2024-11-10 01:15:53 -05:00
dependabot[bot]
9973999e86 Bump globals from 15.11.0 to 15.12.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.11.0 to 15.12.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.11.0...v15.12.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-11-10 06:14:56 +00:00
Trevor Buckner
2aec54748a Merge pull request #3871 from naturalcrit/dependabot/npm_and_yarn/eslint-9.14.0
Bump eslint from 9.13.0 to 9.14.0
2024-11-10 01:13:42 -05:00
dependabot[bot]
5585c27cb8 Bump eslint from 9.13.0 to 9.14.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.13.0 to 9.14.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.13.0...v9.14.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-11-10 06:11:39 +00:00
Trevor Buckner
52ae343309 Merge pull request #3881 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-jest-28.9.0
Bump eslint-plugin-jest from 28.8.3 to 28.9.0
2024-11-10 01:10:26 -05:00
dependabot[bot]
f8d60fc4da Bump eslint-plugin-jest from 28.8.3 to 28.9.0
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 28.8.3 to 28.9.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.8.3...v28.9.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-11-10 06:08:20 +00:00
Trevor Buckner
69e827a663 Merge pull request #3882 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.28.0
Bump react-router-dom from 6.27.0 to 6.28.0
2024-11-10 01:06:58 -05:00
Trevor Buckner
d1d73023a2 Merge branch 'master' into dependabot/npm_and_yarn/react-router-dom-6.28.0 2024-11-10 00:55:34 -05:00
Trevor Buckner
f4af19ed81 Merge pull request #3885 from Gazook89/Fix-dialog.jsx
Fix Dialog.jsx to prevent crashes
2024-11-10 00:06:39 -05:00
Gazook89
bc5a9c9039 lint 2024-11-08 21:57:13 -06:00
Gazook89
f7dfedcd44 give dismisskeys a default 2024-11-08 21:57:13 -06:00
Gazook89
b7b1981bde lint 2024-11-08 21:56:45 -06:00
Gazook89
2e8368d08c give dismisskeys a default 2024-11-08 21:56:35 -06:00
G.Ambatte
c6b0b6a0ea Merge branch 'master' into addAdminFixScriptTool-#3801 2024-11-07 23:31:04 +13:00
dependabot[bot]
7c7326b42a Bump react-router-dom from 6.27.0 to 6.28.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.27.0 to 6.28.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.28.0/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.28.0/packages/react-router-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-07 03:42:03 +00:00
Trevor Buckner
d0cf6eebbb Merge pull request #3869 from 5e-Cleric/fix-splitpane-overflowing-page
Fix-splitpane-overflowing-page
2024-11-05 16:43:59 -05:00
Trevor Buckner
0ba838f5ae Merge pull request #3880 from Gazook89/fix-memoization
Fix Memoization
2024-11-05 13:51:48 -05:00
Gazook89
172a3eaadf fix memoization on brewRenderer.jsx 2024-11-05 12:45:41 -06:00
Trevor Buckner
b868ba9406 Merge pull request #3799 from Gazook89/View-Modes
View Modes - Single / Facing Pages / "Flow" arrangements
2024-11-04 16:05:53 -05:00
Gazook89
89a16956b9 Merge branch 'master' into View-Modes 2024-11-04 14:22:54 -06:00
Gazook89
b098d28407 linting 2024-11-04 14:08:06 -06:00
Gazook89
1be1b3b747 small accessibility changes. 2024-11-04 13:55:18 -06:00
Trevor Buckner
f63d2de8f4 Refactor toolbar view options (#2)
* Refactor toolbar view options

* Remove a couple more unused states
2024-11-01 22:19:48 -05:00
Víctor Losada Hernández
626b602a61 last fix 2024-11-01 20:39:45 +01:00
Víctor Losada Hernández
3d7d90104b revert content deletion 2024-11-01 20:37:28 +01:00
G.Ambatte
422fd7bb57 Merge branch 'master' into addAdminFixScriptTool-#3801 2024-10-30 12:01:42 +13:00
Trevor Buckner
c7c8dafb18 Merge pull request #3816 from G-Ambatte/refactorVersionHistory
Refactor version history to use custom store wrapper
2024-10-29 17:10:22 -04:00
G.Ambatte
ea25ae625a Merge branch 'refactorVersionHistory' of https://github.com/G-Ambatte/homebrewery into refactorVersionHistory 2024-10-30 08:21:56 +13:00
G.Ambatte
ebf900ba24 Remove Proxy version 2024-10-30 08:21:51 +13:00
G.Ambatte
0037c91cc5 Merge branch 'master' into refactorVersionHistory 2024-10-30 08:10:47 +13:00
Trevor Buckner
8a8bf883e6 Merge pull request #3548 from G-Ambatte/fixLinks-#3547
Improve HTML sanitization
2024-10-29 14:20:50 -04:00
Trevor Buckner
17539cb80b Merge branch 'master' into fixLinks-#3547 2024-10-29 14:15:02 -04:00
Trevor Buckner
bb057ba057 Merge pull request #3861 from 5e-Cleric/vault-styles-quickfix
Vault styles quickfix
2024-10-29 14:01:57 -04:00
Trevor Buckner
99ad865311 Merge branch 'master' into vault-styles-quickfix 2024-10-29 13:45:30 -04:00
Víctor Losada Hernández
48baaa33e2 linting 2024-10-29 14:22:51 +01:00
Víctor Losada Hernández
6f07d25101 second fix 2024-10-29 14:20:49 +01:00
Víctor Losada Hernández
fec904b4c6 initial commit 2024-10-29 14:16:45 +01:00
G.Ambatte
a29aca32e7 Display createdAt time 2024-10-29 20:42:53 +13:00
G.Ambatte
6fed42198d Simplify initCustomStore 2024-10-29 19:30:22 +13:00
G.Ambatte
446406758f Merge branch 'master' into refactorVersionHistory 2024-10-29 19:12:59 +13:00
G.Ambatte
2d9da23c25 Merge branch 'master' into addAdminFixScriptTool-#3801 2024-10-29 19:08:49 +13:00
G.Ambatte
b24c9daa08 Update HTML test 2024-10-29 18:15:46 +13:00
G.Ambatte
f5bc490380 Remove type='submit' attribute 2024-10-29 18:10:25 +13:00
G.Ambatte
4673303bc2 Remove unused JSDom package 2024-10-29 17:41:16 +13:00
G.Ambatte
e550ab4046 Remove eslint override from HTML tests 2024-10-29 17:40:55 +13:00
G.Ambatte
be4ba06081 Removed unused themes.json import 2024-10-29 17:32:31 +13:00
G.Ambatte
0fda0673b2 Simplify logic & reduce nesting 2024-10-29 17:27:59 +13:00
G.Ambatte
9edf65c252 Shift to element.remove() 2024-10-29 16:50:44 +13:00
G.Ambatte
3f8ec30f89 Fix broken check for document existence 2024-10-29 16:50:33 +13:00
G.Ambatte
f1bebe3895 Move import to be adjacent to existing requires 2024-10-29 16:36:49 +13:00
G.Ambatte
f5954b03f2 Add comment to reference vue-html-secure pacakge 2024-10-29 16:34:50 +13:00
G.Ambatte
a265723c57 Merge branch 'master' into fixLinks-#3547 2024-10-29 16:32:23 +13:00
Trevor Buckner
ca90e1804a Merge branch 'master' into dependabot/npm_and_yarn/elliptic-6.6.0 2024-10-28 23:26:39 -04:00
Trevor Buckner
e7c0cdae3d Merge pull request #3858 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.7.3
Bump mongoose from 8.7.2 to 8.7.3
2024-10-28 23:26:25 -04:00
dependabot[bot]
db75e0dd66 Bump elliptic from 6.5.7 to 6.6.0
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.0.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-29 03:25:59 +00:00
Trevor Buckner
56d024a2e6 Merge branch 'master' into dependabot/npm_and_yarn/mongoose-8.7.3 2024-10-28 23:25:45 -04:00
Trevor Buckner
f6ed348824 Merge pull request #3857 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.26.0
Bump @babel/preset-env from 7.25.9 to 7.26.0
2024-10-28 23:25:34 -04:00
Trevor Buckner
a4406d953a Merge branch 'master' into dependabot/npm_and_yarn/babel/preset-env-7.26.0 2024-10-28 23:25:05 -04:00
Trevor Buckner
1a41252d70 Merge pull request #3856 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.0
Bump @babel/core from 7.25.9 to 7.26.0
2024-10-28 23:24:54 -04:00
Trevor Buckner
97371be26a Merge branch 'master' into dependabot/npm_and_yarn/babel/core-7.26.0 2024-10-28 23:23:08 -04:00
Trevor Buckner
994fbd197a Merge pull request #3855 from 5e-Cleric/refactor-splitpane
Refactor splitpane as a functional component
2024-10-28 15:00:39 -04:00
dependabot[bot]
4995aebb93 Bump mongoose from 8.7.2 to 8.7.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.7.2 to 8.7.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.7.2...8.7.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-10-28 03:24:31 +00:00
dependabot[bot]
782fa3a2a0 Bump @babel/preset-env from 7.25.9 to 7.26.0
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.25.9 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-preset-env)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 03:24:06 +00:00
dependabot[bot]
5f71d2902b Bump @babel/core from 7.25.9 to 7.26.0
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.25.9 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-core)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 03:23:52 +00:00
Víctor Losada Hernández
391d0a0bfe remove flickering in divider 2024-10-27 10:20:49 +01:00
Víctor Losada Hernández
782ee7a4ad Merge branch 'master' of https://github.com/naturalcrit/homebrewery into refactor-splitpane 2024-10-27 10:14:30 +01:00
Víctor Losada Hernández
987063422d use storage instead of state to correctly save position while resizing 2024-10-27 10:13:59 +01:00
Trevor Buckner
26afb67f61 Merge pull request #3823 from 5e-Cleric/erase-unnecessary-divs
Clean up DOM
2024-10-26 23:06:17 -04:00
Trevor Buckner
d2f6bc03db Merge branch 'master' into erase-unnecessary-divs 2024-10-26 23:02:40 -04:00
Trevor Buckner
547bedb5f9 Merge pull request #3851 from 5e-Cleric/userpage-to-func-comp
Refactor UserPage as a functional component
2024-10-26 23:00:18 -04:00
Trevor Buckner
1b4d41fc19 Remove error prop
Userpage is never passed `Error` prop from anywhere. Thus we can rename the error state here from `currentError` to just `error`
2024-10-26 22:59:49 -04:00
Trevor Buckner
6f2252635a Fix crash; props need props.var to work 2024-10-26 22:56:29 -04:00
Víctor Losada Hernández
46eac41021 further formatting 2024-10-26 23:03:25 +02:00
Víctor Losada Hernández
43441f3185 last changes as suggested 2024-10-26 22:54:16 +02:00
Víctor Losada Hernández
f2765650f7 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into userpage-to-func-comp 2024-10-26 19:46:55 +02:00
Víctor Losada Hernández
a017c28b02 allow for null error instead of undefined 2024-10-26 19:45:55 +02:00
Víctor Losada Hernández
ef1e0f1faa destructure props as per usual 2024-10-26 19:45:26 +02:00
Víctor Losada Hernández
5dc961505b Merge branch 'userpage-to-func-comp' of https://github.com/5e-Cleric/homebrewery into userpage-to-func-comp 2024-10-26 19:44:08 +02:00
Víctor Losada Hernández
b129ec1469 linting 2024-10-26 19:43:27 +02:00
Víctor Losada Hernández
adcbd7c4c8 Merge branch 'master' into erase-unnecessary-divs 2024-10-26 18:18:49 +02:00
Víctor Losada Hernández
843aa6d769 linting and final pass 2024-10-26 16:45:33 +02:00
Víctor Losada Hernández
8ab9b842fc Merge branch 'master' of https://github.com/naturalcrit/homebrewery into refactor-splitpane 2024-10-26 16:29:08 +02:00
Víctor Losada Hernández
f8b5a8133e initial commit 2024-10-26 16:15:53 +02:00
G.Ambatte
478a541d62 Remove wrapper from file name 2024-10-26 09:58:09 +13:00
G.Ambatte
eb852b8045 Add IDB Proxy 2024-10-26 09:50:07 +13:00
G.Ambatte
07e7f3e70c Merge branch 'master' into refactorVersionHistory 2024-10-25 20:54:34 +13:00
G.Ambatte
fea8f157a7 Change script clean to use Homebrew API update 2024-10-25 17:45:12 +13:00
G.Ambatte
898be28af3 Fix Homebrew API parameter 2024-10-25 11:40:17 +13:00
G.Ambatte
63f6f6d3c6 Fix new getBrew access type 2024-10-25 11:27:28 +13:00
G.Ambatte
ac2de613c5 Change Admin lookup to use Homebrew.API getBrew instead 2024-10-25 11:19:55 +13:00
G.Ambatte
948f03b5b8 Add admin access type to getBrew 2024-10-25 11:18:36 +13:00
G.Ambatte
28894adeab Add error check for no brew found 2024-10-25 11:17:44 +13:00
Víctor Losada Hernández
9770fea3fd update navitems call as suggested
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2024-10-24 21:13:49 +02:00
Víctor Losada Hernández
422257743e accept navitems suggestion
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2024-10-24 20:39:59 +02:00
G.Ambatte
e3619bb1fc Add global flag to regex 2024-10-25 00:11:02 +13:00
G.Ambatte
db1fdca3ab Automatically check for SCRIPT tags 2024-10-24 20:45:12 +13:00
G.Ambatte
c2f56833f3 Merge branch 'addAdminFixScriptTool-#3801' of https://github.com/G-Ambatte/homebrewery into addAdminFixScriptTool-#3801 2024-10-24 17:21:00 +13:00
G.Ambatte
d3cc5c890b Display number of SCRIPT tags detected in brew 2024-10-24 17:20:55 +13:00
G.Ambatte
8eac8eff4b Merge branch 'master' into addAdminFixScriptTool-#3801 2024-10-24 16:22:29 +13:00
Trevor Buckner
8bede8f0ee Merge pull request #3853 from 5e-Cleric/fix-snippetbar-behaviour
fix snippetbar weirdness
2024-10-23 22:45:39 -04:00
Víctor Losada Hernández
c7894499b1 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-snippetbar-behaviour 2024-10-24 01:25:15 +02:00
Víctor Losada Hernández
55a50ce261 always stretch 2024-10-24 01:23:26 +02:00
Víctor Losada Hernández
5b675918ad real fix for the meta tab 2024-10-24 01:10:47 +02:00
Víctor Losada Hernández
70046e00f8 fix min-width in meta tab 2024-10-24 01:07:52 +02:00
Víctor Losada Hernández
e129eb2f5d Merge branch 'fix-snippetbar-behaviour' of https://github.com/5e-Cleric/homebrewery into fix-snippetbar-behaviour 2024-10-24 01:04:23 +02:00
Víctor Losada Hernández
56c9413ab5 next iteration 2024-10-24 01:04:22 +02:00
Trevor Buckner
671044ca3a Merge pull request #3850 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.25.9
Bump @babel/plugin-transform-runtime from 7.25.7 to 7.25.9
2024-10-23 17:02:34 -04:00
Víctor Losada Hernández
04baabc27d Merge branch 'master' into addAdminFixScriptTool-#3801 2024-10-23 21:25:36 +02:00
dependabot[bot]
4add67086d Bump @babel/plugin-transform-runtime from 7.25.7 to 7.25.9
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.25.7 to 7.25.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.9/packages/babel-plugin-transform-runtime)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 19:24:33 +00:00
Víctor Losada Hernández
66451a3b1e Merge branch 'master' into refactorVersionHistory 2024-10-23 21:24:21 +02:00
Trevor Buckner
785041dcd1 Merge pull request #3848 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.25.9
Bump @babel/preset-env from 7.25.8 to 7.25.9
2024-10-23 15:23:39 -04:00
Trevor Buckner
40bb823efa Merge branch 'master' into dependabot/npm_and_yarn/babel/preset-env-7.25.9 2024-10-23 15:22:51 -04:00
dependabot[bot]
0dd5b262f7 Bump @babel/preset-env from 7.25.8 to 7.25.9
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.25.8 to 7.25.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.9/packages/babel-preset-env)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 19:22:44 +00:00
Trevor Buckner
2ed6aad6f7 Merge pull request #3849 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.25.9
Bump @babel/core from 7.25.8 to 7.25.9
2024-10-23 15:22:31 -04:00
Víctor Losada Hernández
41cbb67155 Merge branch 'master' into fix-snippetbar-behaviour 2024-10-23 21:21:45 +02:00
Trevor Buckner
1ab6ef078e Merge branch 'master' into dependabot/npm_and_yarn/babel/core-7.25.9 2024-10-23 15:21:40 -04:00
Trevor Buckner
25a7af5b70 Merge pull request #3847 from naturalcrit/dependabot/npm_and_yarn/babel/preset-react-7.25.9
Bump @babel/preset-react from 7.25.7 to 7.25.9
2024-10-23 15:21:27 -04:00
dependabot[bot]
5f41decdb6 Bump @babel/core from 7.25.8 to 7.25.9
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.25.8 to 7.25.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.9/packages/babel-core)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 19:04:33 +00:00
Trevor Buckner
3a1053ad70 Merge branch 'master' into dependabot/npm_and_yarn/babel/preset-react-7.25.9 2024-10-23 15:03:23 -04:00
Trevor Buckner
d768a41746 Merge pull request #3846 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-react-7.37.2
Bump eslint-plugin-react from 7.37.1 to 7.37.2
2024-10-23 15:03:14 -04:00
Trevor Buckner
f5f22fe0e9 Merge pull request #3805 from 5e-Cleric/add-notification-client-side
Add notification client side
2024-10-23 15:01:14 -04:00
G.Ambatte
27c5f86205 Merge branch 'master' into addAdminFixScriptTool-#3801 2024-10-24 07:32:52 +13:00
G.Ambatte
361d1c1aff Merge branch 'master' into refactorVersionHistory 2024-10-24 07:32:09 +13:00
Víctor Losada Hernández
88e5f26dd8 not render snippets element if empty 2024-10-23 20:16:30 +02:00
Víctor Losada Hernández
ddfccba822 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into add-notification-client-side 2024-10-23 19:48:45 +02:00
Víctor Losada Hernández
069d054e30 remove console logs and lint 2024-10-23 19:47:12 +02:00
Víctor Losada Hernández
1f7ff4386b fix html in notifs 2024-10-23 19:45:47 +02:00
Trevor Buckner
6373c398bc Fix scroll PR when no hash 2024-10-23 13:45:10 -04:00
Víctor Losada Hernández
7a78408415 Merge branch 'add-notification-client-side' of https://github.com/5e-Cleric/homebrewery; branch 'master' of https://github.com/naturalcrit/homebrewery into add-notification-client-side 2024-10-23 19:33:14 +02:00
Víctor Losada Hernández
52311f989d Merge branch 'userpage-to-func-comp' of https://github.com/5e-Cleric/homebrewery into userpage-to-func-comp 2024-10-23 17:03:02 +02:00
Víctor Losada Hernández
b9bf9c7e70 "Refactor UserPage component: removed unnecessary import, updated function signature, and moved useState hook declaration" 2024-10-23 17:03:00 +02:00
Víctor Losada Hernández
45ada35a11 Merge branch 'master' into userpage-to-func-comp 2024-10-23 12:48:17 +02:00
Víctor Losada Hernández
9d2a305f03 initial commit 2024-10-23 09:02:23 +02:00
dependabot[bot]
9cc7799d5c Bump @babel/preset-react from 7.25.7 to 7.25.9
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.25.7 to 7.25.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.9/packages/babel-preset-react)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 03:42:08 +00:00
dependabot[bot]
c235695f04 Bump eslint-plugin-react from 7.37.1 to 7.37.2
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.1 to 7.37.2.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.1...v7.37.2)

---
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-10-23 03:41:54 +00:00
Víctor Losada Hernández
999a96b5ce "Rename dismissKeys to dismisskeys in Dialog component and its usage" 2024-10-22 21:46:57 +02:00
Trevor Buckner
ef5e3ddf4c Merge pull request #3506 from naturalcrit/scroll-to-element
Get external link and scroll to element
2024-10-22 14:10:11 -04:00
Trevor Buckner
d5f498cbf9 Merge branch 'master' into scroll-to-element 2024-10-22 14:09:49 -04:00
Trevor Buckner
5040b9528f cleanup 2024-10-22 14:08:20 -04:00
Trevor Buckner
83a48b8d0c Simplify observer 2024-10-22 13:58:35 -04:00
Trevor Buckner
9fbdd24d01 Cleanup 2024-10-22 13:52:34 -04:00
Trevor Buckner
5b136f651c Call scrollToHash from our existing "frameDidMount`
`frameDidMount` is equivalent to using iframe.addEventListener('load');

Let's not add a new listener and just use the existing event we already have. Functionality still works.
2024-10-22 13:03:12 -04:00
Trevor Buckner
effeef0d67 Merge pull request #3842 from naturalcrit/dependabot/npm_and_yarn/eslint-9.13.0
Bump eslint from 9.12.0 to 9.13.0
2024-10-21 13:28:22 -04:00
Trevor Buckner
cf3d5df582 Merge branch 'master' into dependabot/npm_and_yarn/eslint-9.13.0 2024-10-21 11:25:56 -04:00
Trevor Buckner
71cdbf1079 Merge pull request #3843 from naturalcrit/dependabot/npm_and_yarn/superagent-10.1.1
Bump superagent from 10.1.0 to 10.1.1
2024-10-21 11:25:41 -04:00
Trevor Buckner
712db7dfa7 Merge branch 'master' into dependabot/npm_and_yarn/superagent-10.1.1 2024-10-21 11:22:46 -04:00
Trevor Buckner
ed48c6664b Merge pull request #3844 from G-Ambatte/catchIDBErrors
Catch IndexedDB errors
2024-10-21 09:59:29 -04:00
G.Ambatte
917f20cdd5 Merge branch 'master' into catchIDBErrors 2024-10-21 16:27:30 +13:00
G.Ambatte
0c98f832bb Add catch to await calls 2024-10-21 16:21:41 +13:00
dependabot[bot]
41a60e6312 Bump superagent from 10.1.0 to 10.1.1
Bumps [superagent](https://github.com/ladjs/superagent) from 10.1.0 to 10.1.1.
- [Release notes](https://github.com/ladjs/superagent/releases)
- [Changelog](https://github.com/ladjs/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/ladjs/superagent/compare/v10.1.0...v10.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:20:23 +00:00
dependabot[bot]
5994e0d0b3 Bump eslint from 9.12.0 to 9.13.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.12.0 to 9.13.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.12.0...v9.13.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-10-21 03:20:07 +00:00
Trevor Buckner
c723f820f7 Merge pull request #3635 from naturalcrit/snippet-bar-wrapping
Snippet bar wrapping
2024-10-20 13:04:48 -04:00
Trevor Buckner
0e9021049c lint 2024-10-20 13:00:05 -04:00
Víctor Losada Hernández
1d663ef38d last iteration 2024-10-20 12:19:38 +02:00
Víctor Losada Hernández
d3e11ead54 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into snippet-bar-wrapping 2024-10-20 12:19:26 +02:00
Trevor Buckner
79e1f4caf7 Merge pull request #3838 from naturalcrit/Allow-Markdown/styles-to-update-on-any-text-change
Allow Markdown/Style to update on any text change
2024-10-19 00:11:44 -04:00
Trevor Buckner
59e397a65a Merge branch 'master' into Allow-Markdown/styles-to-update-on-any-text-change 2024-10-19 00:10:35 -04:00
Trevor Buckner
6b066c3fd3 Merge pull request #3837 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.7.2
Bump mongoose from 8.7.1 to 8.7.2
2024-10-18 14:59:10 -04:00
Trevor Buckner
b7413714be Merge branch 'master' into dependabot/npm_and_yarn/mongoose-8.7.2 2024-10-18 12:58:04 -04:00
Trevor Buckner
ec49d5e526 Merge pull request #3836 from dbolack-ab/MigrateGlobaltocToggles
Migrate .tocGlobalH? toggles
2024-10-18 10:56:33 -04:00
Trevor Buckner
d53a271c9f Change to .page and tweak spacing/newlines to match other snippets
All of our "global" style snippets are just set at `.page`, not `.pages`. This still applies globally but is consistent with our current approach.
2024-10-18 10:53:16 -04:00
Trevor Buckner
9d81f50b60 Remove disabled = false
Don't need that tag at all when it's false.
2024-10-18 10:46:59 -04:00
Trevor Buckner
ac766f3b37 Update brewRenderer.jsx 2024-10-18 10:23:56 -04:00
dependabot[bot]
cd09408aa8 Bump mongoose from 8.7.1 to 8.7.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.7.1 to 8.7.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.7.1...8.7.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-10-18 03:37:53 +00:00
David Bolack
631ef795b7 Fix .tocGlobalH?
Based on OH DEAR GOD THE LAG! discoveries related to the global toggles, these are being moved from a markup tag to a styles tab insert.
2024-10-17 18:30:31 -05:00
David Bolack
01c4e3ec1f Merge branch 'master' of github.com:naturalcrit/homebrewery 2024-10-17 18:14:45 -05:00
Víctor Losada Hernández
5a75182aff changes as requested, wrapping of editor tools, and linting 2024-10-18 00:52:26 +02:00
Víctor Losada Hernández
8538ccabe6 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into snippet-bar-wrapping 2024-10-17 23:47:02 +02:00
Víctor Losada Hernández
8ef88a4799 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-10-17 23:42:57 +02:00
Víctor Losada Hernández
189625c79b adding margin to scroll action to prevent toolbar overlapping 2024-10-17 23:38:27 +02:00
Víctor Losada Hernández
d872a496a7 fix mutation observer integration 2024-10-17 23:38:11 +02:00
Trevor Buckner
a1d2b5314b Merge pull request #3834 from naturalcrit/DisablePagesHasSelector
Disable Global ToC Snippet
2024-10-17 17:05:11 -04:00
Víctor Losada Hernández
9a4473526a move around 2 2024-10-17 22:59:29 +02:00
Víctor Losada Hernández
5077fda3f6 move stuff around for minimal changes 2024-10-17 22:58:14 +02:00
Víctor Losada Hernández
397ae31f56 remove stale changes 2024-10-17 22:56:58 +02:00
Trevor Buckner
f2f06b23fd Disable Global ToC Snippet 2024-10-17 16:42:57 -04:00
Víctor Losada Hernández
87915ef0ef remove unnecessary changes 2024-10-17 22:42:55 +02:00
Víctor Losada Hernández
c9240c7023 fix last line pack-lock 2024-10-17 22:41:24 +02:00
Víctor Losada Hernández
e54d237a19 fix pack-lock 2024-10-17 22:40:53 +02:00
Víctor Losada Hernández
ccfd5578cf Merge branch 'master' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-10-17 22:39:28 +02:00
Víctor Losada Hernández
618e865e52 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into add-notification-client-side 2024-10-17 22:29:31 +02:00
Trevor Buckner
4dd07a3c11 Merge pull request #3815 from G-Ambatte/tweakBrewRendererBackgroundStyle
Tweak color of Brew Renderer background text
2024-10-16 16:25:11 -04:00
Trevor Buckner
7888bfa878 Merge branch 'master' into tweakBrewRendererBackgroundStyle 2024-10-16 16:23:47 -04:00
Trevor Buckner
17b77f5460 Merge pull request #3832 from naturalcrit/memoizeRenderPages 2024-10-15 22:41:55 -04:00
Trevor Buckner
d348d1e689 Merge branch 'master' into memoizeRenderPages 2024-10-15 22:41:27 -04:00
Trevor Buckner
2aa60f793d Fix /new
/new starts with no `text` so it will crash without `?.`
2024-10-15 22:39:13 -04:00
Trevor Buckner
72739f28e8 Merge pull request #3831 from naturalcrit/memoizeRenderPages 2024-10-15 22:18:46 -04:00
Trevor Buckner
f1aeea18d4 Only jump when on text panel 2024-10-15 22:07:55 -04:00
Trevor Buckner
321bbba4b8 Rearrange 2024-10-15 22:07:41 -04:00
Trevor Buckner
36af1cdb7f Update brewRenderer.jsx 2024-10-15 21:47:27 -04:00
Trevor Buckner
0813daf01f Merge pull request #3826 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.27.0
Bump react-router-dom from 6.26.2 to 6.27.0
2024-10-15 10:39:10 -04:00
dependabot[bot]
6d137d9ca8 Bump react-router-dom from 6.26.2 to 6.27.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.26.2 to 6.27.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.27.0/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.27.0/packages/react-router-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-15 14:38:35 +00:00
Trevor Buckner
5490cc9fe6 Merge pull request #3825 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.10.0
Bump stylelint from 16.9.0 to 16.10.0
2024-10-15 10:37:15 -04:00
dependabot[bot]
10283e6e45 Bump stylelint from 16.9.0 to 16.10.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.9.0 to 16.10.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.9.0...16.10.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-10-14 03:49:21 +00:00
David Bolack
154ee06fc4 iMerge branch 'master' of github.com:naturalcrit/homebrewery 2024-10-13 21:27:11 -05:00
Gazook89
810c2140c9 move some toolbar specific styling to toolbar.less 2024-10-13 20:45:05 -05:00
Gazook89
c4fbc8d827 Merge branch 'master' into View-Modes 2024-10-13 09:40:05 -05:00
Gazook89
96ae07a456 Small style change on checkbox inputs 2024-10-13 09:39:41 -05:00
G.Ambatte
0d1291a713 Merge branch 'master' into addAdminFixScriptTool-#3801 2024-10-13 22:47:32 +13:00
Víctor Losada Hernández
3c66907a86 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-10-13 11:15:15 +02:00
Víctor Losada Hernández
0663e82fa1 remove unnecessary space in homepage markdown 2024-10-13 10:58:24 +02:00
Víctor Losada Hernández
6e241c5bcd remove splitpane parent 2024-10-13 10:53:05 +02:00
Víctor Losada Hernández
ebe5dca7a9 remove unnecessary child in nav 2024-10-13 10:51:06 +02:00
Gazook89
b33d9264d3 unify terms to 'spreads'
This commit only renames things, changes no logic.  Any mention of "book", "view", or "mode" is renamed in relation to "spreads".

The AnchoredBox.jsx file is renamed to Anchored.jsx

Extra icons are deleted, and the remaining ones are renamed.
2024-10-12 23:25:14 -05:00
Gazook89
0855c5c181 remove errant console.log 2024-10-12 23:12:37 -05:00
Gazook89
1741abc3fe Refactor AnchoredBox for greater flexibility
Big change to how the AnchoredBox component is structured so that it can be used in more than just one spot.  Now composed of the wrapper Anchored, with two children AnchoredTrigger and AnchoredBox, each of which can have their own arbitrary children.

Next steps will involve renaming the component file to Anchored.jsx, moving most styling out of the attached stylesheet (and into brewRenderer.less), and adding props to pass in Anchor Positioning properties.
2024-10-12 23:12:27 -05:00
G.Ambatte
63f4104f81 Add UI to Admin page 2024-10-13 13:45:24 +13:00
G.Ambatte
6bc865144a Add cleaning function to API 2024-10-13 13:45:11 +13:00
G.Ambatte
ccafee7a21 Get text from textBin in brew object 2024-10-13 13:44:33 +13:00
G.Ambatte
033a089fd8 Merge branch 'master' into refactorVersionHistory 2024-10-13 12:17:27 +13:00
Gazook89
27f471791d Small change in title phrasing 2024-10-12 13:28:25 -05:00
Gazook89
ae11da2bc7 Fix the styles overriding previous styles
If there were two inputs sending styles to the same target (ie row and column gap), they would override each other.

This change fixes that by deepening the merges.  Admittedly, I turned to cGPT to help me with this as the nesting was throwing me for a loop.  It works, though, and I understand it now that I can read it.
2024-10-12 11:53:52 -05:00
Gazook89
b58b9ca8f0 Merge branch 'View-Modes-Radio-Options' into View-Modes 2024-10-12 11:06:09 -05:00
Gazook89
ba0b3e7d93 Add toggle for Page Shadows
Reworks the pagesStyles to a broader object previewStyles.  This new object has this structure:

```
{
  targetElement : { cssProperty: value }
}
```
2024-10-12 11:05:23 -05:00
Gazook89
6fca21b6ed set defaultValue of gap sliders
defaultValue matches the normal styled values of the gap property on the .pages element.

removed the "tooltip" that showed the current value from the range sliders inside the anchoredbox (the gap sliders).
2024-10-11 22:53:38 -05:00
Gazook89
a3dbaf9e6a refactor anchoredBox to handle focus
Refactoring the AnchoredBox component because I wanted to set focus on the trigger button when the expanded box was closed.  Wrapping the trigger into it's own component, with forwardRef, allows for passing the `ref` to the actual DOM node.  Then the `.focus()` method will work on it.
2024-10-11 22:34:40 -05:00
Gazook89
835305bcf6 Add a title to each button
add a role to the toolbr.
2024-10-11 22:32:53 -05:00
Gazook89
395f2d16fa change view mode toggles to indiv buttons
Rather than a single button with three states, it is three buttons.  Went with buttons with `role='radio'` rather than true radios mostly because that is what Radix does.
2024-10-11 22:06:41 -05:00
Gazook89
6cabdc0a67 add alternative icons in
the `-alt` icons have better sizing/readability than the originals.  The originals likely will be removed.

Also adds back in 'fit-width' icon which was mistakenly overwritten earlier.
2024-10-11 22:05:18 -05:00
G.Ambatte
f64e7d3fd7 Shift grabage collection to use delMany instead of a looped del 2024-10-12 10:57:01 +13:00
G.Ambatte
cdbb2fa26a Add IDB custom store wrapper 2024-10-12 10:55:02 +13:00
G.Ambatte
bec830c3b8 Tweak color of Brew Renderer background text 2024-10-12 08:09:51 +13:00
Gazook89
e80588b234 Add icons 2024-10-11 13:51:41 -05:00
Gazook89
73504a3386 Setting a padding-top to avoid pages hugging toolbar 2024-10-11 13:51:06 -05:00
Gazook89
9c4b936ddd bump node to avoid experimental flag issues from another merge 2024-10-11 13:50:40 -05:00
David Bolack
222d49bdca Merge branch 'master' of github.com:naturalcrit/homebrewery 2024-10-11 12:08:57 -05:00
David Bolack
f386be3494 Merge branch 'master' of github.com:naturalcrit/homebrewery 2024-10-11 09:07:15 -05:00
Gazook89
c4074d67f5 Merge branch 'master' into View-Modes 2024-10-10 23:34:26 -05:00
David Bolack
9506be6b65 Revert "Revert --experimental-vm-modules"
This reverts commit 1aabb84731.
2024-10-10 17:41:40 -05:00
David Bolack
1aabb84731 Revert --experimental-vm-modules
Code works but does not work with jest as expected.
2024-10-10 17:40:32 -05:00
Víctor Losada Hernández
b2c3d620a4 Merge branch 'master' into add-notification-client-side 2024-10-10 13:30:36 +02:00
Víctor Losada Hernández
2d8874acaf move back to conditional showing instead of conditional rendering 2024-10-10 13:17:24 +02:00
Víctor Losada Hernández
001bf4a605 move dismisskeys into state for proper rerender 2024-10-10 12:56:37 +02:00
Víctor Losada Hernández
2b6e166e86 Revert "types react apparently needed?"
This reverts commit 55b5c1e713.
2024-10-10 02:09:52 +02:00
Víctor Losada Hernández
f8c995e59e remove comments 2024-10-10 02:09:11 +02:00
Víctor Losada Hernández
656c9399ef initial commit 2024-10-10 02:06:00 +02:00
Víctor Losada Hernández
55b5c1e713 types react apparently needed? 2024-10-10 01:16:50 +02:00
Víctor Losada Hernández
6456c22c61 testing mutation Observer, don't review this yet 2024-10-09 21:33:26 +02:00
Víctor Losada Hernández
e396a51ad5 Merge branch 'v3.15.2' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-10-07 11:47:13 +02:00
Gazook89
68c75fbfd2 adjust CSS to support Firefox
Set's a non-ideal but functional css style for the box when using Firefox so the box is at least visible.
2024-10-06 22:16:46 -05:00
Gazook89
d6d6cc1e29 Add View Mode Options
Adds a new AnchoredBox component that is functionally a clone of the "saving error" notifications, but drops a lot of the JS in favor of the new (chrome-only!) CSS Anchor Positioning API.  In subsequent commits, either alternate styling or a polyfill will be added non-supported browsers.

The box contains a few inputs that modify the CSS applied to `.pages`, most critically a "start on right" toggle for the Facing Pages mode.
2024-10-06 21:51:44 -05:00
Gazook89
9fce94af63 Small CSS tweaks/display 'value' tooltip only if exists
The range slider should only display a tooltip for the value if the value attribute exists.  For example, the difference between controlled and uncontrolled inputs.

Update toolBar.less
2024-10-06 21:26:48 -05:00
Gazook89
41f390b305 Add a classname to recto configuration
Adding class name so that it can be toggled between 'recto' and 'verso'.  Verso being the normal left/right configuration, no styling is needed.  With recto, the first page is shifted to the second slot, or right side.
2024-10-06 21:24:50 -05:00
Gazook89
8115b1504e Add flex property to pages 2024-10-05 23:01:08 -05:00
Gazook89
34fa724fdd Change .pages margin to .brewRenderer padding
More consistent visuals since the zooming of the .pages element affects the spacing around the edges, and the brewRenderer padding isn't affected.
2024-10-05 22:43:13 -05:00
Gazook89
24544e713e Basically revert previous change
Adding the margin resets back in because otherwise each .page margin is set to "auto" on the sides, and that makes them zoom awkwardly when in facing and flow modes.
2024-10-05 22:35:42 -05:00
Gazook89
06a806e260 preserve margin around .pages
Keep margin around .pages element such that the pages are never bumped right against the divider.
2024-10-05 21:55:42 -05:00
Gazook89
4259931b67 Merge branch 'master' into View-Modes 2024-10-05 21:39:06 -05:00
Gazook89
a96ff6ecb3 Variable name changes for clarity
Followed suggestions on the PR.
2024-09-23 21:05:37 -05:00
Gazook89
5af45f16b0 remove tagInput-class
This file was just the old StringArrayEditor that I kept around for easy reference.  Can be deleted now.
2024-09-23 14:54:24 -05:00
Gazook89
a9b6d5ff38 Merge branch 'master' into Functional-Tag-Editor 2024-09-23 14:53:41 -05:00
Gazook89
433f016c25 rename tag container class to unify with fields 2024-09-19 15:57:40 -05:00
Gazook89
10a9bc2906 Merge branch 'master' into Functional-Tag-Editor 2024-09-19 15:54:08 -05:00
Gazook89
b585e85f0f Fix multiple duplicate tags updating at once
Fixes an issue where tags with duplicate values would all update to the same value after editing just one.

Also an adjustment to the parameters that are passed to handleInputKeyDown-- they are now one object.  This helps handle an "options" object where more optional features can be turned on and off.
2024-09-19 15:48:47 -05:00
Gazook89
8a67e1eccd Merge branch 'Functional-Tag-Editor' into Func-Tag-Editor-Features 2024-09-19 14:54:28 -05:00
Gazook89
7ea1696065 Adjust html structure to handle tags as list
Begin work on setting a better html structure for the component.

Create a .less file for the component, which I may not actually use.
2024-09-19 10:40:09 -05:00
Gazook89
5b4a7c168f Add comma to "submit" buttons
Now comma (`,`) submits a tag, like `Enter`
2024-09-18 23:54:12 -05:00
Gazook89
a54adc1e4b Set new tag input to clear itself after submission
Now whenever a new tag is submitted, the input element is cleared and ready for the next tag.

Whitespace cleanup.
2024-09-18 23:39:26 -05:00
Gazook89
c1288ce4bb Use index to find and remove tags
Fixes issue in last commit, so removing a tag that has duplicate value of other tags only removes the correct one, not the others as well.
2024-09-18 23:24:10 -05:00
Gazook89
c65210b3ed Add 'remove' button and method
New button that triggers `submitTag()` method directly (rather than throw onKeyDown event) and passes `null` as the newValue.  New `if` condition checks for null on newValue and if true, removes the tag that matches the originalValue.

This *does* currently delete all duplicate tags if they match the one you are deleting.  Not sure when you'd ever want duplicate tags, but regardless i'll likely switch this to work via Index, not value.
2024-09-18 23:13:46 -05:00
Gazook89
70a3cb9ef9 Add method for adding new tags
Component now accepts new tags entered in the always-present input field.  Entering a value and hitting Enter submits the tag, and it appears as a new tag.

Updated the tag list keys to be unique (via `index`).

To-Do: empty 'new tag' input after submitting.
2024-09-18 22:46:00 -05:00
Gazook89
d1686c4c8f Add in handlers for TagInput value changes
Now brew metadata is actually updated and preserved across reloads to match updated tag values.  useEffect calls the props.onChange event from the parent component on every change to the valueContext state of this component (right now, after hitting Enter in a tag input).
2024-09-18 22:18:18 -05:00
Gazook89
c5033db336 add editing of input functionality
Currently uses uncontrolled inputs with a `defaultValue` attribute set to the values passed in via props.  The input can then be edited, and when `Enter` is pressed, it updates the stored value state.  Later, this can be updated to be trigger with `Tab` or clicking outside the active input element.
2024-09-18 21:00:24 -05:00
Gazook89
36aa4ea508 Add click handler for readTags to open text input
Clicking on a readTag now converts that tag to a text input, and maintains the tag value.  It also closes any other open text inputs amongst the tags (but leaves the "new tag" input open).
2024-09-17 23:50:59 -05:00
Gazook89
d5c5b4315b Render tags as "write" or "read"
Tags are now either "readTag" or "writeTag", with the former being a div with the tag value and the latter a text input with the value.

Minor class name change in LESS.
2024-09-17 23:28:56 -05:00
Gazook89
d505e4e24c Render element for each value from props
Take an array of values from props, load it into valueContext state with an "editing" boolean for each value.  Then, when rendering the component, take each value in the valueContext array and create a div for each --

at this point, if the value is "being edited", it returns a div with text "editing".  If not being edited, it returns a div with the value as text.

Nothing is being edited at this point since that functionality doesn't exist yet.
2024-09-17 23:16:06 -05:00
Gazook89
ea7f18e3b0 Merge branch 'master' into Functional-Tag-Editor 2024-09-17 14:50:31 -05:00
Gazook89
e8e16f4d66 Initial commit: Rename component, set basic structure
No actual functionality implemented yet, just renames the component from "StringArrayEditor" to "TagInput", for brevity at the possible cost of clarity.  For now, the original StringArrayEditor is kept and named "TagInput-class.jsx" so that I can reference it as I work on the functional component.
2024-09-17 14:46:56 -05:00
Víctor Losada Hernández
2e5ebb861e simplify logic 2024-09-17 19:50:39 +02:00
Víctor Losada Hernández
1b577c4030 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-09-17 13:20:32 +02:00
Víctor Losada Hernández
e92c169e71 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-09-16 23:01:40 +02:00
Víctor Losada Hernández
e213eb0a78 "Refactor BrewRenderer: removed iframe load event listener, simplified page scrolling logic, and inlined getPageContainingElement functionality" 2024-09-15 18:37:27 +02:00
Víctor Losada Hernández
422829cbd8 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-09-15 16:52:12 +02:00
Víctor Losada Hernández
3360b4e829 refctor logic 2024-09-07 19:12:59 +02:00
Víctor Losada Hernández
9e1a532105 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into scroll-to-element 2024-09-07 18:45:03 +02:00
Gazook89
2b79583e8c Merge branch 'pr/3499' into View-Modes 2024-08-24 22:03:06 -05:00
Gazook89
609b40e84c Merge branch 'pr/3499' into View-Modes 2024-08-24 21:28:39 -05:00
Gazook89
07c574fa42 Merge branch 'Fill-Pane-Buttons' into View-Modes 2024-08-23 23:26:24 -05:00
Gazook89
960ecae861 setup styles for different view modes
added styles for different modes-- basically a grid for 'facing' and flex for 'flow'.
2024-08-23 14:29:52 -05:00
Gazook89
49a4daa8f6 useEffect hook to update view after mode select
utilize useEffect hook to trigger render when the mode state is changed.

Move modes array to top level array, and change 'book-mode' to 'facing', and add 'flow' mode.

toggle modes as class names in .pages div, which are each styled as required.
2024-08-23 14:29:02 -05:00
Víctor Losada Hernández
1f41745d2b "Refactored Snippetbar component: updated JSX structure, added div wrapper for snippets, changed CSS styles for editors and snippets" 2024-08-23 13:37:12 +02:00
Víctor Losada Hernández
1602f0af37 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into snippet-bar-wrapping 2024-08-23 13:37:03 +02:00
Gazook89
375c54016c Get basic function worked out
Adds `setBookMode()` which toggles a className on `.pages`.  The `.book-mode` class sets display to grid, and the first child/page to start at the second slot of the book arrangement.
2024-08-23 00:32:30 -05:00
G.Ambatte
de20311299 Fix package-lock.json 2024-08-16 22:01:37 +12:00
G.Ambatte
5fede97fa5 Merge branch 'master' into fixLinks-#3547 2024-08-16 21:46:37 +12:00
Víctor Losada Hernández
cc76ff1478 relocated container query 2024-08-15 12:07:55 +02:00
Víctor Losada Hernández
bbe56bf443 linting 2024-08-14 20:39:15 +02:00
Víctor Losada Hernández
f449132b4c wrap correctly 2024-08-14 20:38:38 +02:00
G.Ambatte
b99c0382f6 Merge branch 'master' into fixLinks-#3547 2024-08-03 14:13:42 +12:00
G.Ambatte
e3586f0734 Fix errors introduced during merge conflict resolution 2024-07-31 22:17:31 +12:00
G.Ambatte
8d49422061 Link clean up 2024-07-31 21:43:49 +12:00
G.Ambatte
59790bd005 Merge branch 'master' into fixLinks-#3547 2024-07-31 21:42:42 +12:00
Víctor Losada Hernández
24c950227a Merge branch 'master' into scroll-to-element 2024-07-22 22:43:43 +02:00
G.Ambatte
b55db94822 Merge branch 'master' into fixLinks-#3547 2024-07-17 15:07:47 +12:00
G.Ambatte
c17f976385 Add HTML sanitization test step to CircleCI 2024-07-04 10:07:05 +12:00
G.Ambatte
e83e6567af Add tests via JSDOM-global 2024-07-04 10:02:45 +12:00
G.Ambatte
b638cca547 Stop evaluation tests as soon as one returns true 2024-07-04 09:32:29 +12:00
G.Ambatte
2fc5bcabb8 Merge branch 'master' into fixLinks-#3547 2024-07-04 09:13:32 +12:00
G.Ambatte
52658d6e44 Remove vue-html-secure package 2024-07-02 15:34:40 +12:00
G.Ambatte
9f3a4dc6bb Functional vue-html-secure version 2024-07-02 08:21:42 +12:00
G.Ambatte
acb10d7695 Exclude tags in FORBID_TAGS 2024-07-01 09:35:48 +12:00
G.Ambatte
fa4ced0592 Explicitly forbid script tags 2024-07-01 09:30:50 +12:00
G.Ambatte
3f1d6a5459 Re-enable DOMPurify cleaning 2024-07-01 09:30:26 +12:00
G.Ambatte
d60d902e27 Merge branch 'master' into fixLinks-#3547 2024-07-01 09:18:02 +12:00
G.Ambatte
e1c1e32a4b Return to official package; use custom addHook 2024-07-01 09:14:57 +12:00
G.Ambatte
5c2f603860 Probably terrible solution 2024-06-30 23:30:40 +12:00
G.Ambatte
47b78510df Change DOMPurify config 2024-06-29 15:35:37 +12:00
Víctor Losada Hernández
fdbec6d789 Merge branch 'master' into scroll-to-element 2024-06-12 18:08:26 +02:00
Víctor Losada Hernández
90b504d67d Oops 2024-05-31 20:26:12 +02:00
Víctor Losada Hernández
8efea112b4 "Updated scrollToPage argument to add 1 to pageNumber" 2024-05-31 20:22:14 +02:00
Víctor Losada Hernández
acbdd1b801 "Removed iframe parameter from scrollToPage and getPageContainingElement functions, instead getting iframe element by id 'BrewRenderer' inside the functions." 2024-05-31 17:13:29 +02:00
Víctor Losada Hernández
d012a09346 "Refactor BrewRenderer: updated iframe handling and scrolling logic in useEffect and scrollToPage function" 2024-05-31 17:06:47 +02:00
Víctor Losada Hernández
3cca38302a "Refactor BrewRenderer component: removed unnecessary code, reorganized useEffect hooks, and simplified getPageContainingElement function." 2024-05-31 17:00:03 +02:00
Víctor Losada Hernández
f9352a94c6 "Refactor BrewRenderer: simplify URL param extraction, remove iframe existence checks, and update scrollIntoView behavior" 2024-05-31 16:47:17 +02:00
Víctor Losada Hernández
1add97b1b2 scrolls to page 100 ms after page load 2024-05-31 16:38:08 +02:00
91 changed files with 3111 additions and 2323 deletions

View File

@@ -76,6 +76,9 @@ jobs:
- run:
name: Test - Routes
command: npm run test:route
- run:
name: Test - HTML sanitization
command: npm run test:safehtml
- run:
name: Test - Coverage
command: npm run test:coverage

10
babel.config.json Normal file
View File

@@ -0,0 +1,10 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-runtime",
"babel-plugin-transform-import-meta"
]
}

View File

@@ -85,6 +85,52 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Wednesday 11/27/2024 - v3.16.1
{{taskList
##### 5e-Cleric
* [x] Allow linking to specific HTML IDs via `#ID` at the end of the URL, e.g.: `homebrewery.naturalcrit.com/share/share/a6RCXwaDS58i#p4` to link to Page 4 directly
Fixes issues [#2820](https://github.com/naturalcrit/homebrewery/issues/2820), [#3505](https://github.com/naturalcrit/homebrewery/issues/3505)
* [x] Fix generation of link to certain Google Drive brews
Fixes issue [#3776](https://github.com/naturalcrit/homebrewery/issues/3776)
##### abquintic
* [x] Fix blank pages appearing when pasting text
Fixes issue [#3718](https://github.com/naturalcrit/homebrewery/issues/3718)
##### Gazook89
* [x] Add new brew viewing options to the view toolbar
- {{fac,single-spread}} {{openSans **SINGLE PAGE**}}
- {{fac,facing-spread}} {{openSans **TWO PAGE**}}
- {{fac,flow-spread}} {{openSans **GRID**}}
Fixes issue [#1379](https://github.com/naturalcrit/homebrewery/issues/1379)
* [x] Updates to tag input boxes
##### G-Ambatte
* [x] Admin tools to fix certain corrupted documents
Fixes issue [#3801](https://github.com/naturalcrit/homebrewery/issues/3801)
* [x] Fix print window being affected by document zoom
Fixes issue [#3744](https://github.com/naturalcrit/homebrewery/issues/3744)
##### calculuschild, 5e-Cleric, G-Ambatte, Gazook89, abquintic
* [x] Multiple code refactors, cleanups, and security fixes
}}
### Saturday 10/12/2024 - v3.16.0
{{taskList

View File

@@ -1,3 +1,5 @@
require('./brewLookup.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
@@ -12,22 +14,43 @@ const BrewLookup = createClass({
},
getInitialState() {
return {
query : '',
foundBrew : null,
searching : false,
error : null
query : '',
foundBrew : null,
searching : false,
error : null,
scriptCount : 0
};
},
handleChange(e){
this.setState({ query: e.target.value });
},
lookup(){
this.setState({ searching: true, error: null });
this.setState({ searching: true, error: null, scriptCount: 0 });
request.get(`/admin/lookup/${this.state.query}`)
.then((res)=>this.setState({ foundBrew: res.body }))
.then((res)=>{
const foundBrew = res.body;
const scriptCheck = foundBrew?.text.match(/(<\/?s)cript/g);
this.setState({
foundBrew : foundBrew,
scriptCount : scriptCheck?.length || 0,
});
})
.catch((err)=>this.setState({ error: err }))
.finally(()=>this.setState({ searching: false }));
.finally(()=>{
this.setState({
searching : false
});
});
},
async cleanScript(){
if(!this.state.foundBrew?.shareId) return;
await request.put(`/admin/clean/script/${this.state.foundBrew.shareId}`)
.catch((err)=>{ this.setState({ error: err }); return; });
this.lookup();
},
renderFoundBrew(){
@@ -46,12 +69,23 @@ const BrewLookup = createClass({
<dt>Share Link</dt>
<dd><a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>/share/{brew.shareId}</a></dd>
<dt>Created Time</dt>
<dd>{brew.createdAt ? Moment(brew.createdAt).toLocaleString() : 'No creation date'}</dd>
<dt>Last Updated</dt>
<dd>{Moment(brew.updatedAt).fromNow()}</dd>
<dt>Num of Views</dt>
<dd>{brew.views}</dd>
<dt>SCRIPT tags detected</dt>
<dd>{this.state.scriptCount}</dd>
</dl>
{this.state.scriptCount > 0 &&
<div className='cleanButton'>
<button onClick={this.cleanScript}>CLEAN BREW</button>
</div>
}
</div>;
},

View File

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

View File

@@ -66,7 +66,7 @@ const NotificationAdd = ()=>{
<label className='field'>
Dismiss Key:
<input className='fieldInput' type='text' ref={dismissKeyRef} required
placeholder='GOOGLEDRIVENOTIF'
placeholder='dismiss_notif_drive'
/>
</label>

View File

@@ -14,9 +14,6 @@ const NotificationDetail = ({ notification, onDelete })=>(
<dt>Title</dt>
<dd>{notification.title || 'No Title'}</dd>
<dt>Text</dt>
<dd>{notification.text || 'No Text'}</dd>
<dt>Created</dt>
<dd>{Moment(notification.createdAt).format('LLLL')}</dd>
@@ -25,6 +22,9 @@ const NotificationDetail = ({ notification, onDelete })=>(
<dt>Stop</dt>
<dd>{Moment(notification.stopAt).format('LLLL') || 'No End Time'}</dd>
<dt>Text</dt>
<dd>{notification.text || 'No Text'}</dd>
</dl>
<button onClick={()=>onDelete(notification.dismissKey)}>DELETE</button>
</>

View File

@@ -0,0 +1,91 @@
import React, { useState, useRef, forwardRef, useEffect, cloneElement, Children } from 'react';
import './Anchored.less';
// Anchored is a wrapper component that must have as children an <AnchoredTrigger> and a <AnchoredBox> component.
// AnchoredTrigger must have a unique `id` prop, which is passed up to Anchored, saved in state on mount, and
// then passed down through props into AnchoredBox. The `id` is used for the CSS Anchor Positioning properties.
// **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.
const Anchored = ({ children })=>{
const [visible, setVisible] = useState(false);
const [anchorId, setAnchorId] = useState(null);
const boxRef = useRef(null);
const triggerRef = useRef(null);
// promote trigger id to Anchored id (to pass it back down to the box as "anchorId")
useEffect(()=>{
if(triggerRef.current){
setAnchorId(triggerRef.current.id);
}
}, []);
// close box on outside click or Escape key
useEffect(()=>{
const handleClickOutside = (evt)=>{
if(
boxRef.current &&
!boxRef.current.contains(evt.target) &&
triggerRef.current &&
!triggerRef.current.contains(evt.target)
) {
setVisible(false);
}
};
const handleEscapeKey = (evt)=>{
if(evt.key === 'Escape') setVisible(false);
};
window.addEventListener('click', handleClickOutside);
window.addEventListener('keydown', handleEscapeKey);
return ()=>{
window.removeEventListener('click', handleClickOutside);
window.removeEventListener('keydown', handleEscapeKey);
};
}, []);
const toggleVisibility = ()=>setVisible((prev)=>!prev);
// Map children to inject necessary props
const mappedChildren = Children.map(children, (child)=>{
if(child.type === AnchoredTrigger) {
return cloneElement(child, { ref: triggerRef, toggleVisibility, visible });
}
if(child.type === AnchoredBox) {
return cloneElement(child, { ref: boxRef, visible, anchorId });
}
return child;
});
return <>{mappedChildren}</>;
};
// forward ref for AnchoredTrigger
const AnchoredTrigger = forwardRef(({ toggleVisibility, visible, children, className, ...props }, ref)=>(
<button
ref={ref}
className={`anchored-trigger${visible ? ' active' : ''} ${className}`}
onClick={toggleVisibility}
style={{ anchorName: `--${props.id}` }} // setting anchor properties here allows greater recyclability.
{...props}
>
{children}
</button>
));
// forward ref for AnchoredBox
const AnchoredBox = forwardRef(({ visible, children, className, anchorId, ...props }, ref)=>(
<div
ref={ref}
className={`anchored-box${visible ? ' active' : ''} ${className}`}
style={{ positionAnchor: `--${anchorId}` }} // setting anchor properties here allows greater recyclability.
{...props}
>
{children}
</div>
));
export { Anchored, AnchoredTrigger, AnchoredBox };

View File

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

View File

@@ -1,22 +1,26 @@
// Dialog box, for popups and modal blocking messages
const React = require('react');
import React from 'react';
const { useRef, useEffect } = React;
function Dialog({ dismissKey, closeText = 'Close', blocking = false, ...rest }) {
function Dialog({ dismisskeys = [], closeText = 'Close', blocking = false, ...rest }) {
const dialogRef = useRef(null);
useEffect(()=>{
if(!dismissKey || !localStorage.getItem(dismissKey)) {
if(dismisskeys.length !== 0) {
blocking ? dialogRef.current?.showModal() : dialogRef.current?.show();
}
}, []);
}, [dialogRef.current, dismisskeys]);
const dismiss = ()=>{
dismissKey && localStorage.setItem(dismissKey, true);
dismisskeys.forEach((key)=>{
if(key) {
localStorage.setItem(key, 'true');
}
});
dialogRef.current?.close();
};
return (
return (
<dialog ref={dialogRef} onCancel={dismiss} {...rest}>
{rest.children}
<button className='dismiss' onClick={dismiss}>

View File

@@ -1,11 +1,11 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./brewRenderer.less');
const React = require('react');
const { useState, useRef, useCallback } = React;
const { useState, useRef, useCallback, useMemo } = React;
const _ = require('lodash');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
const ErrorBar = require('./errorBar/errorBar.jsx');
const ToolBar = require('./toolBar/toolBar.jsx');
@@ -16,8 +16,7 @@ const Frame = require('react-frame-component').default;
const dedent = require('dedent-tabs').default;
const { printCurrentBrew } = require('../../../shared/helpers.js');
const DOMPurify = require('dompurify');
const purifyConfig = { FORCE_BODY: true, SANITIZE_DOM: false };
import { safeHTML } from './safeHTML.js';
const PAGE_HEIGHT = 1056;
@@ -29,6 +28,7 @@ const INITIAL_CONTENT = dedent`
<base target=_blank>
</head><body style='overflow: hidden'><div></div></body></html>`;
//v=====----------------------< Brew Page Component >---------------------=====v//
const BrewPage = (props)=>{
props = {
@@ -36,15 +36,15 @@ const BrewPage = (props)=>{
index : 0,
...props
};
const cleanText = props.contents; //DOMPurify.sanitize(props.contents, purifyConfig);
return <div className={props.className} id={`p${props.index + 1}`} >
const cleanText = safeHTML(props.contents);
return <div className={props.className} id={`p${props.index + 1}`} style={props.style}>
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
</div>;
};
//v=====--------------------< Brew Renderer Component >-------------------=====v//
const renderedPages = [];
let renderedPages = [];
let rawPages = [];
const BrewRenderer = (props)=>{
@@ -65,8 +65,14 @@ const BrewRenderer = (props)=>{
const [state, setState] = useState({
isMounted : false,
visibility : 'hidden',
zoom : 100
visibility : 'hidden'
});
const [displayOptions, setDisplayOptions] = useState({
zoomLevel : 100,
spread : 'single',
startOnRight : true,
pageShadows : true
});
const mainRef = useRef(null);
@@ -77,6 +83,26 @@ const BrewRenderer = (props)=>{
rawPages = props.text.split(/^\\page$/gm);
}
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 updateCurrentPage = useCallback(_.throttle((e)=>{
const { scrollTop, clientHeight, scrollHeight } = e.target;
const totalScrollableHeight = scrollHeight - clientHeight;
@@ -105,9 +131,9 @@ const BrewRenderer = (props)=>{
};
const renderStyle = ()=>{
const cleanStyle = props.style; //DOMPurify.sanitize(props.style, purifyConfig);
const themeStyles = props.themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `${themeStyles} \n\n <style> ${cleanStyle} </style>` }} />;
const cleanStyle = safeHTML(`${themeStyles} \n\n <style> ${props.style} </style>`);
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: cleanStyle }} />;
};
const renderPage = (pageText, index)=>{
@@ -117,7 +143,13 @@ const BrewRenderer = (props)=>{
} 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);
return <BrewPage className='page' index={index} key={index} contents={html} />;
const styles = {
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
// Add more conditions as needed
};
return <BrewPage className='page' index={index} key={index} contents={html} style={styles} />;
}
};
@@ -129,7 +161,8 @@ const BrewRenderer = (props)=>{
renderedPages.length = 0;
// Render currently-edited page first so cross-page effects (variables, links) can propagate out first
renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1);
if(rawPages.length > props.currentEditorCursorPageNum -1)
renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1);
_.forEach(rawPages, (page, index)=>{
if((isInView(index) || !renderedPages[index]) && typeof window !== 'undefined'){
@@ -150,6 +183,8 @@ const BrewRenderer = (props)=>{
};
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
scrollToHash(window.location.hash);
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
renderPages(); //Make sure page is renderable before showing
setState((prevState)=>({
@@ -165,20 +200,25 @@ const BrewRenderer = (props)=>{
document.dispatchEvent(new MouseEvent('click'));
};
//Toolbar settings:
const handleZoom = (newZoom)=>{
setState((prevState)=>({
...prevState,
zoom : newZoom
}));
const handleDisplayOptionsChange = (newDisplayOptions)=>{
setDisplayOptions(newDisplayOptions);
};
const pagesStyle = {
zoom : `${displayOptions.zoomLevel}%`,
columnGap : `${displayOptions.columnGap}px`,
rowGap : `${displayOptions.rowGap}px`
};
const styleObject = {};
if(global.config.deployment) {
styleObject.backgroundImage = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='40px' width='200px'><text x='0' y='15' fill='white' font-size='20'>${global.config.deployment}</text></svg>")`;
styleObject.backgroundImage = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='40px' width='200px'><text x='0' y='15' fill='%23fff7' font-size='20'>${global.config.deployment}</text></svg>")`;
}
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
renderedPages = useMemo(()=>renderPages(), [props.text]);
return (
<>
{/*render dummy page while iFrame is mounting.*/}
@@ -196,7 +236,7 @@ const BrewRenderer = (props)=>{
<NotificationPopup />
</div>
<ToolBar onZoomChange={handleZoom} currentPage={props.currentBrewRendererPageNum} totalPages={rawPages.length}/>
<ToolBar displayOptions={displayOptions} currentPage={props.currentBrewRendererPageNum} totalPages={rawPages.length} onDisplayOptionsChange={handleDisplayOptionsChange} />
{/*render in iFrame so broken code doesn't crash the site.*/}
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
@@ -214,9 +254,10 @@ const BrewRenderer = (props)=>{
{state.isMounted
&&
<>
{renderStyle()}
<div className='pages' lang={`${props.lang || 'en'}`} style={{ zoom: `${state.zoom}%` }}>
{renderPages()}
{renderedStyle}
<div lang={`${props.lang || 'en'}`} style={pagesStyle} className={
`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}` } >
{renderedPages}
</div>
</>
}

View File

@@ -3,13 +3,45 @@
.brewRenderer {
overflow-y : scroll;
will-change : transform;
padding-top : 30px;
padding-top : 60px;
height : 100vh;
&:has(.facing, .flow) {
padding : 60px 30px;
}
&.deployment {
background-color: darkred;
}
:where(.pages) {
margin : 30px 0px;
&.facing {
display: grid;
grid-template-columns: repeat(2, auto);
grid-template-rows: repeat(3, auto);
gap: 10px 10px;
justify-content: center;
&.recto .page:first-child {
// sets first page on 'right' ('recto') of the preview, as if for a Cover page.
// todo: add a checkbox to toggle this setting
grid-column-start: 2;
}
& :where(.page) {
margin-left: unset !important;
margin-right: unset !important;
}
}
&.flow {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-start;
& :where(.page) {
flex: 0 0 auto;
margin-left: unset !important;
margin-right: unset !important;
}
}
& > :where(.page) {
width : 215.9mm;
height : 279.4mm;
@@ -18,6 +50,9 @@
margin-left : auto;
box-shadow : 1px 4px 14px #000000;
}
*[id] {
scroll-margin-top:100px;
}
}
&::-webkit-scrollbar {
width : 20px;

View File

@@ -1,44 +1,62 @@
require('./notificationPopup.less');
const React = require('react');
const _ = require('lodash');
import React, { useEffect, useState } from 'react';
import request from '../../utils/request-middleware.js';
import Dialog from '../../../components/dialog.jsx';
const DISMISS_KEY = 'dismiss_notification01-10-24';
const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
const NotificationPopup = ()=>{
return <Dialog className='notificationPopup' dismissKey={DISMISS_KEY} closeText={DISMISS_BUTTON} >
const [notifications, setNotifications] = useState([]);
const [dissmissKeyList, setDismissKeyList] = useState([]);
const [error, setError] = useState(null);
useEffect(()=>{
getNotifications();
}, []);
const getNotifications = async ()=>{
setError(null);
try {
const res = await request.get('/admin/notification/all');
pickActiveNotifications(res.body || []);
} catch (err) {
console.log(err);
setError(`Error looking up notifications: ${err?.response?.body?.message || err.message}`);
}
};
const pickActiveNotifications = (notifs)=>{
const now = new Date();
const filteredNotifications = notifs.filter((notification)=>{
const startDate = new Date(notification.startAt);
const stopDate = new Date(notification.stopAt);
const dismissed = localStorage.getItem(notification.dismissKey) ? true : false;
return now >= startDate && now <= stopDate && !dismissed;
});
setNotifications(filteredNotifications);
setDismissKeyList(filteredNotifications.map((notif)=>notif.dismissKey));
};
const renderNotificationsList = ()=>{
if(error) return <div className='error'>{error}</div>;
return notifications.map((notification)=>(
<li key={notification.dismissKey} >
<em>{notification.title}</em><br />
<p dangerouslySetInnerHTML={{ __html: notification.text }}></p>
</li>
));
};
return <Dialog className='notificationPopup' dismisskeys={dissmissKeyList} closeText={DISMISS_BUTTON} >
<div className='header'>
<i className='fas fa-info-circle info'></i>
<h3>Notice</h3>
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
</div>
<ul>
<li key='Vault'>
<em>Search brews with our new page!</em><br />
We have been working very hard in making this possible, now you can share your work and look at it in the new <a href='/vault'>Vault</a> page!
All PUBLISHED brews will be available to anyone searching there, by title or author, and filtering by renderer.
More features will be coming.
</li>
<li key='googleDriveFolder'>
<em>Don't delete your Homebrewery folder on Google Drive!</em> <br />
We have had several reports of users losing their brews, not realizing
that they had deleted the files on their Google Drive. If you have a Homebrewery folder
on your Google Drive with *.txt files inside, <em>do not delete it</em>!
We cannot help you recover files that you have deleted from your own
Google Drive.
</li>
<li key='faq'>
<em>Protect your work! </em> <br />
If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews!&nbsp;
<a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'>
See the FAQ
</a> to learn how to avoid losing your work!
</li>
{renderNotificationsList()}
</ul>
</Dialog>;
};

View File

@@ -55,7 +55,10 @@
margin-top : 1.4em;
font-size : 0.8em;
line-height : 1.4em;
em { font-weight : 800; }
em {
text-transform:capitalize;
font-weight : 800;
}
}
}
}

View File

@@ -0,0 +1,46 @@
// Derived from the vue-html-secure package, customized for Homebrewery
let doc = null;
let div = null;
function safeHTML(htmlString) {
// If the Document interface doesn't exist, exit
if(typeof document == 'undefined') return null;
// If the test document and div don't exist, create them
if(!doc) doc = document.implementation.createHTMLDocument('');
if(!div) div = doc.createElement('div');
// Set the test div contents to the evaluation string
div.innerHTML = htmlString;
// Grab all nodes from the test div
const elements = div.querySelectorAll('*');
// Blacklisted tags
const blacklistTags = ['script', 'noscript', 'noembed'];
// Tests to remove attributes
const blacklistAttrs = [
(test)=>{return test.localName.indexOf('on') == 0;},
(test)=>{return test.localName.indexOf('type') == 0 && test.value.match(/submit/i);},
(test)=>{return test.value.replace(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g, '').toLowerCase().trim().indexOf('javascript:') == 0;}
];
elements.forEach((element)=>{
// Check each element for blacklisted type
if(blacklistTags.includes(element?.localName?.toLowerCase())) {
element.remove();
return;
}
// Check remaining elements for blacklisted attributes
for (const attribute of element.attributes){
if(blacklistAttrs.some((test)=>{return test(attribute);})) {
element.removeAttribute(attribute.localName);
break;
};
};
});
return div.innerHTML;
};
module.exports.safeHTML = safeHTML;

View File

@@ -3,26 +3,28 @@ 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 = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
const ToolBar = ({ displayOptions, currentPage, totalPages, onDisplayOptionsChange })=>{
const [zoomLevel, setZoomLevel] = useState(100);
const [pageNum, setPageNum] = useState(currentPage);
const [pageNum, setPageNum] = useState(currentPage);
const [toolsVisible, setToolsVisible] = useState(true);
useEffect(()=>{
onZoomChange(zoomLevel);
}, [zoomLevel]);
useEffect(()=>{
setPageNum(currentPage);
}, [currentPage]);
const handleZoomButton = (zoom)=>{
setZoomLevel(_.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
handleOptionChange('zoomLevel', _.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
};
const handleOptionChange = (optionKey, newValue)=>{
//setDisplayOptions(prevOptions => ({ ...prevOptions, [optionKey]: newValue }));
onDisplayOptionsChange({ ...displayOptions, [optionKey]: newValue });
};
const handlePageInput = (pageInput)=>{
@@ -63,47 +65,51 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
const margin = 5; // extra space so page isn't edge to edge (not truly "to fill")
const deltaZoom = (desiredZoom - zoomLevel) - margin;
const deltaZoom = (desiredZoom - displayOptions.zoomLevel) - margin;
return deltaZoom;
};
return (
<div className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`}>
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
<button className='toggleButton' title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
<div className='group'>
<div className='group' role='group' aria-label='Zoom' aria-hidden={!toolsVisible}>
<button
id='fill-width'
className='tool'
onClick={()=>handleZoomButton(zoomLevel + calculateChange('fill'))}
title='Set zoom to fill preview with one page'
onClick={()=>handleZoomButton(displayOptions.zoomLevel + calculateChange('fill'))}
>
<i className='fac fit-width' />
</button>
<button
id='zoom-to-fit'
className='tool'
onClick={()=>handleZoomButton(zoomLevel + calculateChange('fit'))}
title='Set zoom to fit entire page in preview'
onClick={()=>handleZoomButton(displayOptions.zoomLevel + calculateChange('fit'))}
>
<i className='fac zoom-to-fit' />
</button>
<button
id='zoom-out'
className='tool'
onClick={()=>handleZoomButton(zoomLevel - 20)}
disabled={zoomLevel <= MIN_ZOOM}
onClick={()=>handleZoomButton(displayOptions.zoomLevel - 20)}
disabled={displayOptions.zoomLevel <= MIN_ZOOM}
title='Zoom Out'
>
<i className='fas fa-magnifying-glass-minus' />
</button>
<input
id='zoom-slider'
className='range-input tool'
className='range-input tool hover-tooltip'
type='range'
name='zoom'
title='Set Zoom'
list='zoomLevels'
min={MIN_ZOOM}
max={MAX_ZOOM}
step='1'
value={zoomLevel}
value={displayOptions.zoomLevel}
onChange={(e)=>handleZoomButton(parseInt(e.target.value))}
/>
<datalist id='zoomLevels'>
@@ -113,18 +119,72 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
<button
id='zoom-in'
className='tool'
onClick={()=>handleZoomButton(zoomLevel + 20)}
disabled={zoomLevel >= MAX_ZOOM}
onClick={()=>handleZoomButton(displayOptions.zoomLevel + 20)}
disabled={displayOptions.zoomLevel >= MAX_ZOOM}
title='Zoom In'
>
<i className='fas fa-magnifying-glass-plus' />
</button>
</div>
{/*v=====----------------------< Spread Controls >---------------------=====v*/}
<div className='group' role='group' aria-label='Spread' aria-hidden={!toolsVisible}>
<div className='radio-group' role='radiogroup'>
<button role='radio'
id='single-spread'
className='tool'
title='Single Page'
onClick={()=>{handleOptionChange('spread', 'active');}}
aria-checked={displayOptions.spread === 'single'}
><i className='fac single-spread' /></button>
<button role='radio'
id='facing-spread'
className='tool'
title='Facing Pages'
onClick={()=>{handleOptionChange('spread', 'facing');}}
aria-checked={displayOptions.spread === 'facing'}
><i className='fac facing-spread' /></button>
<button role='radio'
id='flow-spread'
className='tool'
title='Flow Pages'
onClick={()=>{handleOptionChange('spread', 'flow');}}
aria-checked={displayOptions.spread === 'flow'}
><i className='fac flow-spread' /></button>
</div>
<Anchored>
<AnchoredTrigger id='spread-settings' className='tool' title='Spread options'><i className='fas fa-gear' /></AnchoredTrigger>
<AnchoredBox title='Options'>
<h1>Options</h1>
<label title='Modify the horizontal space between pages.'>
Column gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('columnGap', evt.target.value)} />
</label>
<label title='Modify the vertical space between rows of pages.'>
Row gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('rowGap', evt.target.value)} />
</label>
<label title='Start 1st page on the right side, such as if you have cover page.'>
Start on right
<input type='checkbox' checked={displayOptions.startOnRight} onChange={()=>{handleOptionChange('startOnRight', !displayOptions.startOnRight);}}
title={displayOptions.spread !== 'facing' ? 'Switch to Facing to enable toggle.' : null} />
</label>
<label title='Toggle the page shadow on every page.'>
Page shadows
<input type='checkbox' checked={displayOptions.pageShadows} onChange={()=>{handleOptionChange('pageShadows', !displayOptions.pageShadows);}} />
</label>
</AnchoredBox>
</Anchored>
</div>
{/*v=====----------------------< Page Controls >---------------------=====v*/}
<div className='group'>
<div className='group' role='group' aria-label='Pages' aria-hidden={!toolsVisible}>
<button
id='previous-page'
className='previousPage tool'
type='button'
title='Previous Page(s)'
onClick={()=>scrollToPage(pageNum - 1)}
disabled={pageNum <= 1}
>
@@ -137,6 +197,7 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
className='text-input'
type='text'
name='page'
title='Current page(s) in view'
inputMode='numeric'
pattern='[0-9]'
value={pageNum}
@@ -145,12 +206,14 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
onBlur={()=>scrollToPage(pageNum)}
onKeyDown={(e)=>e.key == 'Enter' && scrollToPage(pageNum)}
/>
<span id='page-count'>/ {totalPages}</span>
<span id='page-count' title='Total Page Count'>/ {totalPages}</span>
</div>
<button
id='next-page'
className='tool'
type='button'
title='Next Page(s)'
onClick={()=>scrollToPage(pageNum + 1)}
disabled={pageNum >= totalPages}
>

View File

@@ -13,11 +13,12 @@
height : auto;
padding : 2px 0;
font-family : 'Open Sans', sans-serif;
font-size : 13px;
color : #CCCCCC;
background-color : #555555;
& > *:not(.toggleButton) {
opacity: 1;
transition: all .2s ease;
opacity : 1;
transition : all 0.2s ease;
}
.group {
@@ -34,6 +35,70 @@
align-items : center;
}
.active, [aria-checked='true'] { background-color : #444444; }
.anchored-trigger {
&.active { background-color : #444444; }
}
.anchored-box {
--box-color : #555555;
top : 30px;
display : flex;
flex-direction : column;
gap : 5px;
padding : 15px;
margin-top : 10px;
font-size : 0.8em;
color : #CCCCCC;
background-color : var(--box-color);
border-radius : 5px;
h1 {
padding-bottom : 0.3em;
margin-bottom : 0.5em;
border-bottom : 1px solid currentColor;
}
h2 {
padding-bottom : 0.3em;
margin : 1em 0 0.5em 0;
color : lightgray;
border-bottom : 1px solid currentColor;
}
label {
display : flex;
gap : 6px;
align-items : center;
justify-content : space-between;
}
input {
height : unset;
&[type='range'] { padding : 0; }
}
&::before {
position : absolute;
top : -20px;
left : 50%;
width : 0px;
height : 0px;
pointer-events : none;
content : '';
border : 10px solid transparent;
border-bottom : 10px solid var(--box-color);
transform : translateX(-50%);
}
}
.radio-group:has(button[role='radio']) {
display : flex;
height : 100%;
border : 1px solid #333333;
}
input {
position : relative;
height : 1.5em;
@@ -57,7 +122,7 @@
outline : none;
}
&:hover::after {
&.hover-tooltip[value]:hover::after {
position : absolute;
bottom : -30px;
left : 50%;
@@ -83,46 +148,40 @@
}
button {
box-sizing : content-box;
box-sizing : border-box;
display : flex;
align-items : center;
justify-content : center;
width : auto;
min-width : 46px;
height : 100%;
padding : 0 0px;
font-weight : unset;
color : inherit;
background-color : unset;
&:hover { background-color : #444444; }
&:focus { outline : 1px solid #D3D3D3; }
&:focus { border : 1px solid #D3D3D3;outline : none;}
&:disabled {
color : #777777;
background-color : unset !important;
}
i {
font-size:1.2em;
}
i { font-size : 1.2em; }
}
&.hidden {
width: 32px;
transition: all .3s ease;
flex-wrap:nowrap;
overflow: hidden;
background-color: unset;
opacity: .5;
flex-wrap : nowrap;
width : 32px;
overflow : hidden;
background-color : unset;
opacity : 0.5;
transition : all 0.3s ease;
& > *:not(.toggleButton) {
opacity: 0;
transition: all .2s ease;
opacity : 0;
transition : all 0.2s ease;
}
}
}
button.toggleButton {
z-index : 5;
position:absolute;
left: 0;
width: 32px;
min-width: unset;
position : absolute;
left : 0;
z-index : 5;
width : 32px;
min-width : unset;
}

View File

@@ -4,7 +4,7 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const dedent = require('dedent-tabs').default;
const Markdown = require('../../../shared/naturalcrit/markdown.js');
import Markdown from '../../../shared/naturalcrit/markdown.js';
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
const SnippetBar = require('./snippetbar/snippetbar.jsx');
@@ -314,7 +314,7 @@ const Editor = createClass({
},
brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){
if(!window || isJumping)
if(!window || !this.isText() || isJumping)
return;
// Get current brewRenderer scroll position and calculate target position
@@ -355,7 +355,7 @@ const Editor = createClass({
},
sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){
if(!this.isText || isJumping)
if(!this.isText() || isJumping)
return;
const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/;

View File

@@ -2,6 +2,7 @@
.editor {
position : relative;
width : 100%;
container: editor / inline-size;
.codeEditor {
height : 100%;

View File

@@ -3,10 +3,10 @@ require('./metadataEditor.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const request = require('../../utils/request-middleware.js');
import request from '../../utils/request-middleware.js';
const Nav = require('naturalcrit/nav/nav.jsx');
const Combobox = require('client/components/combobox.jsx');
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
const TagInput = require('../tagInput/tagInput.jsx');
const Themes = require('themes/themes.json');
@@ -341,10 +341,11 @@ const MetadataEditor = createClass({
{this.renderThumbnail()}
</div>
<StringArrayEditor label='tags' valuePatterns={[/^(?:(?:group|meta|system|type):)?[A-Za-z0-9][A-Za-z0-9 \/.\-]{0,40}$/]}
<TagInput label='tags' valuePatterns={[/^(?:(?:group|meta|system|type):)?[A-Za-z0-9][A-Za-z0-9 \/.\-]{0,40}$/]}
placeholder='add tag' unique={true}
values={this.props.metadata.tags}
onChange={(e)=>this.handleFieldChange('tags', e)}/>
onChange={(e)=>this.handleFieldChange('tags', e)}
/>
<div className='field systems'>
<label>systems</label>
@@ -363,12 +364,13 @@ const MetadataEditor = createClass({
{this.renderAuthors()}
<StringArrayEditor label='invited authors' valuePatterns={[/.+/]}
<TagInput label='invited authors' valuePatterns={[/.+/]}
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
placeholder='invite author' unique={true}
values={this.props.metadata.invitedAuthors}
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}
/>
<h2>Privacy</h2>

View File

@@ -79,6 +79,7 @@
text-overflow : ellipsis;
}
button {
.colorButton();
padding : 0px 5px;
color : white;
background-color : black;
@@ -138,16 +139,16 @@
margin-bottom : 15px;
button { width : 100%; }
button.publish {
.button(@blueLight);
.colorButton(@blueLight);
}
button.unpublish {
.button(@silver);
.colorButton(@silver);
}
}
.delete.field .value {
button {
.button(@red);
.colorButton(@red);
}
}
.authors.field .value {
@@ -271,7 +272,7 @@
&:last-child { border-radius : 0 0.5em 0.5em 0; }
}
.badge {
.tag {
padding : 0.3em;
margin : 2px;
font-size : 0.9em;

View File

@@ -150,18 +150,22 @@ const Snippetbar = createClass({
renderSnippetGroups : function(){
const snippets = this.state.snippets.filter((snippetGroup)=>snippetGroup.view === this.props.view);
if(snippets.length === 0) return null;
return _.map(snippets, (snippetGroup)=>{
return <SnippetGroup
brew={this.props.brew}
groupName={snippetGroup.groupName}
icon={snippetGroup.icon}
snippets={snippetGroup.snippets}
key={snippetGroup.groupName}
onSnippetClick={this.handleSnippetClick}
cursorPos={this.props.cursorPos}
/>;
});
return <div className='snippets'>
{_.map(snippets, (snippetGroup)=>{
return <SnippetGroup
brew={this.props.brew}
groupName={snippetGroup.groupName}
icon={snippetGroup.icon}
snippets={snippetGroup.snippets}
key={snippetGroup.groupName}
onSnippetClick={this.handleSnippetClick}
cursorPos={this.props.cursorPos}
/>;
})
}
</div>;
},
replaceContent : function(item){
@@ -203,57 +207,58 @@ const Snippetbar = createClass({
renderEditorButtons : function(){
if(!this.props.showEditButtons) return;
let foldButtons;
if(this.props.view == 'text'){
foldButtons =
<>
<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>
</>;
}
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={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() }
<div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() }
</div>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} >
<i className='fas fa-undo' />
</div>
<div className={`editorTool redo ${this.props.historySize.redo ? 'active' : ''}`}
onClick={this.props.redo} >
<i className='fas fa-redo' />
</div>
</div>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} >
<i className='fas fa-undo' />
</div>
<div className={`editorTool redo ${this.props.historySize.redo ? 'active' : ''}`}
onClick={this.props.redo} >
<i className='fas fa-redo' />
</div>
<div className='divider'></div>
{foldButtons}
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
<div className='codeTools'>
{foldButtons}
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</div>
<div className='divider'></div>
<div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' />
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
<div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' />
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</div>
</div>;
},
@@ -291,8 +296,9 @@ const SnippetGroup = createClass({
return _.map(snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)}>
<i className={snippet.icon} />
<span className='name'title={snippet.name}>{snippet.name}</span>
<span className={`name${snippet.disabled ? ' disabled' : ''}`} title={snippet.name}>{snippet.name}</span>
{snippet.experimental && <span className='beta'>beta</span>}
{snippet.disabled && <span className='beta' title='temporarily disabled due to large slowdown; under re-design'>disabled</span>}
{snippet.subsnippets && <>
<i className='fas fa-caret-right'></i>
<div className='dropdown side'>

View File

@@ -4,97 +4,114 @@
.snippetBar {
@menuHeight : 25px;
position : relative;
height : @menuHeight;
display : flex;
flex-wrap : wrap-reverse;
justify-content : space-between;
height : auto;
color : black;
background-color : #DDDDDD;
.editors {
position : absolute;
top : 0px;
right : 0px;
.snippets {
display : flex;
justify-content : space-between;
height : @menuHeight;
& > div {
width : @menuHeight;
height : @menuHeight;
line-height : @menuHeight;
text-align : center;
cursor : pointer;
&:hover,&.selected { background-color : #999999; }
&.text {
.tooltipLeft('Brew Editor');
}
&.style {
.tooltipLeft('Style Editor');
}
&.meta {
.tooltipLeft('Properties');
}
&.undo {
.tooltipLeft('Undo');
font-size : 0.75em;
color : grey;
&.active { color : inherit; }
}
&.redo {
.tooltipLeft('Redo');
font-size : 0.75em;
color : grey;
&.active { color : inherit; }
}
&.foldAll {
.tooltipLeft('Fold All');
font-size : 0.75em;
color : inherit;
}
&.unfoldAll {
.tooltipLeft('Unfold All');
font-size : 0.75em;
color : inherit;
}
&.history {
.tooltipLeft('History');
font-size : 0.75em;
color : grey;
position : relative;
&.active {
color : inherit;
justify-content : flex-start;
min-width : 327.58px;
}
.editors {
display : flex;
justify-content : flex-end;
min-width : 225px;
&:only-child { margin-left : auto; }
>div {
display : flex;
flex : 1;
justify-content : space-around;
&:first-child { border-left : none; }
& > div {
position : relative;
width : @menuHeight;
height : @menuHeight;
line-height : @menuHeight;
text-align : center;
cursor : pointer;
&:hover,&.selected { background-color : #999999; }
&.text {
.tooltipLeft('Brew Editor');
}
&>.dropdown{
right : -1px;
&>.snippet{
padding-right : 10px;
&.style {
.tooltipLeft('Style Editor');
}
&.meta {
.tooltipLeft('Properties');
}
&.undo {
.tooltipLeft('Undo');
font-size : 0.75em;
color : grey;
&.active { color : inherit; }
}
&.redo {
.tooltipLeft('Redo');
font-size : 0.75em;
color : grey;
&.active { color : inherit; }
}
&.foldAll {
.tooltipLeft('Fold All');
font-size : 0.75em;
color : grey;
&.active { color : inherit; }
}
&.unfoldAll {
.tooltipLeft('Unfold All');
font-size : 0.75em;
color : grey;
&.active { color : inherit; }
}
&.history {
.tooltipLeft('History');
position : relative;
font-size : 0.75em;
color : grey;
border : none;
&.active { color : inherit; }
& > .dropdown {
right : -1px;
& > .snippet { padding-right : 10px; }
}
}
}
&.editorTheme {
.tooltipLeft('Editor Themes');
font-size : 0.75em;
color : black;
&.active {
position : relative;
background-color : #999999;
&.editorTheme {
.tooltipLeft('Editor Themes');
font-size : 0.75em;
color : black;
&.active {
position : relative;
background-color : #999999;
}
}
&.divider {
width : 5px;
background : linear-gradient(currentColor, currentColor) no-repeat center/1px 100%;
&:hover { background-color : inherit; }
}
}
&.divider {
width : 5px;
background : linear-gradient(currentColor, currentColor) no-repeat center/1px 100%;
&:hover { background-color : inherit; }
.themeSelector {
position : absolute;
top : 25px;
right : 0;
z-index : 10;
display : flex;
align-items : center;
justify-content : center;
width : 170px;
height : inherit;
background-color : inherit;
}
}
.themeSelector {
position : absolute;
top : 25px;
right : 0;
z-index : 10;
display : flex;
align-items : center;
justify-content : center;
width : 170px;
height : inherit;
background-color : inherit;
}
}
}
.snippetBarButton {
display : inline-block;
@@ -104,6 +121,7 @@
font-weight : 800;
line-height : @menuHeight;
text-transform : uppercase;
text-wrap : nowrap;
cursor : pointer;
&:hover, &.selected { background-color : #999999; }
i {
@@ -120,7 +138,7 @@
.tooltipLeft('Edit Brew Properties');
}
.snippetGroup {
border-right : 1px solid currentColor;
&:hover {
& > .dropdown { visibility : visible; }
}
@@ -142,11 +160,11 @@
cursor : pointer;
.animate(background-color);
i {
min-width : 25px;
height : 1.2em;
margin-right : 8px;
font-size : 1.2em;
min-width: 25px;
text-align: center;
text-align : center;
& ~ i {
margin-right : 0;
margin-left : 5px;
@@ -179,6 +197,7 @@
}
}
.name { margin-right : auto; }
.disabled { text-decoration : line-through; }
.beta {
align-self : center;
padding : 4px 6px;
@@ -204,4 +223,19 @@
}
}
}
}
}
@container editor (width < 553px) {
.snippetBar {
.editors {
flex : 1;
justify-content : space-between;
border-bottom : 1px solid;
}
.snippets {
flex : 1;
justify-content : space-evenly;
}
.editors > div.history > .dropdown { right : unset; }
}
}

View File

@@ -1,149 +0,0 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const StringArrayEditor = createClass({
displayName : 'StringArrayEditor',
getDefaultProps : function() {
return {
label : '',
values : [],
valuePatterns : null,
validators : [],
placeholder : '',
notes : [],
unique : false,
cannotEdit : [],
onChange : ()=>{}
};
},
getInitialState : function() {
return {
valueContext : !!this.props.values ? this.props.values.map((value)=>({
value,
editing : false
})) : [],
temporaryValue : '',
updateValue : ''
};
},
componentDidUpdate : function(prevProps) {
if(!_.eq(this.props.values, prevProps.values)) {
this.setState({
valueContext : this.props.values ? this.props.values.map((newValue)=>({
value : newValue,
editing : this.state.valueContext.find(({ value })=>value === newValue)?.editing || false
})) : []
});
}
},
handleChange : function(value) {
this.props.onChange({
target : {
value
}
});
},
addValue : function(value){
this.handleChange(_.uniq([...this.props.values, value]));
this.setState({
temporaryValue : ''
});
},
removeValue : function(index){
this.handleChange(this.props.values.filter((_, i)=>i !== index));
},
updateValue : function(value, index){
const valueContext = this.state.valueContext;
valueContext[index].value = value;
valueContext[index].editing = false;
this.handleChange(valueContext.map((context)=>context.value));
this.setState({ valueContext, updateValue: '' });
},
editValue : function(index){
if(!!this.props.cannotEdit && this.props.cannotEdit.includes(this.props.values[index])) {
return;
}
const valueContext = this.state.valueContext.map((context, i)=>{
context.editing = index === i;
return context;
});
this.setState({ valueContext, updateValue: this.props.values[index] });
},
valueIsValid : function(value, index) {
const values = _.clone(this.props.values);
if(index !== undefined) {
values.splice(index, 1);
}
const matchesPatterns = !this.props.valuePatterns || this.props.valuePatterns.some((pattern)=>!!(value || '').match(pattern));
const uniqueIfSet = !this.props.unique || !values.includes(value);
const passesValidators = !this.props.validators || this.props.validators.every((validator)=>validator(value));
return matchesPatterns && uniqueIfSet && passesValidators;
},
handleValueInputKeyDown : function(event, index) {
if(event.key === 'Enter') {
if(this.valueIsValid(event.target.value, index)) {
if(index !== undefined) {
this.updateValue(event.target.value, index);
} else {
this.addValue(event.target.value);
}
}
} else if(event.key === 'Escape') {
this.closeEditInput(index);
}
},
closeEditInput : function(index) {
const valueContext = this.state.valueContext;
valueContext[index].editing = false;
this.setState({ valueContext, updateValue: '' });
},
render : function() {
const valueElements = Object.values(this.state.valueContext).map((context, i)=>context.editing
? <React.Fragment key={i}>
<div className='input-group'>
<input type='text' className={`value ${this.valueIsValid(this.state.updateValue, i) ? '' : 'invalid'}`} autoFocus placeholder={this.props.placeholder}
value={this.state.updateValue}
onKeyDown={(e)=>this.handleValueInputKeyDown(e, i)}
onChange={(e)=>this.setState({ updateValue: e.target.value })}/>
{<div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.closeEditInput(i); }}><i className='fa fa-undo fa-fw'/></div>}
{this.valueIsValid(this.state.updateValue, i) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.updateValue(this.state.updateValue, i); }}><i className='fa fa-check fa-fw'/></div> : null}
</div>
</React.Fragment>
: <div className='badge' key={i} onClick={()=>this.editValue(i)}>{context.value}
{!!this.props.cannotEdit && this.props.cannotEdit.includes(context.value) ? null : <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.removeValue(i); }}><i className='fa fa-times fa-fw'/></div>}
</div>
);
return <div className='field'>
<label>{this.props.label}</label>
<div style={{ flex: '1 0' }} className='value'>
<div className='list'>
{valueElements}
<div className='input-group'>
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
value={this.state.temporaryValue}
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
</div>
</div>
{this.props.notes ? this.props.notes.map((n, index)=><p key={index}><small>{n}</small></p>) : null}
</div>
</div>;
}
});
module.exports = StringArrayEditor;

View File

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

View File

@@ -25,12 +25,11 @@
.homebrew nav {
background-color : #333333;
.navContent {
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
}
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
.navSection {
display : flex;
align-items : center;

View File

@@ -2,7 +2,7 @@ require('./brewItem.less');
const React = require('react');
const createClass = require('create-react-class');
const moment = require('moment');
const request = require('../../../../utils/request-middleware.js');
import request from '../../../../utils/request-middleware.js';
const googleDriveIcon = require('../../../../googleDrive.svg');
const homebreweryIcon = require('../../../../thumbnail.png');

View File

@@ -4,7 +4,7 @@ const React = require('react');
const _ = require('lodash');
const createClass = require('create-react-class');
const request = require('../../utils/request-middleware.js');
import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
@@ -23,7 +23,7 @@ const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const LockNotification = require('./lockNotification/lockNotification.jsx');
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
@@ -228,8 +228,8 @@ const EditPage = createClass({
htmlErrors : Markdown.validate(prevState.brew.text)
}));
await updateHistory(this.state.brew);
await versionHistoryGarbageCollection();
await updateHistory(this.state.brew).catch(console.error);
await versionHistoryGarbageCollection().catch(console.error);
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
@@ -429,41 +429,41 @@ const EditPage = createClass({
<Meta name='robots' content='noindex, nofollow' />
{this.renderNavbar()}
<div className='content'>
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
updateBrew={this.updateBrew}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
updateBrew={this.updateBrew}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
</div>
</div>;
}

View File

@@ -1,7 +1,7 @@
require('./errorPage.less');
const React = require('react');
const UIPage = require('../basePages/uiPage/uiPage.jsx');
const Markdown = require('../../../../shared/naturalcrit/markdown.js');
import Markdown from '../../../../shared/naturalcrit/markdown.js';
const ErrorIndex = require('./errors/errorIndex.js');
const ErrorPage = ({ brew })=>{

View File

@@ -2,7 +2,7 @@ require('./homePage.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
const request = require('../../utils/request-middleware.js');
import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
@@ -100,35 +100,33 @@ const HomePage = createClass({
return <div className='homePage sitePage'>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{this.renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer}
showEditButtons={false}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
/>
</SplitPane>
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer}
showEditButtons={false}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
/>
</SplitPane>
</div>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
Save current <i className='fas fa-save' />
</div>

View File

@@ -91,13 +91,6 @@ If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](
\page
## Markdown+
The Homebrewery aims to make homebrewing as simple as possible, providing a live editor with Markdown syntax that is more human-readable and faster to write with than raw HTML.

View File

@@ -2,9 +2,9 @@
require('./newPage.less');
const React = require('react');
const createClass = require('create-react-class');
const request = require('../../utils/request-middleware.js');
import request from '../../utils/request-middleware.js';
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
const Nav = require('naturalcrit/nav/nav.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
@@ -223,38 +223,38 @@ const NewPage = createClass({
render : function(){
return <div className='newPage sitePage'>
{this.renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
</div>
</div>;
}

View File

@@ -1,5 +1,5 @@
.sharePage{
.navContent .navSection.titleSection {
nav .navSection.titleSection {
flex-grow: 1;
justify-content: center;
}

View File

@@ -1,12 +1,11 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const { useState } = React;
const _ = require('lodash');
const ListPage = require('../basePages/listPage/listPage.jsx');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
@@ -14,69 +13,48 @@ const HelpNavItem = require('../../navbar/help.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const VaultNavitem = require('../../navbar/vault.navitem.jsx');
const UserPage = createClass({
displayName : 'UserPage',
getDefaultProps : function() {
return {
username : '',
brews : [],
query : '',
error : null
};
},
getInitialState : function() {
const usernameWithS = this.props.username + (this.props.username.endsWith('s') ? `` : `s`);
const UserPage = (props)=>{
props = {
username : '',
brews : [],
query : '',
...props
};
const brews = _.groupBy(this.props.brews, (brew)=>{
return (brew.published ? 'published' : 'private');
});
const [error, setError] = useState(null);
const brewCollection = [
{
title : `${usernameWithS} published brews`,
class : 'published',
brews : brews.published
}
];
if(this.props.username == global.account?.username){
brewCollection.push(
{
title : `${usernameWithS} unpublished brews`,
class : 'unpublished',
brews : brews.private
}
);
}
const usernameWithS = props.username + (props.username.endsWith('s') ? `` : `s`);
const groupedBrews = _.groupBy(props.brews, (brew)=>brew.published ? 'published' : 'private');
return {
brewCollection : brewCollection
};
},
errorReported : function(error) {
this.setState({
error
});
},
const brewCollection = [
{
title : `${usernameWithS} published brews`,
class : 'published',
brews : groupedBrews.published || []
},
...(props.username === global.account?.username ? [{
title : `${usernameWithS} unpublished brews`,
class : 'unpublished',
brews : groupedBrews.private || []
}] : [])
];
navItems : function() {
return <Navbar>
const navItems = (
<Navbar>
<Nav.section>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null
}
{error && (<ErrorNavItem error={error} parent={null}></ErrorNavItem>)}
<NewBrew />
<HelpNavItem />
<VaultNavitem/>
<VaultNavitem />
<RecentNavItem />
<Account />
</Nav.section>
</Navbar>;
},
</Navbar>
);
render : function(){
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query} reportError={this.errorReported}></ListPage>;
}
});
return (
<ListPage brewCollection={brewCollection} navItems={navItems} query={props.query} reportError={(err)=>setError(err)} />
);
};
module.exports = UserPage;

View File

@@ -15,7 +15,7 @@ const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
const SplitPane = require('../../../../shared/naturalcrit/splitPane/splitPane.jsx');
const ErrorIndex = require('../errorPage/errors/errorIndex.js');
const request = require('../../utils/request-middleware.js');
import request from '../../utils/request-middleware.js';
const VaultPage = (props)=>{
const [pageState, setPageState] = useState(parseInt(props.query.page) || 1);
@@ -411,19 +411,18 @@ const VaultPage = (props)=>{
};
return (
<div className='vaultPage'>
<div className='sitePage vaultPage'>
<link href='/themes/V3/Blank/style.css' rel='stylesheet' />
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet' />
{renderNavItems()}
<div className='content'>
<SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'>
{renderSortBar()}
{renderFoundBrews()}
</div>
</SplitPane>
<div className="content">
<SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'>
{renderSortBar()}
{renderFoundBrews()}
</div>
</SplitPane>
</div>
</div>
);

View File

@@ -5,373 +5,369 @@
*:not(input) { user-select : none; }
.content {
.content .dataGroup {
width : 100%;
height : 100%;
background : #2C3E50;
background : white;
.dataGroup {
width : 100%;
height : 100%;
background : white;
&.form .brewLookup {
position : relative;
padding : 50px clamp(20px, 4vw, 50px);
&.form .brewLookup {
position : relative;
padding : 50px clamp(20px, 4vw, 50px);
small {
font-size : 10pt;
color : #555555;
small {
font-size : 10pt;
color : #555555;
a { color : #333333; }
}
a { color : #333333; }
}
code {
padding-inline : 5px;
font-family : monospace;
background : lightgrey;
border-radius : 5px;
}
code {
padding-inline : 5px;
font-family : monospace;
background : lightgrey;
border-radius : 5px;
}
h1, h2, h3, h4 {
font-family : 'CodeBold';
letter-spacing : 2px;
}
h1, h2, h3, h4 {
font-family : 'CodeBold';
letter-spacing : 2px;
}
legend {
h3 {
margin-block : 30px 20px;
font-size : 20px;
text-align : center;
border-bottom : 2px solid;
}
ul {
padding-inline : 30px 10px;
li {
margin-block : 5px;
line-height : calc(1em + 5px);
list-style : disc;
}
}
}
&::after {
position : absolute;
top : 0;
right : 0;
left : 0;
display : block;
padding : 10px;
font-weight : 900;
color : white;
white-space : pre-wrap;
content : 'Error:\A At least one renderer should be enabled to make a search';
background : rgb(255, 60, 60);
opacity : 0;
transition : opacity 0.5s;
}
&:not(:has(input[type='checkbox']:checked))::after { opacity : 1; }
.formTitle {
margin : 20px 0;
font-size : 30px;
color : black;
legend {
h3 {
margin-block : 30px 20px;
font-size : 20px;
text-align : center;
border-bottom : 2px solid;
}
.formContents {
position : relative;
display : flex;
flex-direction : column;
label {
display : flex;
align-items : center;
margin : 10px 0;
ul {
padding-inline : 30px 10px;
li {
margin-block : 5px;
line-height : calc(1em + 5px);
list-style : disc;
}
select { margin : 0 10px; }
input {
margin : 0 10px;
&: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 {
position : absolute;
right : 20px;
bottom : 0;
i {
margin-left : 10px;
animation-duration : 1000s;
}
}
}
}
}
&.resultsContainer {
&::after {
position : absolute;
top : 0;
right : 0;
left : 0;
display : block;
padding : 10px;
font-weight : 900;
color : white;
white-space : pre-wrap;
content : 'Error:\A At least one renderer should be enabled to make a search';
background : rgb(255, 60, 60);
opacity : 0;
transition : opacity 0.5s;
}
&:not(:has(input[type='checkbox']:checked))::after { opacity : 1; }
.formTitle {
margin : 20px 0;
font-size : 30px;
color : black;
text-align : center;
border-bottom : 2px solid;
}
.formContents {
position : relative;
display : flex;
flex-direction : column;
height : 100%;
overflow-y : auto;
font-family : 'BookInsanityRemake';
font-size : 0.34cm;
h3 {
font-family : 'Open Sans';
font-weight : 900;
color : white;
label {
display : flex;
align-items : center;
margin : 10px 0;
}
select { margin : 0 10px; }
.sort-container {
display : flex;
flex-wrap : wrap;
column-gap : 15px;
justify-content : center;
height : 30px;
color : white;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
input {
margin : 0 10px;
&:invalid { background : rgb(255, 188, 181); }
.sort-option {
display : flex;
align-items : center;
padding : 0 8px;
&:hover { background-color : #444444; }
&.active {
background-color : #333333;
button {
font-weight : 800;
color : white;
& + .sortDir { padding-left : 5px; }
}
&[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;
}
button {
padding : 0;
font-size : 11px;
font-weight : normal;
color : #CCCCCC;
text-transform : uppercase;
background-color : transparent;
&:hover { background : none; }
}
}
}
.foundBrews {
position : relative;
width : 100%;
height : 100%;
max-height : 100%;
padding : 50px 50px 70px 50px;
overflow-y : scroll;
background-color : #2C3E50;
h3 { font-size : 25px; }
&.noBrews {
display : grid;
place-items : center;
color : white;
}
&.searching {
display : grid;
place-items : center;
color : white;
h3 { position : relative; }
h3.searchAnim::after {
position : absolute;
top : 50%;
right : 0;
width : max-content;
height : 1em;
content : '';
translate : calc(100% + 5px) -50%;
animation : trailingDots 2s ease infinite;
}
}
.totalBrews {
position : fixed;
right : 0;
bottom : 0;
z-index : 1000;
padding : 8px 10px;
font-family : 'Open Sans';
font-size : 11px;
font-weight : 800;
color : white;
background-color : #333333;
.searchAnim {
position : relative;
display : inline-block;
width : 3ch;
height : 1em;
&::before {
display : block;
content : 'No';
}
.searchAnim::after {
position : absolute;
top : 50%;
right : 0;
width : max-content;
height : 1em;
content : '';
translate : -50% -50%;
animation : trailingDots 2s ease infinite;
}
}
.brewItem {
width : 47%;
margin-right : 40px;
color : black;
isolation : isolate;
&::after {
position : absolute;
inset : 0;
z-index : -2;
display : block;
content : '';
background-image : url('/assets/parchmentBackground.jpg');
display : none;
content : 'Yes';
}
&:nth-child(even of .brewItem) { margin-right : 0; }
&:checked {
background : green;
h2 {
font-family : 'MrEavesRemake';
font-size : 0.75cm;
font-weight : 800;
line-height : 0.988em;
color : var(--HB_Color_HeaderText);
&::before { display : none; }
&::after { display : block; }
}
.info {
position : relative;
z-index : 2;
font-family : 'ScalySansRemake';
font-size : 1.2em;
>span {
margin-right : 12px;
line-height : 1.5em;
}
}
.links { z-index : 2; }
hr {
margin : 0px;
visibility : hidden;
}
.thumbnail { z-index : -1; }
}
}
.paginationControls {
position : absolute;
left : 50%;
display : grid;
grid-template-areas : 'previousPage currentPage nextPage';
grid-template-columns : 50px 1fr 50px;
place-items : center;
width : auto;
translate : -50%;
.pages {
display : flex;
grid-area : currentPage;
justify-content : space-evenly;
width : 100%;
height : 100%;
padding : 5px 8px;
text-align : center;
.pageNumber {
margin-inline : 1vw;
font-family : 'Open Sans';
font-weight : 900;
color : white;
text-underline-position : under;
text-wrap : nowrap;
cursor : pointer;
&.currentPage {
color : gold;
text-decoration : underline;
pointer-events : none;
}
&.firstPage { margin-right : -5px; }
&.lastPage { margin-left : -5px; }
}
}
button {
width : max-content;
&.previousPage { grid-area : previousPage; }
&.nextPage { grid-area : nextPage; }
}
#searchButton {
position : absolute;
right : 20px;
bottom : 0;
i {
margin-left : 10px;
animation-duration : 1000s;
}
}
}
}
&.resultsContainer {
display : flex;
flex-direction : column;
height : 100%;
overflow-y : auto;
font-family : 'BookInsanityRemake';
font-size : 0.34cm;
h3 {
font-family : 'Open Sans';
font-weight : 900;
color : white;
}
.sort-container {
display : flex;
flex-wrap : wrap;
column-gap : 15px;
justify-content : center;
height : 30px;
color : white;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
.sort-option {
display : flex;
align-items : center;
padding : 0 8px;
&:hover { background-color : #444444; }
&.active {
background-color : #333333;
button {
font-weight : 800;
color : white;
& + .sortDir { padding-left : 5px; }
}
}
button {
padding : 0;
font-size : 11px;
font-weight : normal;
color : #CCCCCC;
text-transform : uppercase;
background-color : transparent;
&:hover { background : none; }
}
}
}
.foundBrews {
position : relative;
width : 100%;
height : 100%;
max-height : 100%;
padding : 50px 50px 70px 50px;
overflow-y : scroll;
background-color : #2C3E50;
h3 { font-size : 25px; }
&.noBrews {
display : grid;
place-items : center;
color : white;
}
&.searching {
display : grid;
place-items : center;
color : white;
h3 { position : relative; }
h3.searchAnim::after {
position : absolute;
top : 50%;
right : 0;
width : max-content;
height : 1em;
content : '';
translate : calc(100% + 5px) -50%;
animation : trailingDots 2s ease infinite;
}
}
.totalBrews {
position : fixed;
right : 0;
bottom : 0;
z-index : 1000;
padding : 8px 10px;
font-family : 'Open Sans';
font-size : 11px;
font-weight : 800;
color : white;
background-color : #333333;
.searchAnim {
position : relative;
display : inline-block;
width : 3ch;
height : 1em;
}
.searchAnim::after {
position : absolute;
top : 50%;
right : 0;
width : max-content;
height : 1em;
content : '';
translate : -50% -50%;
animation : trailingDots 2s ease infinite;
}
}
.brewItem {
width : 47%;
margin-right : 40px;
color : black;
isolation : isolate;
&::after {
position : absolute;
inset : 0;
z-index : -2;
display : block;
content : '';
background-image : url('/assets/parchmentBackground.jpg');
}
&:nth-child(even of .brewItem) { margin-right : 0; }
h2 {
font-family : 'MrEavesRemake';
font-size : 0.75cm;
font-weight : 800;
line-height : 0.988em;
color : var(--HB_Color_HeaderText);
}
.info {
position : relative;
z-index : 2;
font-family : 'ScalySansRemake';
font-size : 1.2em;
>span {
margin-right : 12px;
line-height : 1.5em;
}
}
.links { z-index : 2; }
hr {
margin : 0px;
visibility : hidden;
}
.thumbnail { z-index : -1; }
}
.paginationControls {
position : absolute;
left : 50%;
display : grid;
grid-template-areas : 'previousPage currentPage nextPage';
grid-template-columns : 50px 1fr 50px;
place-items : center;
width : auto;
translate : -50%;
.pages {
display : flex;
grid-area : currentPage;
justify-content : space-evenly;
width : 100%;
height : 100%;
padding : 5px 8px;
text-align : center;
.pageNumber {
margin-inline : 1vw;
font-family : 'Open Sans';
font-weight : 900;
color : white;
text-underline-position : under;
text-wrap : nowrap;
cursor : pointer;
&.currentPage {
color : gold;
text-decoration : underline;
pointer-events : none;
}
&.firstPage { margin-right : -5px; }
&.lastPage { margin-left : -5px; }
}
}
button {
width : max-content;
&.previousPage { grid-area : previousPage; }
&.nextPage { grid-area : nextPage; }
}
}
}
}
}
}
@keyframes trailingDots {
@@ -388,7 +384,7 @@
// media query for when the page is smaller than 1079 px in width
@media screen and (max-width : 1079px) {
.vaultPage .content {
.vaultPage {
.dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; }

View File

@@ -0,0 +1,19 @@
import * as IDB from 'idb-keyval/dist/index.js';
export function initCustomStore(db, store){
const createCustomStore = async ()=>IDB.createStore(db, store);
return {
entries : async ()=>IDB.entries(await createCustomStore()),
keys : async ()=>IDB.keys(await createCustomStore()),
values : async ()=>IDB.values(await createCustomStore()),
clear : async ()=>IDB.clear(await createCustomStore),
get : async (key)=>IDB.get(key, await createCustomStore()),
getMany : async (keys)=>IDB.getMany(keys, await createCustomStore()),
set : async (key, value)=>IDB.set(key, value, await createCustomStore()),
setMany : async (entries)=>IDB.setMany(entries, await createCustomStore()),
update : async (key, updateFn)=>IDB.update(key, updateFn, await createCustomStore()),
del : async (key)=>IDB.del(key, await createCustomStore()),
delMany : async (keys)=>IDB.delMany(keys, await createCustomStore())
};
};

View File

@@ -1,4 +1,4 @@
const request = require('superagent');
import request from 'superagent';
const addHeader = (request)=>request.set('Homebrewery-Version', global.version);
@@ -9,4 +9,4 @@ const requestMiddleware = {
delete : (path)=>addHeader(request.delete(path)),
};
module.exports = requestMiddleware;
export default requestMiddleware;

View File

@@ -1,4 +1,4 @@
import * as IDB from 'idb-keyval/dist/index.js';
import { initCustomStore } from './customIDBStore.js';
export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY';
export const HISTORY_SLOTS = 5;
@@ -21,13 +21,15 @@ const HISTORY_SAVE_DELAYS = {
// '5' : 5
// };
const HB_DB = 'HOMEBREWERY-DB';
const HB_STORE = 'HISTORY';
const GARBAGE_COLLECT_DELAY = 28 * 24 * 60;
// const GARBAGE_COLLECT_DELAY = 10;
const HB_DB = 'HOMEBREWERY-DB';
const HB_STORE = 'HISTORY';
const IDB = initCustomStore(HB_DB, HB_STORE);
function getKeyBySlot(brew, slot){
// Return a string representing the key for this brew and history slot
return `${HISTORY_PREFIX}-${brew.shareId}-${slot}`;
@@ -53,11 +55,6 @@ function parseBrewForStorage(brew, slot = 0) {
return [key, archiveBrew];
}
// Create a custom IDB store
async function createHBStore(){
return await IDB.createStore(HB_DB, HB_STORE);
}
export async function loadHistory(brew){
const DEFAULT_HISTORY_ITEM = { expireAt: '2000-01-01T00:00:00.000Z', shareId: brew.shareId, noData: true };
@@ -69,7 +66,7 @@ export async function loadHistory(brew){
};
// Load all keys from IDB at once
const dataArray = await IDB.getMany(historyKeys, await createHBStore());
const dataArray = await IDB.getMany(historyKeys);
return dataArray.map((data)=>{ return data ?? DEFAULT_HISTORY_ITEM; });
}
@@ -97,7 +94,7 @@ export async function updateHistory(brew) {
// Update the most recent brew
historyUpdate.push(parseBrewForStorage(brew, 1));
await IDB.setMany(historyUpdate, await createHBStore());
await IDB.setMany(historyUpdate);
// Break out of data checks because we found an expired value
break;
@@ -106,14 +103,17 @@ export async function updateHistory(brew) {
};
export async function versionHistoryGarbageCollection(){
const entries = await IDB.entries();
const entries = await IDB.entries(await createHBStore());
const expiredKeys = [];
for (const [key, value] of entries){
const expireAt = new Date(value.savedAt);
expireAt.setMinutes(expireAt.getMinutes() + GARBAGE_COLLECT_DELAY);
if(new Date() > expireAt){
await IDB.del(key, await createHBStore());
expiredKeys.push(key);
};
};
if(expiredKeys.length > 0){
await IDB.delMany(expiredKeys);
}
};

View File

@@ -73,3 +73,12 @@
.fit-width {
mask-image: url('../icons/fit-width.svg');
}
.single-spread {
mask-image: url('../icons/single-spread.svg');
}
.facing-spread {
mask-image: url('../icons/facing-spread.svg');
}
.flow-spread {
mask-image: url('../icons/flow-spread.svg');
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.979101,0,0,0.919064,-29.0748,1.98095)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.979101,0,0,0.919064,23.058,1.98095)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.0781,0,0,1.0781,-3.90545,-3.90502)">
<g transform="matrix(0.590052,0,0,0.553871,-13.8993,-2.19227)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,-13.8993,44.3152)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,17.5184,-2.19227)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,50.0095,-2.19227)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,17.5184,44.3152)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
<g transform="matrix(0.590052,0,0,0.553871,50.0095,44.3152)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.41826,0,0,1.3313,-26.7845,-19.5573)">
<path d="M78.584,16.13C78.584,15.335 78.164,14.69 77.647,14.69L30.632,14.69C30.115,14.69 29.695,15.335 29.695,16.13L29.695,88.365C29.695,89.16 30.115,89.805 30.632,89.805L77.647,89.805C78.164,89.805 78.584,89.16 78.584,88.365L78.584,16.13Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 777 B

View File

@@ -8,6 +8,8 @@ const template = async function(name, title='', props = {}){
});
const ogMetaTags = ogTags.join('\n');
const ssrModule = await import(`../build/${name}/ssr.cjs`);
return `<!DOCTYPE html>
<html>
<head>
@@ -21,7 +23,7 @@ const template = async function(name, title='', props = {}){
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
</head>
<body>
<main id="reactRoot">${require(`../build/${name}/ssr.js`)(props)}</main>
<main id="reactRoot">${ssrModule.default(props)}</main>
<script src=${`/${name}/bundle.js`}></script>
<script>start_app(${JSON.stringify(props)})</script>
</body>
@@ -29,4 +31,4 @@ const template = async function(name, title='', props = {}){
`;
};
module.exports = template;
export default template;

2051
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.16.0",
"version": "3.16.1",
"type": "module",
"engines": {
"npm": "^10.2.x",
"node": "^20.17.x"
"node": "^20.18.x"
},
"repository": {
"type": "git",
@@ -38,6 +39,7 @@
"test:hard-breaks": "jest tests/markdown/hard-breaks.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",
"phb": "node --experimental-require-module scripts/phb.js",
"prod": "set NODE_ENV=production && npm run build",
"postinstall": "npm run build",
@@ -55,6 +57,9 @@
"shared",
"server"
],
"transformIgnorePatterns": [
"node_modules/(?!nanoid/).*"
],
"coveragePathIgnorePatterns": [
"build/*"
],
@@ -76,20 +81,11 @@
"jest-expect-message"
]
},
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
},
"dependencies": {
"@babel/core": "^7.25.8",
"@babel/plugin-transform-runtime": "^7.25.7",
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.25.9",
"@googleapis/drive": "^8.14.0",
"body-parser": "^1.20.2",
"classnames": "^2.5.1",
@@ -97,11 +93,11 @@
"cookie-parser": "^1.4.7",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"dompurify": "^3.1.7",
"dompurify": "^3.2.1",
"expr-eval": "^2.0.2",
"express": "^4.21.1",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.8",
"express-static-gzip": "2.2.0",
"fs-extra": "11.2.0",
"idb-keyval": "^6.2.1",
"js-yaml": "^4.1.0",
@@ -109,33 +105,35 @@
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "11.2.0",
"marked-emoji": "^1.4.2",
"marked-emoji": "^1.4.3",
"marked-extended-tables": "^1.0.10",
"marked-gfm-heading-id": "^3.2.0",
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.7.1",
"nanoid": "3.3.4",
"mongoose": "^8.8.2",
"nanoid": "5.0.8",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
"react-router-dom": "6.26.2",
"react-router-dom": "6.28.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.0",
"superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"eslint": "^9.12.0",
"eslint-plugin-jest": "^28.8.3",
"eslint-plugin-react": "^7.37.1",
"globals": "^15.11.0",
"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",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.9.0",
"stylelint": "^16.10.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0"

View File

@@ -1,13 +1,14 @@
const fs = require('fs-extra');
const Proj = require('./project.json');
const { pack } = require('vitreum');
import fs from 'fs-extra';
import Proj from './project.json' with { type: 'json' };
import vitreum from 'vitreum';
const { pack } = vitreum;
import lessTransform from 'vitreum/transforms/less.js';
import assetTransform from 'vitreum/transforms/asset.js';
const isDev = !!process.argv.find((arg)=>arg=='--dev');
const lessTransform = require('vitreum/transforms/less.js');
const assetTransform = require('vitreum/transforms/asset.js');
//const Meta = require('vitreum/headtags');
const transforms = {
'.less' : lessTransform,
'*' : assetTransform('./build')
@@ -17,7 +18,7 @@ const build = async ({ bundle, render, ssr })=>{
const css = await lessTransform.generate({ paths: './shared' });
await fs.outputFile('./build/admin/bundle.css', css);
await fs.outputFile('./build/admin/bundle.js', bundle);
await fs.outputFile('./build/admin/ssr.js', ssr);
await fs.outputFile('./build/admin/ssr.cjs', ssr);
};
fs.emptyDirSync('./build/admin');

View File

@@ -1,14 +1,15 @@
const fs = require('fs-extra');
const zlib = require('zlib');
const Proj = require('./project.json');
import fs from 'fs-extra';
import zlib from 'zlib';
import Proj from './project.json' with { type: 'json' };
import vitreum from 'vitreum';
const { pack, watchFile, livereload } = vitreum;
const { pack, watchFile, livereload } = require('vitreum');
const isDev = !!process.argv.find((arg)=>arg=='--dev');
import lessTransform from 'vitreum/transforms/less.js';
import assetTransform from 'vitreum/transforms/asset.js';
import babel from '@babel/core';
import less from 'less';
const lessTransform = require('vitreum/transforms/less.js');
const assetTransform = require('vitreum/transforms/asset.js');
const babel = require('@babel/core');
const less = require('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;
@@ -24,7 +25,7 @@ const build = async ({ bundle, render, ssr })=>{
//css = `@layer bundle {\n${css}\n}`;
await fs.outputFile('./build/homebrew/bundle.css', css);
await fs.outputFile('./build/homebrew/bundle.js', bundle);
await fs.outputFile('./build/homebrew/ssr.js', ssr);
await fs.outputFile('./build/homebrew/ssr.cjs', ssr);
await fs.copy('./client/homebrew/favicon.ico', './build/assets/favicon.ico');
@@ -51,7 +52,7 @@ fs.emptyDirSync('./build');
const themes = { Legacy: {}, V3: {} };
let themeFiles = fs.readdirSync('./themes/Legacy');
for (dir of themeFiles) {
for (let dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`).toString());
themeData.path = dir;
themes.Legacy[dir] = (themeData);
@@ -68,7 +69,7 @@ fs.emptyDirSync('./build');
}
themeFiles = fs.readdirSync('./themes/V3');
for (dir of themeFiles) {
for (let dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`).toString());
themeData.path = dir;
themes.V3[dir] = (themeData);
@@ -104,14 +105,14 @@ fs.emptyDirSync('./build');
const editorThemesBuildDir = './build/homebrew/cm-themes';
await fs.copy('./node_modules/codemirror/theme', editorThemesBuildDir);
await fs.copy('./themes/codeMirror/customThemes', editorThemesBuildDir);
editorThemeFiles = fs.readdirSync(editorThemesBuildDir);
const editorThemeFiles = fs.readdirSync(editorThemesBuildDir);
const editorThemeFile = './themes/codeMirror/editorThemes.json';
if(fs.existsSync(editorThemeFile)) fs.rmSync(editorThemeFile);
const stream = fs.createWriteStream(editorThemeFile, { flags: 'a' });
stream.write('[\n"default"');
for (themeFile of editorThemeFiles) {
for (let themeFile of editorThemeFiles) {
stream.write(`,\n"${themeFile.slice(0, -4)}"`);
}
stream.write('\n]\n');

View File

@@ -1,12 +1,12 @@
const DB = require('./server/db.js');
const server = require('./server/app.js');
const config = require('./server/config.js');
import DB from './server/db.js';
import server from './server/app.js';
import config from './server/config.js';
DB.connect(config).then(()=>{
// Ensure that we have successfully connected to the database
// before launching server
const PORT = process.env.PORT || config.get('web_port') || 8000;
server.app.listen(PORT, ()=>{
server.listen(PORT, ()=>{
const reset = '\x1b[0m'; // Reset to default style
const bright = '\x1b[1m'; // Bright (bold) style
const cyan = '\x1b[36m'; // Cyan color

View File

@@ -1,9 +1,15 @@
const HomebrewModel = require('./homebrew.model.js').model;
const NotificationModel = require('./notifications.model.js').model;
const router = require('express').Router();
const Moment = require('moment');
const templateFn = require('../client/template.js');
const zlib = require('zlib');
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';
import templateFn from '../client/template.js';
import HomebrewAPI from './homebrew.api.js';
import asyncHandler from 'express-async-handler';
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
const router = express.Router();
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3';
@@ -66,23 +72,8 @@ router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
});
/* Searches for matching edit or share id, also attempts to partial match */
router.get('/admin/lookup/:id', mw.adminOnly, async (req, res, next)=>{
HomebrewModel.findOne({
$or : [
{ editId: { $regex: req.params.id, $options: 'i' } },
{ shareId: { $regex: req.params.id, $options: 'i' } },
]
}).exec()
.then((brew)=>{
if(!brew) // No document found
return res.status(404).json({ error: 'Document not found' });
else
return res.json(brew);
})
.catch((err)=>{
console.error(err);
return res.status(500).json({ error: 'Internal Server Error' });
});
router.get('/admin/lookup/:id', mw.adminOnly, asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res, next)=>{
return res.json(req.brew);
});
/* Find 50 brews that aren't compressed yet */
@@ -100,6 +91,25 @@ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
});
});
/* Cleans `<script` and `</script>` from the "text" field of a brew */
router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{
console.log(`[ADMIN] Cleaning script tags from ShareID ${req.params.id}`);
function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');};
const brew = req.brew;
const properties = ['text', 'description', 'title'];
properties.forEach((property)=>{
brew[property] = cleanText(brew[property]);
});
splitTextStyleAndMetadata(brew);
req.body = brew;
return await HomebrewAPI.updateBrew(req, res);
});
/* Compresses the "text" field of a brew to binary */
router.put('/admin/compress/:id', (req, res)=>{
@@ -144,6 +154,7 @@ router.get('/admin/notification/all', async (req, res, next)=>{
try {
const notifications = await NotificationModel.getAll();
return res.json(notifications);
} catch (error) {
console.log('Error getting all notifications: ', error.message);
return res.status(500).json({ message: error.message });
@@ -151,7 +162,6 @@ router.get('/admin/notification/all', async (req, res, next)=>{
});
router.post('/admin/notification/add', mw.adminOnly, async (req, res, next)=>{
console.table(req.body);
try {
const notification = await NotificationModel.addNotification(req.body);
return res.status(201).json(notification);
@@ -182,4 +192,4 @@ router.get('/admin', mw.adminOnly, (req, res)=>{
});
});
module.exports = router;
export default router;

View File

@@ -1,9 +1,10 @@
const supertest = require('supertest');
import supertest from 'supertest';
import HBApp from './app.js';
import {model as NotificationModel } from './notifications.model.js';
const app = supertest.agent(require('app.js').app)
.set('X-Forwarded-Proto', 'https');
const NotificationModel = require('./notifications.model.js').model;
// Mimic https responses to avoid being redirected all the time
const app = supertest.agent(HBApp).set('X-Forwarded-Proto', 'https');
describe('Tests for admin api', ()=>{
afterEach(()=>{

View File

@@ -1,25 +1,41 @@
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
// Set working directory to project root
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import packageJSON from './../package.json' with { type: "json" };
const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(`${__dirname}/..`);
const version = packageJSON.version;
import _ from 'lodash';
import jwt from 'jwt-simple';
import express from 'express';
import yaml from 'js-yaml';
import config from './config.js';
import fs from 'fs-extra';
const _ = require('lodash');
const jwt = require('jwt-simple');
const express = require('express');
const yaml = require('js-yaml');
const app = express();
const config = require('./config.js');
const fs = require('fs-extra');
const { homebrewApi, getBrew, getUsersBrewThemes, getCSS } = require('./homebrew.api.js');
const GoogleActions = require('./googleActions.js');
const serveCompressedStaticAssets = require('./static-assets.mv.js');
const sanitizeFilename = require('sanitize-filename');
const asyncHandler = require('express-async-handler');
const templateFn = require('./../client/template.js');
import api from './homebrew.api.js';
const { homebrewApi, getBrew, getUsersBrewThemes, getCSS } = api;
import adminApi from './admin.api.js';
import vaultApi from './vault.api.js';
import GoogleActions from './googleActions.js';
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';
const { DEFAULT_BREW } = require('./brewDefaults.js');
import { DEFAULT_BREW } from './brewDefaults.js';
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
const { splitTextStyleAndMetadata } = require('../shared/helpers.js');
//==== Middleware Imports ====//
import contentNegotiation from './middleware/content-negotiation.js';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import forceSSL from './forcessl.mw.js';
const sanitizeBrew = (brew, accessType)=>{
@@ -34,10 +50,10 @@ const sanitizeBrew = (brew, accessType)=>{
app.set('trust proxy', 1 /* number of proxies between user and server */)
app.use('/', serveCompressedStaticAssets(`build`));
app.use(require('./middleware/content-negotiation.js'));
app.use(require('body-parser').json({ limit: '25mb' }));
app.use(require('cookie-parser')());
app.use(require('./forcessl.mw.js'));
app.use(contentNegotiation);
app.use(bodyParser.json({ limit: '25mb' }));
app.use(cookieParser());
app.use(forceSSL);
//Account Middleware
app.use((req, res, next)=>{
@@ -57,15 +73,14 @@ app.use((req, res, next)=>{
});
app.use(homebrewApi);
app.use(require('./admin.api.js'));
app.use(require('./vault.api.js'));
app.use(adminApi);
app.use(vaultApi);
const HomebrewModel = require('./homebrew.model.js').model;
const welcomeText = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
const welcomeTextLegacy = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg_legacy.md', 'utf8');
const migrateText = require('fs').readFileSync('client/homebrew/pages/homePage/migrate.md', 'utf8');
const changelogText = require('fs').readFileSync('changelog.md', 'utf8');
const faqText = require('fs').readFileSync('faq.md', 'utf8');
const welcomeText = fs.readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
const welcomeTextLegacy = fs.readFileSync('client/homebrew/pages/homePage/welcome_msg_legacy.md', 'utf8');
const migrateText = fs.readFileSync('client/homebrew/pages/homePage/migrate.md', 'utf8');
const changelogText = fs.readFileSync('changelog.md', 'utf8');
const faqText = fs.readFileSync('faq.md', 'utf8');
String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
@@ -479,7 +494,7 @@ const renderPage = async (req, res)=>{
deployment : config.get('heroku_app_name') ?? ''
};
const props = {
version : require('./../package.json').version,
version : version,
url : req.customUrl || req.originalUrl,
brew : req.brew,
brews : req.brews,
@@ -556,6 +571,4 @@ app.use((req, res)=>{
});
//^=====--------------------------------------=====^//
module.exports = {
app : app
};
export default app;

View File

@@ -1,4 +1,4 @@
const _ = require('lodash');
import _ from 'lodash';
// Default properties for newly-created brews
const DEFAULT_BREW = {
@@ -32,7 +32,7 @@ const DEFAULT_BREW_LOAD = _.defaults(
},
DEFAULT_BREW);
module.exports = {
export {
DEFAULT_BREW,
DEFAULT_BREW_LOAD
};

View File

@@ -1,5 +1,7 @@
module.exports = require('nconf')
.argv()
.env({ lowerCase: true })
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
.file('defaults', { file: 'config/default.json' });
import nconf from 'nconf';
export default nconf
.argv()
.env({ lowerCase: true })
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
.file('defaults', { file: 'config/default.json' });

View File

@@ -5,7 +5,7 @@
// reused by both the main application and all tests which require database
// connection.
const Mongoose = require('mongoose');
import Mongoose from 'mongoose';
const getMongoDBURL = (config)=>{
return config.get('mongodb_uri') ||
@@ -31,7 +31,7 @@ const connect = async (config)=>{
.catch((error)=>handleConnectionError(error));
};
module.exports = {
connect : connect,
disconnect : disconnect
export default {
connect,
disconnect
};

View File

@@ -1,4 +1,4 @@
module.exports = (req, res, next)=>{
export default (req, res, next)=>{
if(process.env.NODE_ENV === 'local' || process.env.NODE_ENV === 'docker') return next();
if(req.header('x-forwarded-proto') !== 'https') {
return res.redirect(302, `https://${req.get('Host')}${req.url}`);

View File

@@ -1,8 +1,9 @@
/* eslint-disable max-lines */
const googleDrive = require('@googleapis/drive');
const { nanoid } = require('nanoid');
const token = require('./token.js');
const config = require('./config.js');
import googleDrive from '@googleapis/drive';
import { nanoid } from 'nanoid';
import token from './token.js';
import config from './config.js';
let serviceAuth;
if(!config.get('service_account')){
@@ -59,7 +60,7 @@ const GoogleActions = {
account.googleRefreshToken = tokens.refresh_token;
}
account.googleAccessToken = tokens.access_token;
const JWTToken = token.generateAccessToken(account);
const JWTToken = token(account);
//Save updated token to cookie
//res.cookie('nc_session', JWTToken, { maxAge: 1000*60*60*24*365, path: '/', sameSite: 'lax' });
@@ -72,7 +73,7 @@ const GoogleActions = {
getGoogleFolder : async (auth)=>{
const drive = googleDrive.drive({ version: 'v3', auth });
fileMetadata = {
const fileMetadata = {
'name' : 'Homebrewery',
'mimeType' : 'application/vnd.google-apps.folder'
};
@@ -344,4 +345,4 @@ const GoogleActions = {
}
};
module.exports = GoogleActions;
export default GoogleActions;

View File

@@ -1,18 +1,20 @@
/* eslint-disable max-lines */
const _ = require('lodash');
const HomebrewModel = require('./homebrew.model.js').model;
const router = require('express').Router();
const zlib = require('zlib');
const GoogleActions = require('./googleActions.js');
const Markdown = require('../shared/naturalcrit/markdown.js');
const yaml = require('js-yaml');
const asyncHandler = require('express-async-handler');
const { nanoid } = require('nanoid');
const { splitTextStyleAndMetadata } = require('../shared/helpers.js');
import _ from 'lodash';
import {model as HomebrewModel} from './homebrew.model.js';
import express from 'express';
import zlib from 'zlib';
import GoogleActions from './googleActions.js';
import Markdown from '../shared/naturalcrit/markdown.js';
import yaml from 'js-yaml';
import asyncHandler from 'express-async-handler';
import { nanoid } from 'nanoid';
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
import checkClientVersion from './middleware/check-client-version.js';
const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
const router = express.Router();
const Themes = require('../themes/themes.json');
import { DEFAULT_BREW, DEFAULT_BREW_LOAD } from './brewDefaults.js';
import Themes from '../themes/themes.json' with { type: 'json' };
const isStaticTheme = (renderer, themeName)=>{
return Themes[renderer]?.[themeName] !== undefined;
@@ -87,8 +89,18 @@ const api = {
// Get relevant IDs for the brew
const { id, googleId } = api.getId(req);
const accessMap = {
edit : { editId: id },
share : { 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(accessType === 'edit' ? { editId: id } : { shareId: id })
let stub = await HomebrewModel.get(accessMap[accessType])
.catch((err)=>{
if(googleId) {
console.warn(`Unable to find document stub for ${accessType}Id ${id}`);
@@ -295,9 +307,8 @@ const api = {
req.params.id = currentTheme.theme;
req.params.renderer = currentTheme.renderer;
}
} else {
//=== Static Themes ===//
else {
const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client
const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`;
completeSnippets.push(localSnippets);
@@ -464,7 +475,7 @@ const api = {
}
};
router.use('/api', require('./middleware/check-client-version.js'));
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));
@@ -472,4 +483,4 @@ router.delete('/api/:id', asyncHandler(api.deleteBrew));
router.get('/api/remove/:id', asyncHandler(api.deleteBrew));
router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));
module.exports = api;
export default api;

View File

@@ -36,8 +36,9 @@ describe('Tests for api', ()=>{
}
});
google = require('./googleActions.js');
model = require('./homebrew.model.js').model;
google = require('./googleActions.js').default;
model = require('./homebrew.model.js').model;
api = require('./homebrew.api').default;
jest.mock('./googleActions.js');
google.authCheck = jest.fn(()=>'client');
@@ -54,8 +55,6 @@ describe('Tests for api', ()=>{
setHeader : jest.fn(()=>{})
};
api = require('./homebrew.api');
hbBrew = {
text : `brew text`,
style : 'hello yes i am css',

View File

@@ -1,7 +1,8 @@
const mongoose = require('mongoose');
const { nanoid } = require('nanoid');
const _ = require('lodash');
const zlib = require('zlib');
import mongoose from 'mongoose';
import { nanoid } from 'nanoid';
import _ from 'lodash';
import zlib from 'zlib';
const HomebrewSchema = mongoose.Schema({
shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } },
@@ -44,7 +45,7 @@ HomebrewSchema.statics.get = async function(query, fields=null){
const brew = await Homebrew.findOne(query, fields).orFail()
.catch((error)=>{throw 'Can not find brew';});
if(!_.isNil(brew.textBin)) { // Uncompress zipped text field
unzipped = zlib.inflateRawSync(brew.textBin);
const unzipped = zlib.inflateRawSync(brew.textBin);
brew.text = unzipped.toString();
}
return brew;
@@ -62,7 +63,7 @@ HomebrewSchema.statics.getByUser = async function(username, allowAccess=false, f
const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
module.exports = {
schema : HomebrewSchema,
model : Homebrew,
export {
HomebrewSchema as schema,
Homebrew as model
};

View File

@@ -1,6 +1,8 @@
module.exports = (req, res, next)=>{
import packageJSON from '../../package.json' with { type: "json" };
const version = packageJSON.version;
export default (req, res, next)=>{
const userVersion = req.get('Homebrewery-Version');
const version = require('../../package.json').version;
if(userVersion != version) {
return res.status(412).send({

View File

@@ -1,8 +1,8 @@
const config = require('../config.js');
import config from '../config.js';
const nodeEnv = config.get('node_env');
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
module.exports = (req, res, next)=>{
export default (req, res, next)=>{
const isImageRequest = req.get('Accept')?.split(',')
?.filter((h)=>!h.includes('q='))
?.every((h)=>/image\/.*/.test(h));

View File

@@ -1,41 +0,0 @@
const contentNegotiationMiddleware = require('./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

@@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const _ = require('lodash');
import mongoose from 'mongoose';
import _ from 'lodash';
const NotificationSchema = new mongoose.Schema({
dismissKey : { type: String, unique: true, required: true },
@@ -56,7 +56,7 @@ NotificationSchema.statics.getAll = async function() {
const Notification = mongoose.model('Notification', NotificationSchema);
module.exports = {
schema : NotificationSchema,
model : Notification,
export {
NotificationSchema as schema,
Notification as model
};

View File

@@ -1,4 +1,4 @@
const expressStaticGzip = require('express-static-gzip');
import expressStaticGzip from 'express-static-gzip';
// Serve brotli-compressed static files if available
const customCacheControlHandler=(response, path)=>{
@@ -28,4 +28,4 @@ const init=(pathToAssets)=>{
} });
};
module.exports = init;
export default init;

View File

@@ -1,7 +1,5 @@
const jwt = require('jwt-simple');
// Load configuration values
const config = require('./config.js');
import jwt from 'jwt-simple';
import config from './config.js';
// Generate an Access Token for the given User ID
const generateAccessToken = (account)=>{
@@ -24,6 +22,4 @@ const generateAccessToken = (account)=>{
return token;
};
module.exports = {
generateAccessToken : generateAccessToken
};
export default generateAccessToken;

View File

@@ -1,6 +1,6 @@
const express = require('express');
const asyncHandler = require('express-async-handler');
const HomebrewModel = require('./homebrew.model.js').model;
import express from 'express';
import asyncHandler from 'express-async-handler';
import {model as HomebrewModel } from './homebrew.model.js';
const router = express.Router();
@@ -106,4 +106,4 @@ const findTotal = async (req, res)=>{
router.get('/api/vault/total', asyncHandler(findTotal));
router.get('/api/vault', asyncHandler(findBrews));
module.exports = router;
export default router;

View File

@@ -1,6 +1,6 @@
const _ = require('lodash');
const yaml = require('js-yaml');
const request = require('../client/homebrew/utils/request-middleware.js');
import _ from 'lodash';
import yaml from 'js-yaml';
import request from '../client/homebrew/utils/request-middleware.js';
const splitTextStyleAndMetadata = (brew)=>{
brew.text = brew.text.replaceAll('\r\n', '\n');
@@ -51,7 +51,7 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{
}));
};
module.exports = {
export {
splitTextStyleAndMetadata,
printCurrentBrew,
fetchThemeBundle,

View File

@@ -1,7 +1,7 @@
const diceFont = require('../../../themes/fonts/iconFonts/diceFont.js');
const elderberryInn = require('../../../themes/fonts/iconFonts/elderberryInn.js');
const fontAwesome = require('../../../themes/fonts/iconFonts/fontAwesome.js');
const gameIcons = require('../../../themes/fonts/iconFonts/gameIcons.js');
import diceFont from '../../../themes/fonts/iconFonts/diceFont.js';
import elderberryInn from '../../../themes/fonts/iconFonts/elderberryInn.js';
import fontAwesome from '../../../themes/fonts/iconFonts/fontAwesome.js';
import gameIcons from '../../../themes/fonts/iconFonts/gameIcons.js';
const emojis = {
...diceFont,

View File

@@ -1,19 +1,19 @@
/* eslint-disable max-lines */
const _ = require('lodash');
const Marked = require('marked');
const MarkedExtendedTables = require('marked-extended-tables');
const { markedSmartypantsLite: MarkedSmartypantsLite } = require('marked-smartypants-lite');
const { gfmHeadingId: MarkedGFMHeadingId, resetHeadings: MarkedGFMResetHeadingIDs } = require('marked-gfm-heading-id');
const { markedEmoji: MarkedEmojis } = require('marked-emoji');
import _ from 'lodash';
import { Parser as MathParser } from 'expr-eval';
import { marked as Marked } from 'marked';
import MarkedExtendedTables from 'marked-extended-tables';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
//Icon fonts included so they can appear in emoji autosuggest dropdown
const diceFont = require('../../themes/fonts/iconFonts/diceFont.js');
const elderberryInn = require('../../themes/fonts/iconFonts/elderberryInn.js');
const fontAwesome = require('../../themes/fonts/iconFonts/fontAwesome.js');
const gameIcons = require('../../themes/fonts/iconFonts/gameIcons.js');
import diceFont from '../../themes/fonts/iconFonts/diceFont.js';
import elderberryInn from '../../themes/fonts/iconFonts/elderberryInn.js';
import gameIcons from '../../themes/fonts/iconFonts/gameIcons.js';
import fontAwesome from '../../themes/fonts/iconFonts/fontAwesome.js';
const MathParser = require('expr-eval').Parser;
const renderer = new Marked.Renderer();
const renderer = new Marked.Renderer();
const tokenizer = new Marked.Tokenizer();
//Limit math features to simple items
@@ -854,7 +854,7 @@ const globalVarsList = {};
let varsQueue = [];
let globalPageNumber = 0;
module.exports = {
const Markdown = {
marked : Marked,
render : (rawBrewText, pageNumber=0)=>{
globalVarsList[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order
@@ -865,6 +865,7 @@ module.exports = {
}
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`);
const opts = Marked.defaults;
rawBrewText = opts.hooks.preprocess(rawBrewText);
@@ -935,3 +936,6 @@ module.exports = {
return errors;
},
};
export default Markdown;

View File

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

View File

@@ -1,200 +1,110 @@
require('./splitPane.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
const { useState, useEffect } = React;
const SplitPane = createClass({
displayName : 'SplitPane',
getDefaultProps : function() {
return {
storageKey : 'naturalcrit-pane-split',
onDragFinish : function(){}, //fires when dragging
showDividerButtons : true
};
},
const storageKey = 'naturalcrit-pane-split';
getInitialState : function() {
return {
currentDividerPos : null,
windowWidth : 0,
isDragging : false,
moveSource : false,
moveBrew : false,
showMoveArrows : true
};
},
const SplitPane = (props)=>{
const {
onDragFinish = ()=>{},
showDividerButtons = true
} = props;
pane1 : React.createRef(null),
pane2 : React.createRef(null),
const [isDragging, setIsDragging] = useState(false);
const [dividerPos, setDividerPos] = useState(null);
const [moveSource, setMoveSource] = useState(false);
const [moveBrew, setMoveBrew] = useState(false);
const [showMoveArrows, setShowMoveArrows] = useState(true);
const [liveScroll, setLiveScroll] = useState(false);
componentDidMount : function() {
const dividerPos = window.localStorage.getItem(this.props.storageKey);
if(dividerPos){
this.setState({
currentDividerPos : this.limitPosition(dividerPos, 0.1*(window.innerWidth-13), 0.9*(window.innerWidth-13)),
userSetDividerPos : dividerPos,
windowWidth : window.innerWidth
});
} else {
this.setState({
currentDividerPos : window.innerWidth / 2,
userSetDividerPos : window.innerWidth / 2
});
}
window.addEventListener('resize', this.handleWindowResize);
useEffect(()=>{
const savedPos = window.localStorage.getItem(storageKey);
setDividerPos(savedPos ? limitPosition(savedPos, 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)) : window.innerWidth / 2);
setLiveScroll(window.localStorage.getItem('liveScroll') === 'true');
// This lives here instead of in the initial render because you cannot touch localStorage until the componant mounts.
const loadLiveScroll = window.localStorage.getItem('liveScroll') === 'true';
this.setState({ liveScroll: loadLiveScroll });
},
window.addEventListener('resize', handleResize);
return ()=>window.removeEventListener('resize', handleResize);
}, []);
componentWillUnmount : function() {
window.removeEventListener('resize', this.handleWindowResize);
},
const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x)));
handleWindowResize : function() {
// Allow divider to increase in size to last user-set position
// Limit current position to between 10% and 90% of visible space
const newLoc = this.limitPosition(this.state.userSetDividerPos, 0.1*(window.innerWidth-13), 0.9*(window.innerWidth-13));
this.setState({
currentDividerPos : newLoc,
windowWidth : window.innerWidth
});
},
limitPosition : function(x, min = 1, max = window.innerWidth - 13) {
const result = Math.round(Math.min(max, Math.max(min, x)));
return result;
},
handleUp : function(e){
//when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position
const handleResize = () =>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)));
const handleUp =(e)=>{
e.preventDefault();
if(this.state.isDragging){
this.props.onDragFinish(this.state.currentDividerPos);
window.localStorage.setItem(this.props.storageKey, this.state.currentDividerPos);
if(isDragging) {
onDragFinish(dividerPos);
window.localStorage.setItem(storageKey, dividerPos);
}
this.setState({ isDragging: false });
},
setIsDragging(false);
};
handleDown : function(e){
const handleDown = (e)=>{
e.preventDefault();
this.setState({ isDragging: true });
//this.unFocus()
},
handleMove : function(e){
if(!this.state.isDragging) return;
setIsDragging(true);
};
const handleMove = (e)=>{
if(!isDragging) return;
e.preventDefault();
const newSize = this.limitPosition(e.pageX);
this.setState({
currentDividerPos : newSize,
userSetDividerPos : newSize
});
},
setDividerPos(limitPosition(e.pageX));
};
liveScrollToggle : function() {
window.localStorage.setItem('liveScroll', String(!this.state.liveScroll));
this.setState({ liveScroll: !this.state.liveScroll });
},
/*
unFocus : function() {
if(document.selection){
document.selection.empty();
}else{
window.getSelection().removeAllRanges();
}
},
*/
const liveScrollToggle = ()=>{
window.localStorage.setItem('liveScroll', String(!liveScroll));
setLiveScroll(!liveScroll);
};
setMoveArrows : function(newState) {
if(this.state.showMoveArrows != newState){
this.setState({
showMoveArrows : newState
});
}
},
renderMoveArrows : function(){
if(this.state.showMoveArrows) {
return <>
<div className='arrow left'
style={{ left: this.state.currentDividerPos-4 }}
onClick={()=>this.setState({ moveSource: !this.state.moveSource })} >
<i className='fas fa-arrow-left' />
</div>
<div className='arrow right'
style={{ left: this.state.currentDividerPos-4 }}
onClick={()=>this.setState({ moveBrew: !this.state.moveBrew })} >
<i className='fas fa-arrow-right' />
</div>
<div id='scrollToggleDiv' className={this.state.liveScroll ? 'arrow lock' : 'arrow unlock'}
style={{ left: this.state.currentDividerPos-4 }}
onClick={this.liveScrollToggle} >
<i id='scrollToggle' className={this.state.liveScroll ? 'fas fa-lock' : 'fas fa-unlock'} />
</div>
</>;
}
},
renderDivider : function(){
return <>
{this.props.showDividerButtons && this.renderMoveArrows()}
<div className='divider' onPointerDown={this.handleDown} >
<div className='dots'>
<i className='fas fa-circle' />
<i className='fas fa-circle' />
<i className='fas fa-circle' />
</div>
const renderMoveArrows = (showMoveArrows &&
<>
<div className='arrow left'
onClick={()=>setMoveSource(!moveSource)} >
<i className='fas fa-arrow-left' />
</div>
</>;
},
<div className='arrow right'
onClick={()=>setMoveBrew(!moveBrew)} >
<i className='fas fa-arrow-right' />
</div>
<div id='scrollToggleDiv' className={liveScroll ? 'arrow lock' : 'arrow unlock'}
onClick={liveScrollToggle} >
<i id='scrollToggle' className={liveScroll ? 'fas fa-lock' : 'fas fa-unlock'} />
</div>
</>
);
render : function(){
return <div className='splitPane' onPointerMove={this.handleMove} onPointerUp={this.handleUp}>
<Pane
width={this.state.currentDividerPos}
>
{React.cloneElement(this.props.children[0], {
...(this.props.showDividerButtons && {
moveBrew : this.state.moveBrew,
moveSource : this.state.moveSource,
liveScroll : this.state.liveScroll,
setMoveArrows : this.setMoveArrows,
}),
})}
const renderDivider = (
<div className={`divider ${isDragging && 'dragging'}`} onPointerDown={handleDown}>
{showDividerButtons && renderMoveArrows}
<div className='dots'>
<i className='fas fa-circle' />
<i className='fas fa-circle' />
<i className='fas fa-circle' />
</div>
</div>
);
return (
<div className='splitPane' onPointerMove={handleMove} onPointerUp={handleUp}>
<Pane width={dividerPos} moveBrew={moveBrew} moveSource={moveSource} liveScroll={liveScroll} setMoveArrows={setShowMoveArrows}>
{props.children[0]}
</Pane>
{this.renderDivider()}
<Pane isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
</div>;
}
});
{renderDivider}
<Pane isDragging={isDragging}>{props.children[1]}</Pane>
</div>
);
};
const Pane = createClass({
displayName : 'Pane',
getDefaultProps : function() {
return {
width : null
};
},
render : function(){
let styles = {};
if(this.props.width){
styles = {
flex : 'none',
width : `${this.props.width}px`
};
} else {
styles = {
pointerEvents : this.props.isDragging ? 'none' : 'auto' //Disable mouse capture in the rightmost pane; dragging into the iframe drops the divider otherwise
};
}
const Pane = ({ width, children, isDragging, moveBrew, moveSource, liveScroll, setMoveArrows })=>{
const styles = width
? { flex: 'none', width: `${width}px` }
: { pointerEvents: isDragging ? 'none' : 'auto' }; //Disable mouse capture in the right pane; else dragging into the iframe drops the divider
return <div className={cx('pane', this.props.className)} style={styles}>
{this.props.children}
</div>;
}
});
return (
<div className='pane' style={styles}>
{React.cloneElement(children, { moveBrew, moveSource, liveScroll, setMoveArrows })}
</div>
);
};
module.exports = SplitPane;

View File

@@ -1,69 +1,68 @@
.splitPane{
.splitPane {
position : relative;
display : flex;
flex-direction : row;
height : 100%;
outline : none;
flex-direction : row;
.pane{
.pane {
flex : 1;
overflow-x : hidden;
overflow-y : hidden;
flex : 1;
}
.divider{
touch-action : none;
.divider {
position : relative;
display : table;
height : 100%;
width : 15px;
cursor : ew-resize;
background-color : #bbb;
height : 100%;
text-align : center;
.dots{
touch-action : none;
cursor : ew-resize;
background-color : #BBBBBB;
.dots {
display : table-cell;
vertical-align : middle;
text-align : center;
i{
vertical-align : middle;
i {
display : block !important;
margin : 10px 0px;
font-size : 6px;
color : #666;
color : #666666;
}
}
&:hover{
background-color: #999;
}
&:hover,&.dragging { background-color : #999999; }
}
.arrow{
.arrow {
position : absolute;
left : 50%;
z-index : 999;
width : 25px;
height : 25px;
border : 2px solid #bbb;
border-radius : 15px;
text-align : center;
font-size : 1.2em;
text-align : center;
cursor : pointer;
background-color : #ddd;
z-index : 999;
box-shadow : 0 4px 5px #0000007f;
&.left{
background-color : #DDDDDD;
border : 2px solid #BBBBBB;
border-radius : 15px;
box-shadow : 0 4px 5px #0000007F;
translate : -50%;
&.left {
.tooltipLeft('Jump to location in Editor');
top : 30px;
}
&.right{
&.right {
.tooltipRight('Jump to location in Preview');
top : 60px;
}
&.lock{
&.lock {
.tooltipRight('De-sync Editor and Preview locations.');
top : 90px;
background: #666;
top : 90px;
background : #666666;
}
&.unlock{
&.unlock {
.tooltipRight('Sync Editor and Preview locations');
top : 90px;
}
&:hover{
background-color: #666;
}
&:hover { background-color : #666666; }
}
}

View File

@@ -21,10 +21,7 @@ html,body, #reactRoot{
*{
box-sizing : border-box;
}
button{
.button();
}
.button(@backgroundColor : @green){
.colorButton(@backgroundColor : @green){
.animate(background-color);
display : inline-block;
padding : 0.6em 1.2em;

View File

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

View File

@@ -0,0 +1,50 @@
require('jsdom-global')();
import { safeHTML } from '../../client/homebrew/brewRenderer/safeHTML';
test('Javascript via href', function() {
const source = `<a href="javascript:alert('This is a JavaScript injection via href attribute')">Click me</a>`;
const rendered = safeHTML(source);
expect(rendered).toBe('<a>Click me</a>');
});
test('Javascript via src', function() {
const source = `<img src="javascript:alert('This is a JavaScript injection via src attribute')">`;
const rendered = safeHTML(source);
expect(rendered).toBe('<img>');
});
test('Javascript via form submit action', function() {
const source = `<form action="javascript:alert('This is a JavaScript injection via action attribute')">\n<input type="submit" value="Submit">\n</form>`;
const rendered = safeHTML(source);
expect(rendered).toBe('<form>\n<input value=\"Submit\">\n</form>');
});
test('Javascript via inline event handler - onClick', function() {
const source = `<div style="background-color: red; color: white; width: 100px; height: 100px;" onclick="alert('This is a JavaScript injection via inline event handler')">\nClick me\n</div>`;
const rendered = safeHTML(source);
expect(rendered).toBe('<div style=\"background-color: red; color: white; width: 100px; height: 100px;\">\nClick me\n</div>');
});
test('Javascript via inline event handler - onMouseOver', function() {
const source = `<div onmouseover="alert('This is a JavaScript injection via inline event handler')">Hover over me</div>`;
const rendered = safeHTML(source);
expect(rendered).toBe('<div>Hover over me</div>');
});
test('Javascript via data attribute', function() {
const source = `<div data-code="javascript:alert('This is a JavaScript injection via data attribute')">Test</div>`;
const rendered = safeHTML(source);
expect(rendered).toBe('<div>Test</div>');
});
test('Javascript via event delegation', function() {
const source = `<div id="parent"><button id="child">Click me</button></div><script>document.getElementById('parent').addEventListener('click', function(event) {if (event.target.id === 'child') {console.log('This is JavaScript executed via event delegation');}});</script>`;
const rendered = safeHTML(source);
expect(rendered).toBe('<div id="parent"><button id="child">Click me</button></div>');
});

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-lines */
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
const source = '<div>*Bold text*</div>';

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-lines */
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
describe('Inline Definition Lists', ()=>{
test('No Term 1 Definition', function() {

View File

@@ -1,4 +1,4 @@
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
const dedent = require('dedent-tabs').default;
// Marked.js adds line returns after closing tags on some default tokens.

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-lines */
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
describe('Hard Breaks', ()=>{
test('Single Break', function() {

View File

@@ -1,7 +1,7 @@
/* eslint-disable max-lines */
const dedent = require('dedent-tabs').default;
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.

View File

@@ -1,7 +1,7 @@
/* eslint-disable max-lines */
const dedent = require('dedent-tabs').default;
const Markdown = require('naturalcrit/markdown.js');
import Markdown from 'naturalcrit/markdown.js';
// Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake.

View File

@@ -1,8 +1,8 @@
const supertest = require('supertest');
import supertest from 'supertest';
import HBApp from 'app.js';
// Mimic https responses to avoid being redirected all the time
const app = supertest.agent(require('app.js').app)
.set('X-Forwarded-Proto', 'https');
const app = supertest.agent(HBApp).set('X-Forwarded-Proto', 'https');
describe('Tests for static pages', ()=>{
it('Home page works', ()=>{

View File

@@ -154,28 +154,6 @@ module.exports = [
]
},
{
name : 'Table of Contents Toggles',
icon : 'fas fa-book',
gen : `{{tocGlobalH4}}\n\n`,
subsnippets : [
{
name : 'Enable H1-H4 all pages',
icon : 'fas fa-dice-four',
gen : `{{tocGlobalH4}}\n\n`,
},
{
name : 'Enable H1-H5 all pages',
icon : 'fas fa-dice-five',
gen : `{{tocGlobalH5}}\n\n`,
},
{
name : 'Enable H1-H6 all pages',
icon : 'fas fa-dice-six',
gen : `{{tocGlobalH6}}\n\n`,
},
]
}
]
},
{
@@ -214,6 +192,27 @@ module.exports = [
line-height: 1em;
}\n\n`
},
{
name : 'Table of Contents Toggles',
icon : 'fas fa-book',
subsnippets : [
{
name : 'Enable H1-H4 all pages',
icon : 'fas fa-dice-four',
gen : `.page {\n\th4 {--TOC: include; }\n}\n\n`,
},
{
name : 'Enable H1-H5 all pages',
icon : 'fas fa-dice-five',
gen : `.page {\n\th4, h5 {--TOC: include; }\n}\n\n`,
},
{
name : 'Enable H1-H6 all pages',
icon : 'fas fa-dice-six',
gen : `.page {\n\th4, h5, h6 {--TOC: include; }\n}\n\n`,
},
]
}
]
},

View File

@@ -812,17 +812,8 @@ h6,
// Brew level default inclusion changes.
// These add Headers 'back' to inclusion.
.pages:has(.tocGlobalH4) {
h4 {--TOC: include; }
}
.pages:has(.tocGlobalH5) {
h4, h5 {--TOC: include; }
}
.pages:has(.tocGlobalH6) {
h4, h5, h6 {--TOC: include; }
}
//NOTE: DO NOT USE :HAS WITH .PAGES!!! EXTREMELY SLOW TO RENDER ON LARGE DOCS!
// Block level inclusion changes
// These include either a single (include) or a range (depth)

View File

@@ -1,4 +1,4 @@
const Markdown = require('../../../../shared/naturalcrit/markdown.js');
import Markdown from '../../../../shared/naturalcrit/markdown.js';
module.exports = {
createFooterFunc : function(headerSize=1){

View File

@@ -93,4 +93,4 @@ const diceFont = {
'df_solid_small_dot_d6_6' : 'df solid-small-dot-d6-6'
};
module.exports = diceFont;
export default diceFont;

View File

@@ -206,4 +206,4 @@ const elderberryInn = {
'ei_wish' : 'ei wish'
};
module.exports = elderberryInn;
export default elderberryInn;

View File

@@ -2051,4 +2051,4 @@ const fontAwesome = {
'fab_zhihu' : 'fab fa-zhihu'
};
module.exports = fontAwesome;
export default fontAwesome;

View File

@@ -506,4 +506,4 @@ const gameIcons = {
'gi_acid' : 'gi acid'
};
module.exports = gameIcons;
export default gameIcons;