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

Compare commits

..

212 Commits

Author SHA1 Message Date
Charlie Humphreys
e8b427ea21 fix a few issues 2023-07-18 22:13:40 -05:00
Charlie Humphreys
379f260de5 add color-selector component and colorings 2023-07-17 00:46:55 -05:00
Charlie Humphreys
46f413c656 update pill margin for snippet fields 2023-07-16 12:59:30 -05:00
Charlie Humphreys
77b0e93dd3 add more image snippets and other snippets 2023-07-16 12:35:08 -05:00
Charlie Humphreys
1b2d8d46a6 delete field component 2023-07-16 02:16:16 -05:00
Charlie Humphreys
62aae96012 Merge branch 'editor-widgets' of github.com:naturalcrit/homebrewery into editor-widgets 2023-07-16 02:15:17 -05:00
Charlie Humphreys
bef6b94dc4 add image selector field type, modal component, and new snippet widgets 2023-07-16 02:14:43 -05:00
Trevor Buckner
c113b8dd1f Merge branch 'editor-widgets' of https://github.com/naturalcrit/homebrewery into editor-widgets 2023-07-16 01:53:24 -04:00
Charlie Humphreys
e6055bd417 add support for function values 2023-07-15 01:05:41 -05:00
Charlie Humphreys
4c087e9aa5 refactor CodeMirror library instantiation 2023-07-15 01:05:41 -05:00
Charlie Humphreys
9d6a9c4ebf update component/key names 2023-07-15 01:05:41 -05:00
Charlie Humphreys
18c94f95f3 adjust field/checkbox styles 2023-07-15 01:05:41 -05:00
Charlie Humphreys
23f2f1f53b update based on feedback 2023-07-15 01:05:41 -05:00
Trevor Buckner
d044229b49 clean up codeEditor 2023-07-15 01:05:41 -05:00
Trevor Buckner
8e40cec051 tweak cClass logic 2023-07-15 01:05:41 -05:00
Trevor Buckner
ebbf0ca88b Simplify click-outside close widget logic 2023-07-15 01:05:41 -05:00
Charlie Humphreys
51760e02e7 fix ref issues and remove unneeded value 2023-07-15 01:05:41 -05:00
Charlie Humphreys
f52d42bef5 update widgets - add hints component and adjust autocomplete logic 2023-07-15 01:05:41 -05:00
Charlie Humphreys
3af5d27e3e add the concept of widgets and widget fields 2023-07-15 01:05:41 -05:00
Trevor Buckner
9d64740678 Merge pull request #2931 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recess-order-4.3.0
Bump stylelint-config-recess-order from 4.2.0 to 4.3.0
2023-07-10 23:52:35 -04:00
dependabot[bot]
053aadeff4 Bump stylelint-config-recess-order from 4.2.0 to 4.3.0
Bumps [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v4.2.0...v4.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-11 03:45:53 +00:00
Trevor Buckner
d3763beb15 Merge pull request #2927 from naturalcrit/dependabot/npm_and_yarn/marked-5.1.1
Bump marked from 5.1.0 to 5.1.1
2023-07-10 22:23:20 -04:00
Trevor Buckner
8281051797 Merge pull request #2928 from naturalcrit/dependabot/npm_and_yarn/stylelint-stylistic-0.4.3
Bump stylelint-stylistic from 0.4.2 to 0.4.3
2023-07-10 22:23:12 -04:00
Trevor Buckner
5b0104fc10 Merge pull request #2929 from naturalcrit/dependabot/npm_and_yarn/semver-5.7.2
Bump semver from 5.7.1 to 5.7.2
2023-07-10 19:45:54 -04:00
dependabot[bot]
b3fa902d85 Bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 23:38:16 +00:00
dependabot[bot]
a22d223927 Bump stylelint-stylistic from 0.4.2 to 0.4.3
Bumps [stylelint-stylistic](https://github.com/elirasza/stylelint-stylistic) from 0.4.2 to 0.4.3.
- [Commits](https://github.com/elirasza/stylelint-stylistic/compare/0.4.2...0.4.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 03:07:34 +00:00
dependabot[bot]
5c08926576 Bump marked from 5.1.0 to 5.1.1
Bumps [marked](https://github.com/markedjs/marked) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v5.1.0...v5.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 03:07:24 +00:00
Trevor Buckner
797ca7e64e Merge pull request #2543 from naturalcrit/dependabot/npm_and_yarn/react-and-react-dom-18.2.0
Bump react and react-dom
2023-07-08 02:53:15 -04:00
dependabot[bot]
5b8f2d8e3c Bump react and react-dom
Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom). These dependencies needed to be updated together.

Updates `react` from 17.0.2 to 18.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v18.2.0/packages/react)

Updates `react-dom` from 17.0.2 to 18.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v18.2.0/packages/react-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-08 06:31:42 +00:00
Trevor Buckner
bd0ef5da48 Merge pull request #2406 from G-Ambatte/addStyleSanitization-#1437
Add sanitization of Style content
2023-07-08 02:23:56 -04:00
Trevor Buckner
fe324d6822 Merge pull request #2417 from G-Ambatte/addSmartPageBreak-#2289
Add smarter footer shortcut/snippet
2023-07-08 02:17:37 -04:00
Trevor Buckner
3140299d73 Renderer is always V3. No need to check. 2023-07-08 01:48:49 -04:00
Trevor Buckner
96d04ad75a Fix TOC generators 2023-07-08 01:43:08 -04:00
Trevor Buckner
62532f788e Remove extra param sent to execute() 2023-07-08 01:42:47 -04:00
Trevor Buckner
69a3d04bb7 Merge branch 'master' into pr/2417 2023-07-08 01:11:45 -04:00
Trevor Buckner
21017e45fe Merge pull request #2901 from G-Ambatte/experimentalClickToOpen-#2702
Change dropdowns to stay open when clicked
2023-07-07 20:46:05 -04:00
Trevor Buckner
48474c6f7b Simplify dropdown & convert to Functional Component 2023-07-07 20:38:56 -04:00
Trevor Buckner
3d318f8863 Merge pull request #2926 from naturalcrit/dependabot/npm_and_yarn/jest-29.6.1
Bump jest from 29.6.0 to 29.6.1
2023-07-06 23:21:52 -04:00
Trevor Buckner
2b798f4ecb Merge pull request #2925 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.22.7
Bump @babel/plugin-transform-runtime from 7.22.6 to 7.22.7
2023-07-06 23:21:43 -04:00
Trevor Buckner
938802e1a3 Merge pull request #2924 from naturalcrit/dependabot/npm_and_yarn/mongoose-7.3.2
Bump mongoose from 7.3.1 to 7.3.2
2023-07-06 23:21:32 -04:00
Trevor Buckner
14f825f3b5 Merge pull request #2923 from naturalcrit/dependabot/npm_and_yarn/stylelint-15.10.1
Bump stylelint from 15.10.0 to 15.10.1
2023-07-06 23:21:03 -04:00
dependabot[bot]
37241a70eb Bump jest from 29.6.0 to 29.6.1
Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) from 29.6.0 to 29.6.1.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v29.6.1/packages/jest)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 03:08:55 +00:00
dependabot[bot]
bcd86a7f0c Bump @babel/plugin-transform-runtime from 7.22.6 to 7.22.7
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.22.6 to 7.22.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.7/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>
2023-07-07 03:08:33 +00:00
dependabot[bot]
cf54594a4c Bump mongoose from 7.3.1 to 7.3.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 7.3.1 to 7.3.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/7.3.1...7.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 03:08:12 +00:00
dependabot[bot]
535fdeaf62 Bump stylelint from 15.10.0 to 15.10.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.10.0 to 15.10.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/15.10.0...15.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 03:07:59 +00:00
Trevor Buckner
77bf1b5258 Merge pull request #2922 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.22.8
Bump @babel/core from 7.22.5 to 7.22.8
2023-07-06 14:41:49 -04:00
Trevor Buckner
63da418b60 Merge pull request #2921 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.22.7
Bump @babel/preset-env from 7.22.5 to 7.22.7
2023-07-06 14:41:40 -04:00
dependabot[bot]
67f5e53160 Bump @babel/core from 7.22.5 to 7.22.8
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.22.5 to 7.22.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.8/packages/babel-core)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-06 18:41:20 +00:00
dependabot[bot]
c6103d51c5 Bump @babel/preset-env from 7.22.5 to 7.22.7
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.22.5 to 7.22.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.22.7/packages/babel-preset-env)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-06 18:41:14 +00:00
Trevor Buckner
a4a10783f6 Merge pull request #2915 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recommended-13.0.0
Bump stylelint-config-recommended from 12.0.0 to 13.0.0
2023-07-06 14:41:05 -04:00
Trevor Buckner
5ed53f75c5 Merge pull request #2912 from naturalcrit/dependabot/npm_and_yarn/jest-29.6.0
Bump jest from 29.5.0 to 29.6.0
2023-07-06 14:40:38 -04:00
Trevor Buckner
81b289923a Merge pull request #2911 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.22.6
Bump @babel/plugin-transform-runtime from 7.22.5 to 7.22.6
2023-07-06 14:40:30 -04:00
Trevor Buckner
c3d8364789 Merge pull request #2910 from naturalcrit/dependabot/npm_and_yarn/stylelint-15.10.0
Bump stylelint from 15.9.0 to 15.10.0
2023-07-06 14:40:24 -04:00
Trevor Buckner
82fec9901d Merge pull request #2916 from naturalcrit/dependabot/npm_and_yarn/npm-9.8.0
Bump npm from 9.7.2 to 9.8.0
2023-07-06 14:40:16 -04:00
Trevor Buckner
173d0a726b restore livereload
It might work after all. Takes some fiddling.
2023-07-06 00:55:00 -04:00
Trevor Buckner
e064219ca0 Merge pull request #2919 from naturalcrit/FixNodemonbehavior
Fix Nodemon config in `buildHomebrew.js`
2023-07-06 00:33:27 -04:00
Trevor Buckner
ec339f2717 Fix Nodemon config 2023-07-06 00:29:45 -04:00
Trevor Buckner
d9d27808a8 Merge pull request #2918 from naturalcrit/FixGoogleLinksWith10-charShareId
Fix google links with10 char shareid
2023-07-06 00:29:08 -04:00
Trevor Buckner
a4584dc78e Fix spec tests 2023-07-06 00:19:23 -04:00
Trevor Buckner
6344eaa17d Handle Old Google Drive links that used 10-char shareID
When the Homebrewery was first made, editIds and ShareIds only had 10 characters. We later increased this to 12.

However this means some old, old Google Drive links (in the form of `googleId + editId`) were being split incorrectly because they assumed the newer 12-char length, accidentally cutting the last 2 chars from the googleId.
2023-07-06 00:10:07 -04:00
dependabot[bot]
5c41110e50 Bump npm from 9.7.2 to 9.8.0
Bumps [npm](https://github.com/npm/cli) from 9.7.2 to 9.8.0.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v9.7.2...v9.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-06 03:11:38 +00:00
G.Ambatte
085cb99562 Merge branch 'master' into addStyleSanitization-#1437 2023-07-06 12:41:14 +12:00
G.Ambatte
568586541a Merge branch 'master' into addSmartPageBreak-#2289 2023-07-06 12:40:05 +12:00
G.Ambatte
0d44e1778f Emit click event when iFrame clicked 2023-07-05 15:48:54 +12:00
dependabot[bot]
4a5269e1f3 Bump stylelint-config-recommended from 12.0.0 to 13.0.0
Bumps [stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended) from 12.0.0 to 13.0.0.
- [Release notes](https://github.com/stylelint/stylelint-config-recommended/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-recommended/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-recommended/compare/12.0.0...13.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-05 03:33:11 +00:00
dependabot[bot]
62cf0a4483 Bump jest from 29.5.0 to 29.6.0
Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) from 29.5.0 to 29.6.0.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v29.6.0/packages/jest)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-05 03:32:27 +00:00
dependabot[bot]
07c7352aa2 Bump @babel/plugin-transform-runtime from 7.22.5 to 7.22.6
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.22.5 to 7.22.6.
- [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.22.6/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>
2023-07-05 03:32:05 +00:00
dependabot[bot]
cf6c8bce88 Bump stylelint from 15.9.0 to 15.10.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.9.0 to 15.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/15.9.0...15.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>
2023-07-05 03:31:53 +00:00
G.Ambatte
45d32ebfc3 Merge branch 'master' into experimentalClickToOpen-#2702 2023-07-05 14:24:24 +12:00
Charlie
314275122d Merge pull request #2907 from naturalcrit/CleanCodeEditor.jsx
[Widgets] clean up codeEditor
2023-07-04 12:03:52 -05:00
Charlie
5716e4fcfd Merge pull request #2908 from naturalcrit/tweak-cClass-logic
[Widgets] Tweak cClass logic
2023-07-04 12:02:11 -05:00
Trevor Buckner
b9aaee43c2 tweak cClass logic 2023-07-04 03:31:21 -04:00
Trevor Buckner
35a74b3e46 clean up codeEditor 2023-07-03 17:16:29 -04:00
Charlie
f4fe08f8fd Merge pull request #2906 from naturalcrit/SimplifyMouseclickToggle 2023-07-03 15:58:47 -05:00
Trevor Buckner
65c0c81984 Merge branch 'master' into editor-widgets 2023-07-03 16:35:41 -04:00
Trevor Buckner
84496f51ba Merge pull request #2904 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.14.1
Bump react-router-dom from 6.14.0 to 6.14.1
2023-07-03 15:48:45 -04:00
Trevor Buckner
4cf659e711 Merge pull request #2903 from naturalcrit/dependabot/npm_and_yarn/eslint-8.44.0
Bump eslint from 8.43.0 to 8.44.0
2023-07-03 15:48:38 -04:00
Trevor Buckner
712f0309e9 Simplify click-outside close widget logic 2023-07-03 15:27:18 -04:00
dependabot[bot]
e0bfef5231 Bump react-router-dom from 6.14.0 to 6.14.1
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.14.0 to 6.14.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.14.1/packages/react-router-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 03:25:11 +00:00
dependabot[bot]
afb6962407 Bump eslint from 8.43.0 to 8.44.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.43.0 to 8.44.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/v8.43.0...v8.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 03:24:46 +00:00
Charlie Humphreys
b7be2d6463 fix ref issues and remove unneeded value 2023-06-30 00:37:20 -05:00
Charlie Humphreys
47c84d9f01 update widgets - add hints component and adjust autocomplete logic 2023-06-30 00:18:23 -05:00
Trevor Buckner
8d2945ee5c Fix changelog.md typos 2023-06-29 20:25:57 -04:00
G.Ambatte
1dad009298 Merge branch 'experimentalClickToOpen-#2702' of https://github.com/G-Ambatte/homebrewery into experimentalClickToOpen-#2702 2023-06-30 07:57:38 +12:00
G.Ambatte
aadf663623 Add scrollbar on dropdowns 2023-06-30 07:57:33 +12:00
G.Ambatte
8685f32b49 Merge branch 'master' into experimentalClickToOpen-#2702 2023-06-30 00:03:35 +12:00
G.Ambatte
678ac90cd0 Shift Recent Items to use Nav.dropdown 2023-06-29 23:58:52 +12:00
G.Ambatte
3cb5e8ed42 Initial functionality pass 2023-06-29 21:59:42 +12:00
G.Ambatte
273f0ca05d Merge branch 'master' into addStyleSanitization-#1437 2023-06-29 13:26:21 +12:00
G.Ambatte
3c929870cb Merge branch 'master' into addSmartPageBreak-#2289 2023-06-29 13:25:11 +12:00
Trevor Buckner
36df5a3212 Merge pull request #2899 from naturalcrit/v3.9.1
Up version to v3.9.1
2023-06-28 16:13:14 -04:00
Trevor Buckner
cea5f2e43a Up version to v3.9.1 2023-06-28 16:13:01 -04:00
Trevor Buckner
046845885d Merge pull request #2892 from G-Ambatte/experimentalErrorPage
Basic ErrorPage functionality
2023-06-28 16:05:23 -04:00
G.Ambatte
9713cc4be9 Merge branch 'master' into experimentalErrorPage 2023-06-27 15:35:28 +12:00
Trevor Buckner
8baf0fc849 Add additional test for when logged in, but not in author list 2023-06-26 23:26:59 -04:00
Sean Robertson
a7040e554a Fix test 2023-06-27 15:19:33 +12:00
Trevor Buckner
ba43055f32 another text tweak 2023-06-26 23:14:08 -04:00
Trevor Buckner
d0de7ca28c Rephrasing of error texts 2023-06-26 22:48:58 -04:00
Trevor Buckner
c0164dce6a Fix for username undefined (not logged in) 2023-06-26 17:02:28 -04:00
Trevor Buckner
9e2b8477a8 Merge pull request #2898 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.14.0
Bump react-router-dom from 6.13.0 to 6.14.0
2023-06-26 16:44:09 -04:00
Trevor Buckner
5a32ae5cd4 Merge pull request #2897 from naturalcrit/dependabot/npm_and_yarn/stylelint-15.9.0
Bump stylelint from 15.8.0 to 15.9.0
2023-06-26 16:44:00 -04:00
G.Ambatte
e88e7f852c Add account check to Google File not found error 2023-06-26 20:40:11 +12:00
G.Ambatte
a3b2c6987f Fix test 2023-06-26 18:08:52 +12:00
G.Ambatte
3d47b5a0bc Fix test 2023-06-26 18:06:37 +12:00
G.Ambatte
c5f4793c23 Add owner info to missing Google file message 2023-06-26 17:43:19 +12:00
dependabot[bot]
10e14bfcfd Bump react-router-dom from 6.13.0 to 6.14.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.13.0 to 6.14.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.14.0/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.14.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>
2023-06-26 03:57:17 +00:00
dependabot[bot]
f3c36ffb0a Bump stylelint from 15.8.0 to 15.9.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.8.0 to 15.9.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/15.8.0...15.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 03:56:53 +00:00
G.Ambatte
cff4f8eae5 Catch duplicate delete requests 2023-06-25 21:26:02 +12:00
G.Ambatte
4799e8b443 Fix test 2023-06-25 21:10:28 +12:00
G.Ambatte
fa38d5c892 Additional info in errors 2023-06-25 20:39:36 +12:00
G.Ambatte
04eb7d0556 Add brew title to Not an Author page 2023-06-25 20:13:13 +12:00
G.Ambatte
f175323221 Use common error object to reduce DRY 2023-06-25 18:22:22 +12:00
G.Ambatte
9f4de3c66e Fix test 2023-06-25 18:16:57 +12:00
G.Ambatte
800bff611a Fix test 2023-06-25 18:14:12 +12:00
G.Ambatte
e28b6e7a19 Differentiate Not an Author from Not logged in 2023-06-25 18:10:31 +12:00
G.Ambatte
4c6de90d82 Fix test 2023-06-25 17:13:37 +12:00
G.Ambatte
e5ef0aedd3 Pass all error properties to message generator 2023-06-25 17:10:25 +12:00
G.Ambatte
da8e7ec610 Change Not an Author to 401 2023-06-25 16:51:17 +12:00
G.Ambatte
d1412abe03 Increase specificity of ErrorPage.less 2023-06-25 16:48:50 +12:00
G.Ambatte
9de4a82977 Remove unneeded HR 2023-06-25 15:24:10 +12:00
G.Ambatte
9ddae7bbea Update UIPage.less to increase specificity 2023-06-25 15:23:58 +12:00
G.Ambatte
4fdc6b79ea Use Less var to not break server build process 2023-06-25 15:11:12 +12:00
G.Ambatte
0001cf16d9 Change UIPage width calculation 2023-06-25 15:00:10 +12:00
G.Ambatte
438cb7f26d Fix test 2023-06-25 14:41:15 +12:00
G.Ambatte
ffa240f78d Fix test 2023-06-25 14:39:42 +12:00
G.Ambatte
782aa8e658 Increase ESLint max lines in app.js 2023-06-25 14:23:14 +12:00
G.Ambatte
7efe8964f1 Change throw method, update HBErrors 2023-06-25 14:21:35 +12:00
G.Ambatte
853515e09e Switch Error gen from spread operator to func 2023-06-25 12:56:22 +12:00
Trevor Buckner
f6c5354ce0 Add support for custom HBErrorCodes 2023-06-24 02:57:03 -04:00
Trevor Buckner
6353341738 styleLint 2023-06-24 01:50:57 -04:00
Trevor Buckner
66b9a792e7 Change Missing Google error text 2023-06-24 01:47:07 -04:00
Trevor Buckner
2775614eab Add styling to errorPage 2023-06-24 01:46:44 -04:00
Trevor Buckner
32229c6e6e Change json file to js so we can use multiline strings 2023-06-24 00:40:10 -04:00
Trevor Buckner
37c88b83f1 Don't redirect/use cookies. Just render page. 2023-06-23 17:24:31 -04:00
Trevor Buckner
2fa1b2bb8b Break out page rendering into function 2023-06-23 16:29:17 -04:00
G.Ambatte
949d763e35 Detect heading from Markdown Lexer tokens 2023-06-23 17:58:54 +12:00
G.Ambatte
46cb2e6b5b Merge branch 'master' into addStyleSanitization-#1437 2023-06-23 09:44:05 +12:00
G.Ambatte
e7224e97ef Merge branch 'master' into addSmartPageBreak-#2289 2023-06-23 09:43:24 +12:00
G.Ambatte
e07d53aa5f Merge branch 'master' into experimentalErrorPage 2023-06-23 09:42:04 +12:00
Trevor Buckner
1e004977be Merge pull request #2895 from naturalcrit/dependabot/npm_and_yarn/npm-9.7.2
Bump npm from 9.7.1 to 9.7.2
2023-06-22 15:29:24 -04:00
Trevor Buckner
9110e7cf7e Merge pull request #2894 from naturalcrit/dependabot/npm_and_yarn/mongoose-7.3.1
Bump mongoose from 7.3.0 to 7.3.1
2023-06-22 15:29:17 -04:00
dependabot[bot]
27e8b54528 Bump npm from 9.7.1 to 9.7.2
Bumps [npm](https://github.com/npm/cli) from 9.7.1 to 9.7.2.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v9.7.1...v9.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 19:11:53 +00:00
dependabot[bot]
aa31919563 Bump mongoose from 7.3.0 to 7.3.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 7.3.0 to 7.3.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/7.3.0...7.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 19:11:27 +00:00
Trevor Buckner
7bf3295fc2 Merge pull request #2893 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-jest-27.2.2
Bump eslint-plugin-jest from 27.2.1 to 27.2.2
2023-06-22 15:10:31 -04:00
dependabot[bot]
9fd3f47689 Bump eslint-plugin-jest from 27.2.1 to 27.2.2
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 27.2.1 to 27.2.2.
- [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/v27.2.1...v27.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 18:33:26 +00:00
Trevor Buckner
0ca7e43d73 Merge pull request #2896 from naturalcrit/extractMarkedSmartypantsIntoPackage
extract smartypants into package
2023-06-22 14:32:01 -04:00
Trevor Buckner
b33b3cd49b extract smartypants into package 2023-06-22 14:31:14 -04:00
G.Ambatte
71c384ee0b Fix tests 2023-06-20 16:45:36 +12:00
G.Ambatte
546b8d5725 Merge branch 'experimentalErrorPage' of https://github.com/G-Ambatte/homebrewery into experimentalErrorPage 2023-06-20 13:45:04 +12:00
G.Ambatte
4d6ac2b142 Update ErrorPage to use basePage/UIPage 2023-06-20 13:45:01 +12:00
G.Ambatte
ce538ebbfd Add errorIndex.json 2023-06-20 13:44:41 +12:00
G.Ambatte
cf17e73dfa Merge branch 'master' into experimentalErrorPage 2023-06-20 11:20:02 +12:00
Trevor Buckner
69ef4d7653 Merge pull request #2888 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.13.0
Bump react-router-dom from 6.12.1 to 6.13.0
2023-06-19 11:03:13 -04:00
Trevor Buckner
c98224f3e4 Merge pull request #2887 from naturalcrit/dependabot/npm_and_yarn/mongoose-7.3.0
Bump mongoose from 7.2.3 to 7.3.0
2023-06-19 11:03:03 -04:00
Trevor Buckner
4f870de68f Merge pull request #2891 from naturalcrit/dependabot/npm_and_yarn/eslint-8.43.0
Bump eslint from 8.42.0 to 8.43.0
2023-06-19 11:02:54 -04:00
Trevor Buckner
2cfee2e8ad Merge pull request #2890 from naturalcrit/dependabot/npm_and_yarn/stylelint-15.8.0
Bump stylelint from 15.7.0 to 15.8.0
2023-06-19 11:02:46 -04:00
G.Ambatte
9e1d53a30c Basic ErrorPage functionality 2023-06-19 21:06:38 +12:00
dependabot[bot]
1fe9f0c8d0 Bump eslint from 8.42.0 to 8.43.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.42.0 to 8.43.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/v8.42.0...v8.43.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 03:57:15 +00:00
dependabot[bot]
adc7233cab Bump stylelint from 15.7.0 to 15.8.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.7.0 to 15.8.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/15.7.0...15.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 03:56:34 +00:00
G.Ambatte
1b2fc746d3 Remove script check from basic Markdown tests 2023-06-17 22:45:09 +12:00
G.Ambatte
b472fc1115 Move script tag sanitization to BrewRenderer 2023-06-17 20:25:15 +12:00
G.Ambatte
a7a47afaae Merge branch 'master' into addStyleSanitization-#1437 2023-06-17 20:09:45 +12:00
dependabot[bot]
509c7d8832 Bump react-router-dom from 6.12.1 to 6.13.0
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.12.1 to 6.13.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.13.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>
2023-06-15 03:57:17 +00:00
dependabot[bot]
caff1d8e2b Bump mongoose from 7.2.3 to 7.3.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 7.2.3 to 7.3.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/7.2.3...7.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-15 03:57:03 +00:00
G.Ambatte
e06f5e17d9 Relocate from 5ePHB to Blank theme 2023-06-15 13:42:10 +12:00
G.Ambatte
ade61971d0 Remove snippet.params 2023-06-15 11:50:38 +12:00
Trevor Buckner
6451d79d92 Merge branch 'master' into pr/2417 2023-06-12 22:26:10 -04:00
Trevor Buckner
9202f9c8eb Merge pull request #2881 from naturalcrit/dependabot/npm_and_yarn/marked-5.1.0
Bump marked from 5.0.5 to 5.1.0
2023-06-12 16:42:18 -04:00
Trevor Buckner
097cc220f8 Merge pull request #2882 from naturalcrit/dependabot/npm_and_yarn/mongoose-7.2.3
Bump mongoose from 7.2.2 to 7.2.3
2023-06-12 16:42:07 -04:00
dependabot[bot]
2e3c10c35b Bump mongoose from 7.2.2 to 7.2.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 7.2.2 to 7.2.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/7.2.2...7.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 03:57:24 +00:00
dependabot[bot]
a5aeb7dccd Bump marked from 5.0.5 to 5.1.0
Bumps [marked](https://github.com/markedjs/marked) from 5.0.5 to 5.1.0.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v5.0.5...v5.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 03:56:39 +00:00
Charlie Humphreys
b6d37dd825 add the concept of widgets and widget fields 2023-06-10 13:02:13 -05:00
Trevor Buckner
92ff776270 Merge pull request #2877 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.22.5
Bump @babel/plugin-transform-runtime from 7.22.4 to 7.22.5
2023-06-09 11:11:20 -04:00
dependabot[bot]
bf1fb97789 Bump @babel/plugin-transform-runtime from 7.22.4 to 7.22.5
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.22.4 to 7.22.5.
- [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.22.5/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>
2023-06-09 15:10:52 +00:00
Trevor Buckner
2cddc2debe Merge pull request #2876 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.22.5
Bump @babel/preset-env from 7.22.4 to 7.22.5
2023-06-09 11:09:42 -04:00
dependabot[bot]
0d4a1a11c1 Bump @babel/preset-env from 7.22.4 to 7.22.5
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.22.4 to 7.22.5.
- [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.22.5/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>
2023-06-09 15:04:42 +00:00
Trevor Buckner
aa3cf1d9c1 Merge pull request #2875 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.22.5
Bump @babel/core from 7.22.1 to 7.22.5
2023-06-09 11:02:39 -04:00
Trevor Buckner
5d0062f610 Merge pull request #2874 from naturalcrit/dependabot/npm_and_yarn/react-router-dom-6.12.1
Bump react-router-dom from 6.11.2 to 6.12.1
2023-06-09 11:02:28 -04:00
Trevor Buckner
7976917bb9 Merge pull request #2873 from naturalcrit/dependabot/npm_and_yarn/babel/preset-react-7.22.5
Bump @babel/preset-react from 7.22.3 to 7.22.5
2023-06-09 11:02:16 -04:00
Trevor Buckner
023071c874 Merge pull request #2869 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recess-order-4.2.0
Bump stylelint-config-recess-order from 4.0.0 to 4.2.0
2023-06-09 11:01:03 -04:00
dependabot[bot]
7da42d3742 Bump stylelint-config-recess-order from 4.0.0 to 4.2.0
Bumps [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order) from 4.0.0 to 4.2.0.
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v4.0.0...v4.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-09 15:00:36 +00:00
Trevor Buckner
3269e94757 Merge pull request #2872 from naturalcrit/dependabot/npm_and_yarn/npm-9.7.1
Bump npm from 9.6.7 to 9.7.1
2023-06-09 10:59:26 -04:00
Trevor Buckner
c69f4289ed Merge pull request #2867 from naturalcrit/dependabot/npm_and_yarn/stylelint-15.7.0
Bump stylelint from 15.6.3 to 15.7.0
2023-06-09 10:59:13 -04:00
Trevor Buckner
8752a32626 Merge pull request #2863 from naturalcrit/dependabot/npm_and_yarn/marked-gfm-heading-id-3.0.4
Bump marked-gfm-heading-id from 3.0.3 to 3.0.4
2023-06-09 10:58:54 -04:00
Trevor Buckner
8735d1f222 Merge pull request #2862 from naturalcrit/dependabot/npm_and_yarn/eslint-8.42.0
Bump eslint from 8.41.0 to 8.42.0
2023-06-09 10:58:42 -04:00
Trevor Buckner
21929e676d Merge pull request #2871 from naturalcrit/dependabot/npm_and_yarn/marked-5.0.5
Bump marked from 5.0.4 to 5.0.5
2023-06-09 10:58:30 -04:00
dependabot[bot]
5ca61935a8 Bump @babel/core from 7.22.1 to 7.22.5
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.22.1 to 7.22.5.
- [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.22.5/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>
2023-06-09 03:57:54 +00:00
dependabot[bot]
10143cec93 Bump react-router-dom from 6.11.2 to 6.12.1
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.11.2 to 6.12.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.12.1/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>
2023-06-09 03:57:22 +00:00
dependabot[bot]
643c8503c0 Bump @babel/preset-react from 7.22.3 to 7.22.5
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.22.3 to 7.22.5.
- [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.22.5/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>
2023-06-09 03:57:04 +00:00
dependabot[bot]
e92d3ecd68 Bump npm from 9.6.7 to 9.7.1
Bumps [npm](https://github.com/npm/cli) from 9.6.7 to 9.7.1.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v9.6.7...v9.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 03:57:47 +00:00
dependabot[bot]
4f092828ac Bump marked from 5.0.4 to 5.0.5
Bumps [marked](https://github.com/markedjs/marked) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v5.0.4...v5.0.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 03:57:08 +00:00
dependabot[bot]
2d4c211483 Bump stylelint from 15.6.3 to 15.7.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.6.3 to 15.7.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/15.6.3...15.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-06 03:56:50 +00:00
Trevor Buckner
7d30abc4d9 Merge pull request #2865 from G-Ambatte/bumpNodeInDocker-#2861
[Docker] - Bump Node from 16.13 to 18
2023-06-05 07:47:29 -04:00
G.Ambatte
1d513f7a0e Bump Node from 16.13 to 18 2023-06-05 20:33:46 +12:00
Trevor Buckner
44922f5261 Merge pull request #2860 from G-Ambatte/fixTestFailure-#2859
Fix test failure
2023-06-05 00:17:05 -04:00
dependabot[bot]
f695cc6948 Bump marked-gfm-heading-id from 3.0.3 to 3.0.4
Bumps [marked-gfm-heading-id](https://github.com/markedjs/marked-gfm-heading-id) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/markedjs/marked-gfm-heading-id/releases)
- [Changelog](https://github.com/markedjs/marked-gfm-heading-id/blob/main/release.config.cjs)
- [Commits](https://github.com/markedjs/marked-gfm-heading-id/compare/v3.0.3...v3.0.4)

---
updated-dependencies:
- dependency-name: marked-gfm-heading-id
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 04:13:33 +00:00
dependabot[bot]
3722387f1f Bump eslint from 8.41.0 to 8.42.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.41.0 to 8.42.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/v8.41.0...v8.42.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 04:13:30 +00:00
Trevor Buckner
8950cb944f Merge pull request #2864 from naturalcrit/dependabot/npm_and_yarn/stylelint-15.6.3
Bump stylelint from 15.6.2 to 15.6.3
2023-06-05 00:12:10 -04:00
dependabot[bot]
66fb70a5f8 Bump stylelint from 15.6.2 to 15.6.3
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.6.2 to 15.6.3.
- [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/15.6.2...15.6.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 03:57:36 +00:00
G.Ambatte
69c242425b Update test output 2023-06-03 14:20:45 +12:00
G.Ambatte
9093f610bd Add params from snippet to function 2023-06-03 14:09:50 +12:00
G.Ambatte
d2b2e69123 Shift Footer generation to snippet 2023-06-03 13:30:32 +12:00
G.Ambatte
052c255068 Lint fixes 2023-06-03 13:23:39 +12:00
G.Ambatte
e6ad8aefde Lint fix 2023-06-03 12:18:22 +12:00
G.Ambatte
43ae80e80d Merge branch 'master' into addSmartPageBreak-#2289 2023-06-03 12:16:18 +12:00
Trevor Buckner
e6e04ad21d Fix changelog.md typos 2023-06-02 17:48:01 -04:00
Trevor Buckner
7be6b913b0 Merge pull request #2858 from naturalcrit/v3.9.0
Up version to v3.9.0
2023-06-02 17:14:05 -04:00
G.Ambatte
c30eb9056a Break up text generation for legibility 2022-10-05 22:11:03 +13:00
G.Ambatte
ee3cd21486 Add to base snippets 2022-10-05 21:29:49 +13:00
G.Ambatte
5de63799c2 Update default footnote text 2022-10-05 21:20:39 +13:00
G.Ambatte
dfa8aea1ec Add newPageWithFooter function 2022-10-05 20:37:05 +13:00
G.Ambatte
8e26161244 Add sanitization of Style content 2022-09-28 17:27:11 +13:00
50 changed files with 19351 additions and 18038 deletions

View File

@@ -15,7 +15,7 @@ module.exports = {
rules : {
/** Errors **/
'camelcase' : ['error', { properties: 'never' }],
'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
//'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
'no-array-constructor' : 'error',
'no-iterator' : 'error',
'no-nested-ternary' : 'error',

View File

@@ -1,4 +1,4 @@
FROM node:16.13-alpine
FROM node:18-alpine
RUN apk --no-cache add git
ENV NODE_ENV=docker

View File

@@ -80,6 +80,16 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Wednesday 28/06/2023 - v3.9.1
{{taskList
##### G-Ambatte
* [x] Better error pages with more useful information
Fixes issue [#1924](https://github.com/naturalcrit/homebrewery/issues/1924)
}}
### Friday 02/06/2023 - v3.9.0
{{taskList
@@ -101,7 +111,7 @@ Fixes issues [#2092](https://github.com/naturalcrit/homebrewery/issues/2092), [#
Fixes issue [#2790](https://github.com/naturalcrit/homebrewery/issues/2790)
##### 5e-Clerc
##### 5e-Cleric
* [x] New {{openSans **PHB → {{fac,book-part-cover}} PART COVER PAGE** }} snippet for V3!
@@ -145,7 +155,7 @@ Fixes issues [#1679](https://github.com/naturalcrit/homebrewery/issues/1679)
* [x] Add local Windows install script via Chocolatey
##### 5e-Clerc
##### 5e-Cleric
* [x] New {{openSans **TABLES → {{fas,fa-language}} RUNE TABLE**}} snippets for V3. Adds an alphabetic script translation table.

View File

@@ -108,6 +108,12 @@ const BrewRenderer = createClass({
return false;
},
sanitizeScriptTags : function(content) {
return content
.replace(/<script/ig, '&lt;script')
.replace(/<\/script>/ig, '&lt;/script&gt;');
},
renderPageInfo : function(){
return <div className='pageInfo' ref='main'>
<div>
@@ -135,18 +141,20 @@ const BrewRenderer = createClass({
renderStyle : function() {
if(!this.props.style) return;
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.props.style}\n} </style>` }} />;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>\n${this.props.style}\n</style>` }} />;
const cleanStyle = this.sanitizeScriptTags(this.props.style);
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.sanitizeScriptTags(this.props.style)}\n} </style>` }} />;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${cleanStyle} </style>` }} />;
},
renderPage : function(pageText, index){
const cleanPageText = this.sanitizeScriptTags(pageText);
if(this.props.renderer == 'legacy')
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }} key={index} />;
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(cleanPageText) }} key={index} />;
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)
return (
<div className='page' id={`p${index + 1}`} key={index} >
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} />
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(cleanPageText) }} />
</div>
);
}
@@ -185,6 +193,12 @@ const BrewRenderer = createClass({
}, 100);
},
emitClick : function(){
// console.log('iFrame clicked');
if(!window || !document) return;
document.dispatchEvent(new MouseEvent('click'));
},
render : function(){
//render in iFrame so broken code doesn't crash the site.
//Also render dummy page while iframe is mounting.
@@ -203,7 +217,9 @@ const BrewRenderer = createClass({
<Frame id='BrewRenderer' initialContent={this.state.initialContent}
style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
contentDidMount={this.frameDidMount}>
contentDidMount={this.frameDidMount}
onClick={()=>{this.emitClick();}}
>
<div className={'brewRenderer'}
onScroll={this.handleScroll}
style={{ height: this.state.height }}>

View File

@@ -269,7 +269,7 @@ const Editor = createClass({
view={this.state.view}
value={this.props.brew.text}
onChange={this.props.onTextChange}
rerenderParent={this.rerenderParent} />
rerenderParent={this.rerenderParent}/>
</>;
}
if(this.isStyle()){
@@ -323,7 +323,8 @@ const Editor = createClass({
theme={this.props.brew.theme}
undo={this.undo}
redo={this.redo}
historySize={this.historySize()} />
historySize={this.historySize()}
cursorPos={this.refs.codeEditor?.getCursorPosition() || {}} />
{this.renderEditor()}
</div>

View File

@@ -15,8 +15,8 @@ ThemeSnippets['V3_5eDMG'] = require('themes/V3/5eDMG/snippets.js');
ThemeSnippets['V3_Journal'] = require('themes/V3/Journal/snippets.js');
ThemeSnippets['V3_Blank'] = require('themes/V3/Blank/snippets.js');
const execute = function(val, brew){
if(_.isFunction(val)) return val(brew);
const execute = function(val, props){
if(_.isFunction(val)) return val(props);
return val;
};
@@ -33,7 +33,8 @@ const Snippetbar = createClass({
renderer : 'legacy',
undo : ()=>{},
redo : ()=>{},
historySize : ()=>{}
historySize : ()=>{},
cursorPos : {}
};
},
@@ -105,6 +106,7 @@ const Snippetbar = createClass({
snippets={snippetGroup.snippets}
key={snippetGroup.groupName}
onSnippetClick={this.handleSnippetClick}
cursorPos={this.props.cursorPos}
/>;
});
},
@@ -165,7 +167,7 @@ const SnippetGroup = createClass({
},
handleSnippetClick : function(e, snippet){
e.stopPropagation();
this.props.onSnippetClick(execute(snippet.gen, this.props.brew));
this.props.onSnippetClick(execute(snippet.gen, this.props));
},
renderSnippets : function(snippets){
return _.map(snippets, (snippet)=>{

View File

@@ -9,7 +9,7 @@ const EditPage = require('./pages/editPage/editPage.jsx');
const UserPage = require('./pages/userPage/userPage.jsx');
const SharePage = require('./pages/sharePage/sharePage.jsx');
const NewPage = require('./pages/newPage/newPage.jsx');
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
const ErrorPage = require('./pages/errorPage/errorPage.jsx');
const PrintPage = require('./pages/printPage/printPage.jsx');
const AccountPage = require('./pages/accountPage/accountPage.jsx');
@@ -78,6 +78,7 @@ const Homebrew = createClass({
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} uiItems={this.props.brew.uiItems} />} />
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
<Route path='/error' element={<WithRoute el={ErrorPage} brew={this.props.brew} />} />
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
</Routes>

View File

@@ -81,20 +81,70 @@
color : pink;
}
}
.recent.navItem {
.recent.navDropdownContainer {
position : relative;
.dropdown {
position : absolute;
z-index : 10000;
top : 28px;
left : 0;
.navDropdown .navItem {
overflow : hidden auto;
width : 100%;
max-height : ~"calc(100vh - 28px)";
scrollbar-color : #666 #333;
scrollbar-width : thin;
h4 {
font-size : 0.8em;
#backgroundColorsHover;
.animate(background-color);
position : relative;
display : block;
overflow : clip;
box-sizing : border-box;
padding : 8px 5px 13px;
text-decoration : none;
color : white;
border-top : 1px solid #888;
background-color : #333;
.clear {
position : absolute;
top : 50%;
right : 0;
display : none;
width : 20px;
height : 100%;
transform : translateY(-50%);
opacity : 70%;
border-radius : 3px;
background-color : #333;
&:hover {
opacity : 100%;
}
i {
font-size : 10px;
width : 100%;
height : 100%;
margin : 0;
text-align : center;
}
}
&:hover {
background-color : @blue;
.clear {
display : grid;
place-content : center;
}
}
.title {
display : inline-block;
overflow : hidden;
width : 100%;
white-space : nowrap;
text-overflow : ellipsis;
}
.time {
font-size : 0.7em;
position : absolute;
right : 2px;
bottom : 2px;
color : #888;
}
&.header {
display : block;
box-sizing : border-box;
padding : 5px 0;
@@ -109,62 +159,6 @@
background-color : darken(@purple, 30%);
}
}
.item {
#backgroundColorsHover;
.animate(background-color);
position : relative;
display : block;
overflow : clip;
box-sizing : border-box;
padding : 8px 5px 13px;
text-decoration : none;
color : white;
border-top : 1px solid #888;
background-color : #333;
.clear {
position : absolute;
top : 50%;
right : 0;
display : none;
width : 20px;
height : 100%;
transform : translateY(-50%);
opacity : 70%;
border-radius : 3px;
background-color : #333;
&:hover {
opacity : 100%;
}
i {
font-size : 10px;
width : 100%;
height : 100%;
margin : 0;
text-align : center;
}
}
&:hover {
background-color : @blue;
.clear {
display : grid;
place-content : center;
}
}
.title {
display : inline-block;
overflow : hidden;
width : 100%;
white-space : nowrap;
text-overflow : ellipsis;
}
.time {
font-size : 0.7em;
position : absolute;
right : 2px;
bottom : 2px;
color : #888;
}
}
}
}
.metadata.navItem {

View File

@@ -121,6 +121,7 @@ const RecentItems = createClass({
removeItem : function(url, evt){
evt.preventDefault();
evt.stopPropagation();
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
let viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
@@ -139,11 +140,11 @@ const RecentItems = createClass({
},
renderDropdown : function(){
if(!this.state.showDropdown) return null;
// if(!this.state.showDropdown) return null;
const makeItems = (brews)=>{
return _.map(brews, (brew, i)=>{
return <a href={brew.url} className='item' key={`${brew.id}-${i}`} target='_blank' rel='noopener noreferrer' title={brew.title || '[ no title ]'}>
return <a className='navItem' href={brew.url} key={`${brew.id}-${i}`} target='_blank' rel='noopener noreferrer' title={brew.title || '[ no title ]'}>
<span className='title'>{brew.title || '[ no title ]'}</span>
<span className='time'>{Moment(brew.ts).fromNow()}</span>
<div className='clear' title='Remove from Recents' onClick={(e)=>{this.removeItem(`${brew.url}`, e);}}><i className='fas fa-times'></i></div>
@@ -151,25 +152,25 @@ const RecentItems = createClass({
});
};
return <div className='dropdown'>
return <>
{(this.props.showEdit && this.props.showView) ?
<h4>edited</h4> : null }
<Nav.item className='header'>edited</Nav.item> : null }
{this.props.showEdit ?
makeItems(this.state.edit) : null }
{(this.props.showEdit && this.props.showView) ?
<h4>viewed</h4> : null }
<Nav.item className='header'>viewed</Nav.item> : null }
{this.props.showView ?
makeItems(this.state.view) : null }
</div>;
</>;
},
render : function(){
return <Nav.item icon='fas fa-history' color='grey' className='recent'
onMouseEnter={()=>this.handleDropdown(true)}
onMouseLeave={()=>this.handleDropdown(false)}>
{this.props.text}
return <Nav.dropdown className='recent'>
<Nav.item icon='fas fa-history' color='grey' >
{this.props.text}
</Nav.item>
{this.renderDropdown()}
</Nav.item>;
</Nav.dropdown>;
}
});

View File

@@ -1,47 +1,52 @@
.uiPage{
.content{
overflow-y : hidden;
width : 90vw;
background-color: #f0f0f0;
font-family: 'Open Sans';
margin-left: auto;
margin-right: auto;
margin-top: 25px;
padding: 2% 4%;
font-size: 0.8em;
line-height: 1.8em;
.dataGroup{
padding: 6px 20px 15px;
border: 2px solid black;
border-radius: 5px;
margin: 5px 0px;
}
h1, h2, h3, h4{
font-weight: 900;
text-transform: uppercase;
margin: 0.5em 30% 0.25em 0;
border-bottom: 2px solid slategrey;
}
h1 {
font-size: 2em;
border-bottom: 2px solid darkslategrey;
margin-bottom: 0.5em;
margin-right: 0;
}
h2 {
font-size: 1.75em;
}
h3 {
font-size: 1.5em;
svg {
width: 19px;
.homebrew {
.uiPage.sitePage {
.content {
width : ~"min(90vw, 1000px)";
padding : 2% 4%;
margin-top : 25px;
margin-right : auto;
margin-left : auto;
overflow-y : scroll;
font-family : 'Open Sans';
font-size : 0.8em;
line-height : 1.8em;
background-color : #F0F0F0;
.dataGroup {
padding : 6px 20px 15px;
margin : 5px 0px;
border : 2px solid black;
border-radius : 5px;
}
h1, h2, h3, h4 {
width : 100%;
margin : 0.5em 30% 0.25em 0;
font-weight : 900;
text-transform : uppercase;
border-bottom : 2px solid slategrey;
}
h1 {
margin-right : 0;
margin-bottom : 0.5em;
font-size : 2em;
border-bottom : 2px solid darkslategrey;
}
h2 { font-size : 1.75em; }
h3 {
font-size : 1.5em;
svg { width : 19px; }
}
h4 { font-size : 1.25em; }
strong { font-weight : bold; }
em { font-style : italic; }
ul {
padding-left : 1.25em;
list-style : square;
}
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}
h4 {
font-size: 1.25em;
}
strong {
font-weight: bold;
}
}
}
}

View File

@@ -4,44 +4,37 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const UIPage = require('../basePages/uiPage/uiPage.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const Markdown = require('../../../../shared/naturalcrit/markdown.js');
const ErrorIndex = require('./errors/errorIndex.js');
const ErrorPage = createClass({
displayName : 'ErrorPage',
getDefaultProps : function() {
return {
ver : '0.0.0',
errorId : ''
errorId : '',
text : '# Oops \n We could not find a brew with that id. **Sorry!**',
error : {}
};
},
text : '# Oops \n We could not find a brew with that id. **Sorry!**',
render : function(){
return <div className='errorPage sitePage'>
<Navbar ver={this.props.ver}>
<Nav.section>
<Nav.item className='errorTitle'>
Crit Fail!
</Nav.item>
</Nav.section>
const errorText = ErrorIndex(this.props)[this.props.brew.HBErrorCode.toString()] || '';
<Nav.section>
<PatreonNavItem />
<HelpNavItem />
<RecentNavItem />
</Nav.section>
</Navbar>
<div className='content'>
<BrewRenderer text={this.text} />
return <UIPage brew={{ title: 'Crit Fail!' }}>
<div className='dataGroup'>
<div className='errorTitle'>
<h1>{`Error ${this.props.brew.status || '000'}`}</h1>
<h4>{this.props.brew.text || 'No error text'}</h4>
</div>
<hr />
<div dangerouslySetInnerHTML={{ __html: Markdown.render(errorText) }} />
</div>
</div>;
</UIPage>;
}
});

View File

@@ -1,5 +1,13 @@
.errorPage{
.errorTitle{
background-color: @orange;
.homebrew {
.uiPage.sitePage {
.errorTitle {
//background-color: @orange;
color : #D02727;
text-align : center;
}
.content {
h1, h2, h3, h4 { border-bottom : none; }
hr { border-bottom : 2px solid slategrey; }
}
}
}

View File

@@ -0,0 +1,126 @@
const dedent = require('dedent-tabs').default;
const loginUrl = 'https://www.naturalcrit.com/login';
const errorIndex = (props)=>{
return {
// Default catch all
'00' : dedent`
## An unknown error occurred!
We aren't sure what happened, but our server wasn't able to find what you
were looking for.`,
// General Google load error
'01' : dedent`
## An error occurred while retrieving this brew from Google Drive!
Google reported an error while attempting to retrieve a brew from this link.`,
// Google Drive - 404 : brew deleted or access denied
'02' : dedent`
## We can't find this brew in Google Drive!
This file was saved on Google Drive, but this link doesn't work anymore.
${ props.brew.authors?.length > 0
? `Note that this brew belongs to the Homebrewery account **${ props.brew.authors[0] }**,
${ props.brew.account
? `which is
${props.brew.authors[0] == props.brew.account
? `your account.`
: `not your account (you are currently signed in as **${props.brew.account}**).`
}`
: 'and you are not currently signed in to any account.'
}`
: ''
}
The Homebrewery cannot delete files from Google Drive on its own, so there
are three most likely possibilities:
:
- **The Google Drive files may have been accidentally deleted.** Look in
the Google Drive account that owns this brew (or ask the owner to do so),
and make sure the Homebrewery folder is still there, and that it holds your brews
as text files.
- **You may have changed the sharing settings for your files.** If the files
are still on Google Drive, change all of them to be shared *with everyone who has
the link* so the Homebrewery can access them.
- **The Google Account may be closed.** Google may have removed the account
due to inactivity or violating a Google policy. Make sure the owner can
still access Google Drive normally and upload/download files to it.
:
If the file isn't found, Google Drive usually puts your file in your Trash folder for
30 days. Assuming the trash hasn't been emptied yet, it might be worth checking.
You can also find the Activity tab on the right side of the Google Drive page, which
shows the recent activity on Google Drive. This can help you pin down the exact date
the brew was deleted or moved, and by whom.
:
If the brew still isn't found, some people have had success asking Google to recover
accidentally deleted files at this link:
https://support.google.com/drive/answer/1716222?hl=en&ref_topic=7000946.
At the bottom of the page there is a button that says *Send yourself an Email*
and you will receive instructions on how to request the files be restored.
:
Also note, if you prefer not to use your Google Drive for storage, you can always
change the storage location of a brew by clicking the Google drive icon by the
brew title and choosing *transfer my brew to/from Google Drive*.`,
// User is not Authors list
'03' : dedent`
## Current signed-in user does not have editor access to this brew.
If you believe you should have access to this brew, ask one of its authors to invite you
as an author by opening the Edit page for the brew, viewing the {{fa,fa-info-circle}}
**Properties** tab, and adding your username to the "invited authors" list. You can
then try to access this document again.
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}`,
// User is not signed in; must be a user on the Authors List
'04' : dedent`
## Sign-in required to edit this brew.
You must be logged in to one of the accounts listed as an author of this brew.
User is not logged in. Please log in [here](${loginUrl}).
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
**Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}`,
// Brew load error
'05' : dedent`
## No Homebrewery document could be found.
The server could not locate the Homebrewery document. It was likely deleted by
its owner.
**Requested access:** ${props.brew.accessType}
**Brew ID:** ${props.brew.brewId}`,
// Brew save error
'06' : dedent`
## Unable to save Homebrewery document.
An error occurred wil attempting to save the Homebrewery document.`,
// Brew delete error
'07' : dedent`
## Unable to delete Homebrewery document.
An error occurred while attempting to remove the Homebrewery document.
**Brew ID:** ${props.brew.brewId}`,
// Author delete error
'08' : dedent`
## Unable to remove user from Homebrewery document.
An error occurred while attempting to remove the user from the Homebrewery document author list!
**Brew ID:** ${props.brew.brewId}`,
};
};
module.exports = errorIndex;

35105
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.9.0",
"version": "3.9.1",
"engines": {
"node": ">=18.16.x"
},
@@ -78,10 +78,10 @@
]
},
"dependencies": {
"@babel/core": "^7.22.1",
"@babel/plugin-transform-runtime": "^7.22.4",
"@babel/preset-env": "^7.22.4",
"@babel/preset-react": "^7.22.3",
"@babel/core": "^7.22.8",
"@babel/plugin-transform-runtime": "^7.22.7",
"@babel/preset-env": "^7.22.7",
"@babel/preset-react": "^7.22.5",
"@googleapis/drive": "^5.1.0",
"body-parser": "^1.20.2",
"classnames": "^2.3.2",
@@ -97,34 +97,35 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "5.0.4",
"marked": "5.1.1",
"marked-extended-tables": "^1.0.6",
"marked-gfm-heading-id": "^3.0.3",
"marked-gfm-heading-id": "^3.0.4",
"marked-smartypants-lite": "^1.0.0",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4",
"mongoose": "^7.2.2",
"mongoose": "^7.3.2",
"nanoid": "3.3.4",
"nconf": "^0.12.0",
"npm": "^9.6.7",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"npm": "^9.8.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-frame-component": "^4.1.3",
"react-router-dom": "6.11.2",
"react-router-dom": "6.14.1",
"sanitize-filename": "1.6.3",
"superagent": "^6.1.0",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"eslint": "^8.41.0",
"eslint-plugin-jest": "^27.2.1",
"eslint": "^8.44.0",
"eslint-plugin-jest": "^27.2.2",
"eslint-plugin-react": "^7.32.2",
"jest": "^29.5.0",
"jest": "^29.6.1",
"jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0",
"stylelint": "^15.6.2",
"stylelint-config-recess-order": "^4.0.0",
"stylelint-config-recommended": "^12.0.0",
"stylelint-stylistic": "^0.4.2",
"stylelint": "^15.10.1",
"stylelint-config-recess-order": "^4.3.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.3"
}
}

View File

@@ -135,12 +135,12 @@ fs.emptyDirSync('./build');
})().catch(console.error);
//In development set up a watch server and livereload
//In development, set up LiveReload (refreshes browser), and Nodemon (restarts server)
if(isDev){
livereload('./build');
watchFile('./server.js', { // Rebuild when change detected to this file or any nested directory from here
ignore : ['./build'], // Ignore ./build or it will rebuild again
ext : 'less', // Other extensions to watch (only .js/.json/.jsx by default)
//watch: ['./client', './server', './themes'], // Watch additional folders if you want
livereload('./build'); // Install the Chrome extension LiveReload to automatically refresh the browser
watchFile('./server.js', { // Restart server when change detected to this file or any nested directory from here
ignore : ['./build', './client', './themes'], // Ignore folders that are not running server code / avoids unneeded restarts
ext : 'js json' // Extensions to watch (only .js/.json by default)
//watch : ['./server', './themes'], // Watch additional folders if needed
});
}

View File

@@ -1,4 +1,4 @@
/*eslint max-lines: ["warn", {"max": 400, "skipBlankLines": true, "skipComments": true}]*/
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
// Set working directory to project root
process.chdir(`${__dirname}/..`);
@@ -324,8 +324,8 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r
};
if(req.params.id.length > 12 && !brew._id) {
const googleId = req.params.id.slice(0, -12);
const shareId = req.params.id.slice(-12);
const googleId = brew.googleId;
const shareId = brew.shareId;
await GoogleActions.increaseView(googleId, shareId, 'share', brew)
.catch((err)=>{next(err);});
} else {
@@ -397,7 +397,6 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
return next();
}));
const nodeEnv = config.get('node_env');
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
// Local only
@@ -414,8 +413,7 @@ if(isLocalEnvironment){
//Render the page
const templateFn = require('./../client/template.js');
app.use(asyncHandler(async (req, res, next)=>{
const renderPage = async (req, res)=>{
// Create configuration object
const configuration = {
local : isLocalEnvironment,
@@ -424,7 +422,7 @@ app.use(asyncHandler(async (req, res, next)=>{
};
const props = {
version : require('./../package.json').version,
url : req.originalUrl,
url : req.customUrl || req.originalUrl,
brew : req.brew,
brews : req.brews,
googleBrews : req.googleBrews,
@@ -438,15 +436,20 @@ app.use(asyncHandler(async (req, res, next)=>{
const page = await templateFn('homebrew', title, props)
.catch((err)=>{
console.log(err);
return res.sendStatus(500);
});
return page;
};
//Send rendered page
app.use(asyncHandler(async (req, res, next)=>{
const page = await renderPage(req, res);
if(!page) return;
res.send(page);
}));
//v=====----- Error-Handling Middleware -----=====v//
//Format Errors so all fields will be sent
const replaceErrors = (key, value)=>{
//Format Errors as plain objects so all fields will appear in the string sent
const formatErrors = (key, value)=>{
if(value instanceof Error) {
const error = {};
Object.getOwnPropertyNames(value).forEach(function (key) {
@@ -458,13 +461,30 @@ const replaceErrors = (key, value)=>{
};
const getPureError = (error)=>{
return JSON.parse(JSON.stringify(error, replaceErrors));
return JSON.parse(JSON.stringify(error, formatErrors));
};
app.use((err, req, res, next)=>{
const status = err.status || 500;
app.use(async (err, req, res, next)=>{
const status = err.status || err.code || 500;
console.error(err);
res.status(status).send(getPureError(err));
req.ogMeta = { ...defaultMetaTags,
title : 'Error Page',
description : 'Something went wrong!'
};
req.brew = {
...err,
title : 'Error - Something went wrong!',
text : err.errors?.map((error)=>{return error.message;}).join('\n\n') || err.message || 'Unknown error!',
status : status,
HBErrorCode : err.HBErrorCode ?? '00',
pureError : getPureError(err)
};
req.customUrl= '/error';
const page = await renderPage(req, res);
if(!page) return;
res.send(page);
});
app.use((req, res)=>{

View File

@@ -100,12 +100,12 @@ const GoogleActions = {
const drive = googleDrive.drive({ version: 'v3', auth });
const fileList = [];
let NextPageToken = "";
let NextPageToken = '';
do {
const obj = await drive.files.list({
pageSize : 1000,
pageToken : NextPageToken || "",
pageToken : NextPageToken || '',
fields : 'nextPageToken, files(id, name, description, createdTime, modifiedTime, properties)',
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
})
@@ -243,9 +243,9 @@ const GoogleActions = {
if(obj) {
if(accessType == 'edit' && obj.data.properties.editId != accessId){
throw ('Edit ID does not match');
throw ({ message: 'Edit ID does not match' });
} else if(accessType == 'share' && obj.data.properties.shareId != accessId){
throw ('Share ID does not match');
throw ({ message: 'Share ID does not match' });
}
const file = await drive.files.get({

View File

@@ -27,8 +27,13 @@ const api = {
// If the id is longer than 12, then it's a google id + the edit id. This splits the longer id up.
if(id.length > 12) {
googleId = id.slice(0, -12);
id = id.slice(-12);
if(id.length >= (33 + 12)) { // googleId is minimum 33 chars (may increase)
googleId = id.slice(0, -12); // current editId is 12 chars
} else { // old editIds used to be 10 chars;
googleId = id.slice(0, -10); // if total string is too short, must be old brew
console.log('Old brew, using 10-char Id');
}
id = id.slice(googleId.length);
}
return { id, googleId };
},
@@ -57,7 +62,14 @@ const api = {
googleError = err;
});
// Throw any error caught while attempting to retrieve Google brew.
if(googleError) throw googleError;
if(googleError) {
const reason = googleError.errors?.[0].reason;
if(reason == 'notFound') {
throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
} else {
throw { ...googleError, HBErrorCode: '01' };
}
}
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
stub = stub ? _.assign({ ...api.excludeStubProps(stub), stubbed: true }, api.excludeGoogleProps(googleBrew)) : googleBrew;
}
@@ -65,14 +77,16 @@ const api = {
const isAuthor = stub?.authors?.includes(req.account?.username);
const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
throw `The current logged in user does not have editor access to this brew.
If you believe you should have access to this brew, ask the file owner to invite you as an author by opening the brew, viewing the Properties tab, and adding your username to the "invited authors" list. You can then try to access this document again.`;
const accessError = { name: 'Access Error', status: 401 };
if(req.account){
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title };
}
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title };
}
// If after all of that we still don't have a brew, throw an exception
if(!stub && !stubOnly) {
throw 'Brew not found in Homebrewery database or Google Drive';
throw { name: 'BrewLoad Error', message: 'Brew not found', status: 404, HBErrorCode: '05', accessType: accessType, brewId: id };
}
// Clean up brew: fill in missing fields with defaults / fix old invalid values
@@ -181,7 +195,7 @@ If you believe you should have access to this brew, ask the file owner to invite
saved = await newHomebrew.save()
.catch((err)=>{
console.error(err, err.toString(), err.stack);
throw `Error while creating new brew, ${err.toString()}`;
throw { name: 'BrewSave Error', message: `Error while creating new brew, ${err.toString()}`, status: 500, HBErrorCode: '06' };
});
if(!saved) return;
saved = saved.toObject();
@@ -283,10 +297,13 @@ If you believe you should have access to this brew, ask the file owner to invite
try {
await api.getBrew('edit')(req, res, ()=>{});
} catch (err) {
const { id, googleId } = api.getId(req);
console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
await HomebrewModel.deleteOne({ editId: id });
return next();
// Only if the error code is HBErrorCode '02', that is, Google returned "404 - Not Found"
if(err.HBErrorCode == '02') {
const { id, googleId } = api.getId(req);
console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
await HomebrewModel.deleteOne({ editId: id });
return next();
}
}
let brew = req.brew;
@@ -308,7 +325,7 @@ If you believe you should have access to this brew, ask the file owner to invite
await HomebrewModel.deleteOne({ _id: brew._id })
.catch((err)=>{
console.error(err);
throw { status: 500, message: 'Error while removing' };
throw { name: 'BrewDelete Error', message: 'Error while removing', status: 500, HBErrorCode: '07', brewId: brew._id };
});
} else {
if(shouldDeleteGoogleBrew) {
@@ -320,7 +337,7 @@ If you believe you should have access to this brew, ask the file owner to invite
brew.markModified('authors'); //Mongo will not properly update arrays without markModified()
await brew.save()
.catch((err)=>{
throw { status: 500, message: err };
throw { name: 'BrewAuthorDelete Error', message: err, status: 500, HBErrorCode: '08', brewId: brew._id };
});
}
}

View File

@@ -111,21 +111,32 @@ describe('Tests for api', ()=>{
expect(googleId).toEqual('12345');
});
it('should return id and google id from params', ()=>{
it('should return 12-char id and google id from params', ()=>{
const { id, googleId } = api.getId({
params : {
id : '123456789012abcdefghijkl'
id : '123456789012345678901234567890123abcdefghijkl'
}
});
expect(googleId).toEqual('123456789012345678901234567890123');
expect(id).toEqual('abcdefghijkl');
expect(googleId).toEqual('123456789012');
});
it('should return 10-char id and google id from params', ()=>{
const { id, googleId } = api.getId({
params : {
id : '123456789012345678901234567890123abcdefghij'
}
});
expect(googleId).toEqual('123456789012345678901234567890123');
expect(id).toEqual('abcdefghij');
});
});
describe('getBrew', ()=>{
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
const notFoundError = 'Brew not found in Homebrewery database or Google Drive';
const notFoundError = { HBErrorCode: '05', message: 'Brew not found', name: 'BrewLoad Error', status: 404, accessType: 'share', brewId: '1' };
it('returns middleware', ()=>{
const getFn = api.getBrew('share');
@@ -183,7 +194,7 @@ describe('Tests for api', ()=>{
expect(next).toHaveBeenCalled();
});
it('throws if invalid author', async ()=>{
it('throws if not logged in as author', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
@@ -197,9 +208,24 @@ describe('Tests for api', ()=>{
err = e;
}
expect(err).toEqual(`The current logged in user does not have editor access to this brew.
expect(err).toEqual({ HBErrorCode: '04', message: 'User is not logged in', name: 'Access Error', status: 401, brewTitle: 'test brew', authors: ['a'] });
});
If you believe you should have access to this brew, ask the file owner to invite you as an author by opening the brew, viewing the Properties tab, and adding your username to the "invited authors" list. You can then try to access this document again.`);
it('throws if logged in as invalid author', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
const fn = api.getBrew('edit', true);
const req = { brew: {}, account: { username: 'b' } };
let err;
try {
await fn(req, null, null);
} catch (e) {
err = e;
}
expect(err).toEqual({ HBErrorCode: '03', message: 'User is not an Author', name: 'Access Error', status: 401, brewTitle: 'test brew', authors: ['a'] });
});
it('does not throw if no authors', async ()=>{
@@ -545,7 +571,7 @@ brew`);
describe('deleteBrew', ()=>{
it('should handle case where fetching the brew returns an error', async ()=>{
api.getBrew = jest.fn(()=>async ()=>{ throw 'err'; });
api.getBrew = jest.fn(()=>async ()=>{ throw { message: 'err', HBErrorCode: '02' }; });
api.getId = jest.fn(()=>({ id: '1', googleId: '2' }));
model.deleteOne = jest.fn(async ()=>{});
const next = jest.fn(()=>{});

View File

@@ -0,0 +1,36 @@
let CodeMirror;
if(typeof navigator !== 'undefined'){
CodeMirror = require('codemirror');
//Language Modes
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
require('codemirror/mode/css/css.js');
require('codemirror/mode/javascript/javascript.js');
//Addons
//Code folding
require('codemirror/addon/fold/foldcode.js');
require('codemirror/addon/fold/foldgutter.js');
//Search and replace
require('codemirror/addon/search/search.js');
require('codemirror/addon/search/searchcursor.js');
require('codemirror/addon/search/jump-to-line.js');
require('codemirror/addon/search/match-highlighter.js');
require('codemirror/addon/search/matchesonscrollbar.js');
require('codemirror/addon/dialog/dialog.js');
//Trailing space highlighting
// require('codemirror/addon/edit/trailingspace.js');
//Active line highlighting
// require('codemirror/addon/selection/active-line.js');
//Scroll past last line
require('codemirror/addon/scroll/scrollpastend.js');
//Auto-closing
//XML code folding is a requirement of the auto-closing tag feature and is not enabled
require('codemirror/addon/fold/xml-fold.js');
require('codemirror/addon/edit/closetag.js');
const foldCode = require('./helpers/fold-code');
foldCode.registerHomebreweryHelper(CodeMirror);
}
module.exports = CodeMirror;

View File

@@ -4,58 +4,32 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const closeTag = require('./close-tag');
const closeTag = require('./helpers/close-tag');
const Hints = require('./helpers/widget-elements/hints/hints.jsx');
const CodeMirror = require('./code-mirror.js');
let CodeMirror;
if(typeof navigator !== 'undefined'){
CodeMirror = require('codemirror');
//Language Modes
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
require('codemirror/mode/css/css.js');
require('codemirror/mode/javascript/javascript.js');
//Addons
//Code folding
require('codemirror/addon/fold/foldcode.js');
require('codemirror/addon/fold/foldgutter.js');
//Search and replace
require('codemirror/addon/search/search.js');
require('codemirror/addon/search/searchcursor.js');
require('codemirror/addon/search/jump-to-line.js');
require('codemirror/addon/search/match-highlighter.js');
require('codemirror/addon/search/matchesonscrollbar.js');
require('codemirror/addon/dialog/dialog.js');
//Trailing space highlighting
// require('codemirror/addon/edit/trailingspace.js');
//Active line highlighting
// require('codemirror/addon/selection/active-line.js');
//Scroll past last line
require('codemirror/addon/scroll/scrollpastend.js');
//Auto-closing
//XML code folding is a requirement of the auto-closing tag feature and is not enabled
require('codemirror/addon/fold/xml-fold.js');
require('codemirror/addon/edit/closetag.js');
const foldCode = require('./fold-code');
foldCode.registerHomebreweryHelper(CodeMirror);
}
const themeWidgets = require('../../../themes/V3/5ePHB/widgets');
const CodeEditor = createClass({
displayName : 'CodeEditor',
hintsRef : React.createRef(),
getDefaultProps : function() {
return {
language : '',
value : '',
wrap : true,
onChange : ()=>{},
enableFolding : true
enableFolding : true,
};
},
getInitialState : function() {
return {
docs : {}
docs : {},
widgetUtils : {},
widgets : {},
hints : [],
hintsField : undefined,
};
},
@@ -91,6 +65,9 @@ const CodeEditor = createClass({
} else {
this.codeMirror.setOption('foldOptions', false);
}
this.state.widgetUtils.updateWidgetGutter();
this.state.widgetUtils.updateAllLineWidgets();
},
buildEditor : function() {
@@ -155,7 +132,7 @@ const CodeEditor = createClass({
},
foldGutter : true,
foldOptions : this.foldOptions(this.codeMirror),
gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'widget-gutter'],
autoCloseTags : true,
styleActiveLine : true,
showTrailingSpace : false,
@@ -169,14 +146,74 @@ const CodeEditor = createClass({
});
closeTag.autoCloseCurlyBraces(CodeMirror, this.codeMirror);
this.setState({
widgetUtils : require('./helpers/widgets')(themeWidgets, this.codeMirror, (hints, field)=>{
this.setState({
hints,
hintsField : field
});
})
});
// Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works.
this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());});
this.codeMirror.on('change', (cm)=>{
this.props.onChange(cm.getValue());
this.state.widgetUtils.updateWidgetGutter();
});
this.codeMirror.on('cursorActivity', (cm)=>{
const { line } = cm.getCursor();
for (const key in this.state.widgets) {
if(key != line) {
this.state.widgetUtils.removeLineWidget(key, this.state.widgets[key]);
}
}
this.setState({
hints : [],
hintsField : undefined
});
const { widgets } = this.codeMirror.lineInfo(line);
if(!widgets) {
const widget = this.state.widgetUtils.updateLineWidgets(line);
if(widget) {
this.setState({
widgets : {
[line] : widget
}
});
}
}
});
this.updateSize();
this.codeMirror.on('gutterClick', (cm, n)=>{
// Open line widgets when 'widget-gutter' marker clicked
if(this.codeMirror.lineInfo(n).gutterMarkers?.['widget-gutter']) {
const { widgets } = this.codeMirror.lineInfo(n);
if(!widgets) {
const widget = this.state.widgetUtils.updateLineWidgets(n);
if(widget) {
this.setState({
widgets : { ...this.state.widgets, [n]: widget }
});
}
} else {
for (const widget of widgets) {
this.state.widgetUtils.removeLineWidget(n, widget);
}
this.setState({
hints : [],
hintsField : undefined
});
}
}
});
},
indent : function () {
const cm = this.codeMirror;
if (cm.somethingSelected()) {
if(cm.somethingSelected()) {
cm.execCommand('indentMore');
} else {
cm.execCommand('insertSoftTab');
@@ -403,10 +440,19 @@ const CodeEditor = createClass({
}
};
},
keyDown : function(e) {
if(this.hintsRef.current) {
this.hintsRef.current.keyDown(e);
}
},
//----------------------//
render : function(){
return <div className='codeEditor' ref='editor' style={this.props.style}/>;
const { hints, hintsField } = this.state;
return <React.Fragment>
<div className='codeEditor' ref='editor' style={this.props.style} onKeyDown={this.keyDown}/>
<Hints ref={this.hintsRef} hints={hints} field={hintsField}/>
</React.Fragment>;
}
});

View File

@@ -2,6 +2,8 @@
@import (less) 'codemirror/addon/fold/foldgutter.css';
@import (less) 'codemirror/addon/search/matchesonscrollbar.css';
@import (less) 'codemirror/addon/dialog/dialog.css';
@import (less) 'codemirror/addon/hint/show-hint.css';
@import 'naturalcrit/styles/colors.less';
@keyframes sourceMoveAnimation {
50% {background-color: red; color: white;}
@@ -14,7 +16,7 @@
text-shadow: none;
font-weight: 600;
color: grey;
}
}
.sourceMoveFlash .CodeMirror-line{
animation-name: sourceMoveAnimation;
@@ -30,4 +32,51 @@
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
// }
//}
.widget-gutter {
width: .7em;
}
.snippet-options-widget {
padding: 2px 0;
>div {
display: flex;
flex-wrap: wrap;
}
* {
margin: 0 2px 2px 2px;
}
input {
max-width: 10vw;
}
}
.widget-field {
border: 2px solid #ddd;
&.default {
background-color: @purple;
border: 2px solid @purple;
color: white;
>input {
background-color: @purple;
color: white;
}
}
&.suggested {
background-color: #ddd;
border: 2px dashed grey;
color: grey;
>input {
background-color: #ddd;
color: black;
}
}
}
}

View File

@@ -0,0 +1,48 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
require('./checkbox.less');
const CodeMirror = require('../../../code-mirror.js');
const Checkbox = createClass({
getDefaultProps : function() {
return {
value : '',
prefix : '',
cm : {},
n : -1,
default : false
};
},
handleChange : function (e) {
const { cm, n, value, prefix } = this.props;
const { text } = cm.lineInfo(n);
const updatedPrefix = `{{${prefix}`;
if(e.target?.checked)
cm.replaceRange(`,${value}`, CodeMirror.Pos(n, updatedPrefix.length), CodeMirror.Pos(n, updatedPrefix.length), '+insert');
else {
const start = text.indexOf(`,${value}`);
if(start > -1)
cm.replaceRange('', CodeMirror.Pos(n, start), CodeMirror.Pos(n, start + value.length + 1), '-delete');
}
},
render : function() {
const { cm, n, value, prefix, def } = this.props;
const { text } = cm.lineInfo(n);
const id = [prefix, value, n].join('-');
let className = 'widget-field widget-checkbox';
if(def) {
className += ' default';
}
return <React.Fragment>
<div className={className}>
<input type='checkbox' id={id} onChange={this.handleChange} checked={_.includes(text, `,${value}`)}/>
<label htmlFor={id}>{_.startCase(value)}</label>
</div>
</React.Fragment>;
}
});
module.exports = Checkbox;

View File

@@ -0,0 +1,7 @@
.widget-checkbox {
display: inline-block;
flex: 0 0 auto;
background-color: #ddd;
border-radius: 10px;
padding: 4px 2px;
}

View File

@@ -0,0 +1,95 @@
require('./color-selector.less');
const React = require('react');
const createClass = require('create-react-class');
const { PATTERNS, STYLE_FN, SNIPPET_TYPE } = require('../constants');
const CodeMirror = require('../../../code-mirror');
const debounce = require('lodash.debounce');
const ColorSelector = createClass({
getDefaultProps : function() {
return {
field : {},
cm : {},
n : undefined,
text : '',
def : false
};
},
getInitialState : function() {
return {
value : ''
};
},
componentDidMount : function() {
const { field, text } = this.props;
const pattern = PATTERNS.field[field.type](field.name);
const [_, __, value] = text.match(pattern) ?? [];
this.setState({
value : value,
});
},
componentDidUpdate({ text }) {
const { field } = this.props;
if(this.props.text !== text) {
const pattern = PATTERNS.field[field.type](field.name);
const [_, __, value] = this.props.text.match(pattern) ?? [];
this.setState({
value,
});
}
},
onChange : function(e) {
const { cm, text, field, n, snippetType } = this.props;
const pattern = PATTERNS.field[field.type](field.name);
const [_, label, current] = text.match(pattern) ?? [null, field.name, ''];
let index = text.indexOf(`${label}:${current}`);
while (index !== -1 && text[index - 1] === '-') {
index = text.indexOf(`${label}:${current}`, index + 1);
}
let value = e.target.value;
if(index === -1) {
if(snippetType === SNIPPET_TYPE.INLINE) {
index = text.indexOf('}');
}
index = index === -1 ? text.length : index;
value = `,${field.name}:${value}`;
} else {
index = index + 1 + field.name.length;
}
cm.replaceRange(value, CodeMirror.Pos(n, index), CodeMirror.Pos(n, index + current.length), '+insert');
this.setState({
value : e.target.value,
});
},
debounce : debounce((self, e)=>self.onChange(e), 300),
onChangeDebounce : function(e) {
this.setState({
value : e.target.value,
});
this.debounce(this, e);
},
render : function() {
const { field, n, text, def } = this.props;
const { value } = this.state;
const style = STYLE_FN(value);
const id = `${field?.name}-${n}`;
const pattern = PATTERNS.field[field.type](field.name);
const [_, label, __] = text.match(pattern) ?? [null, undefined, ''];
let className = 'widget-field color-selector';
if(!label) {
className += ' suggested';
}
if(def) {
className += ' default';
}
return <React.Fragment>
<div className={className}>
<label htmlFor={id}>{field.name}:</label>
<input className='color' type='color' value={value} onChange={this.onChangeDebounce}/>
<input id={id} className='text' type='text' style={style} value={value} onChange={this.onChange}/>
</div>
</React.Fragment>;
}
});
module.exports = ColorSelector;

View File

@@ -0,0 +1,8 @@
.color-selector {
.color {
height: 17px;
width: 13px;
padding: 0;
margin: 0;
}
}

View File

@@ -0,0 +1,50 @@
const _ = require('lodash');
export const UNITS = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%'];
export const HINT_TYPE = {
VALUE : 0,
NUMBER_SUFFIX : 1
};
export const SNIPPET_TYPE = {
BLOCK : 0,
INLINE : 1,
INJECTOR : 2,
};
export const FIELD_TYPE = {
TEXT : 0,
CHECKBOX : 1,
IMAGE_SELECTOR : 2,
COLOR_SELECTOR : 3,
};
const textField = (name)=>new RegExp(`[{,;](${name}):([^};,"\\(]*\\((?!,)[^};"\\)]*\\)|"[^},;"]*"|[^},;]*)`);
export const PATTERNS = {
snippet : {
[SNIPPET_TYPE.BLOCK] : (name)=>new RegExp(`^{{${name}(?:[^a-zA-Z].*)?`),
[SNIPPET_TYPE.INLINE] : (name)=>new RegExp(`{{${name}`),
[SNIPPET_TYPE.INJECTOR] : ()=>new RegExp(`^\\!\\[(?:[a-zA-Z -]+)?\\]\\(.*\\).*{[a-zA-Z0-9:, "'-]+}$`),
},
field : {
[FIELD_TYPE.TEXT] : textField,
[FIELD_TYPE.IMAGE_SELECTOR] : (name)=>new RegExp(`{{(${name})(\\d*)`),
[FIELD_TYPE.COLOR_SELECTOR] : textField
},
collectStyles : new RegExp(`(?:([a-zA-Z-]+):(?!\\/))+`, 'g'),
};
export const NUMBER_PATTERN = new RegExp(`([^-\\d]*)([-\\d]+)(${UNITS.join('|')})?(.*)`);
export const fourDigitNumberFromValue = (value)=>typeof value === 'number' ? (()=>{
const str = String(value);
return _.range(0, 4 - str.length).map(()=>'0').join('') + str;
})() : value;
const DEFAULT_WIDTH = '30px';
export const STYLE_FN = (value, extras = {})=>({
width : `calc(${value?.length ?? 0}ch + ${value?.length ? `${DEFAULT_WIDTH} / 2` : DEFAULT_WIDTH})`,
...extras
});

View File

@@ -0,0 +1,128 @@
const React = require('react');
const createClass = require('create-react-class');
const { NUMBER_PATTERN } = require('../constants');
const Hints = createClass({
hintsRef : React.createRef(),
activeHintRef : React.createRef(),
getDefaultProps : function() {
return {
hints : [],
field : undefined,
};
},
getInitialState : function() {
return {
activeHint : 0
};
},
componentDidUpdate : function({ hints }) {
const hintsLength = this.props.hints.length;
if(hintsLength - 1 < this.state.activeHint && hintsLength !== hints.length) {
this.setState({
activeHint : hintsLength === 0 ? 0 : hintsLength - 1
});
return;
}
if(this.hintsRef.current && this.activeHintRef.current) {
const offset = this.activeHintRef.current.offsetTop;
const scrollTop = this.hintsRef.current.scrollTop;
if(scrollTop + 50 < offset || scrollTop + 50 > offset) {
this.hintsRef.current.scrollTo({
top : offset - 50,
behavior : 'smooth'
});
}
}
},
componentDidMount : function() {},
keyDown : function(e) {
const { code } = e;
const { activeHint } = this.state;
const { hints, field } = this.props;
const match = field?.state?.value?.match(NUMBER_PATTERN);
if(code === 'ArrowDown') {
e.preventDefault();
if(!match || !match?.at(3)) {
this.setState({
activeHint : activeHint === hints.length - 1 ? 0 : activeHint + 1
});
}
} else if(code === 'ArrowUp') {
e.preventDefault();
if(!match || !match?.at(3)) {
this.setState({
activeHint : activeHint === 0 ? hints.length - 1 : activeHint - 1
});
}
} else if(code === 'Enter') {
e.preventDefault();
if(!match || !match?.at(3)) {
field?.hintSelected(hints[activeHint]);
this.setState({
activeHint : 0
});
}
}
},
render : function() {
const { activeHint } = this.state;
const { hints, field } = this.props;
if(!field) return null;
const bounds = field.fieldRef[field.state.id]?.current?.getBoundingClientRect();
if(!bounds) return null;
const hintElements = hints
.filter((h)=>h.hint !== field.state.value)
.map((h, i)=>{
let className = 'CodeMirror-hint';
if(activeHint === i) {
className += ' CodeMirror-hint-active';
return <li key={i}
role={'option'}
className={className}
onMouseDown={(e)=>field.hintSelected(h, e)}
ref={this.activeHintRef}>
{h.hint}
</li>;
}
return <li key={i}
role={'option'}
className={className}
onMouseDown={(e)=>field.hintSelected(h, e)}>
{h.hint}
</li>;
});
let style = {
display : 'none'
};
if(hintElements.length > 0) {
style = {
...style,
display : 'block',
top : `${bounds.top - 5}px`,
left : `${bounds.left}px`
};
}
return <React.Fragment>
<ul role={'listbox'}
id={'hints'}
aria-expanded={true}
className={'CodeMirror-hints default'}
style={style}
ref={this.hintsRef}>
{hintElements}
</ul>
</React.Fragment>;
}
});
module.exports = Hints;

View File

@@ -0,0 +1,92 @@
require('./image-selector.less');
const React = require('react');
const createClass = require('create-react-class');
const { Modal, modalHelpers } = require('../modal/modal.jsx');
const { PATTERNS } = require('../constants.js');
const CodeMirror = require('../../../code-mirror.js');
const _ = require('lodash');
const ImageSelector = createClass({
modalRef : React.createRef(),
getDefaultProps : function () {
return {
field : {},
cm : {},
n : undefined
};
},
getInitialState : function() {
return {
selected : undefined,
};
},
componentDidMount : function() {
modalHelpers.mount(this);
},
componentDidUpdate : function() {
const { name, preview, values } = this.props.field;
const { selected } = this.state;
const images = values.map((v, i)=>{
const className = String(selected) === String(v) ? 'selected' : '';
return <img key={i} className={className} src={preview(v)} alt={`${name} image ${v}`} onClick={()=>this.select(v)}/>;
});
this.state.modalRoot?.render(<Modal ref={this.modalRef} header={_.startCase(name)} save={this.save}>
<div className={'images'}>
{images}
</div>
</Modal>);
},
componentWillUnmount : function() {
modalHelpers.unmount(this);
},
save : function() {
const { cm, field, n } = this.props;
const { text } = cm.lineInfo(n);
const pattern = PATTERNS.field[field.type](field.name);
const [fullmatch, label, current] = text.match(pattern);
if(!fullmatch) {
console.warn('something is wrong... please report this warning with a screenshot');
return;
}
const currentText = `${label}${current ?? ''}`;
const index = 2;
const value = label + this.state.selected;
cm.replaceRange(value, CodeMirror.Pos(n, index), CodeMirror.Pos(n, index + currentText.length), '+insert');
},
select : function(value) {
this.setState({
selected : value
});
},
showModal : function() {
const { cm, field, n } = this.props;
const { text } = cm.lineInfo(n);
const pattern = PATTERNS.field[field.type](field.name);
const [fullmatch, _, current] = text.match(pattern);
if(!fullmatch) {
return;
}
this.setState({
selected : current
});
this.modalRef.current.setVisible(true);
},
render : function () {
return <React.Fragment>
<button onClick={this.showModal}>Select Image</button>
</React.Fragment>;
}
});
module.exports = ImageSelector;

View File

@@ -0,0 +1,23 @@
.images {
display: flex;
flex-direction: row;
flex-wrap: wrap;
max-height: 60vh;
overflow-y: scroll;
img {
flex: 0 0 auto;
max-width: 10vw;
max-height: 20vh;
border-radius: 10px;
&:hover {
background-color: rgba(0, 0, 0, .1);
}
&.selected {
background-color: rgba(0, 0, 0, .175);
}
}
}

View File

@@ -0,0 +1,11 @@
const Text = require('./text/text.jsx');
const Checkbox = require('./checkbox/checkbox.jsx');
const ImageSelector = require('./image-selector/image-selector.jsx');
const ColorSelector = require('./color-selector/color-selector.jsx');
module.exports = {
Text : Text,
Checkbox : Checkbox,
ImageSelector : ImageSelector,
ColorSelector : ColorSelector,
};

View File

@@ -0,0 +1,75 @@
require('./modal.less');
const React = require('react');
const ReactDOMClient = require('react-dom/client');
const createClass = require('create-react-class');
const Modal = createClass({
getDefaultProps : function () {
return {
header : '',
save : ()=>{},
};
},
getInitialState : function() {
return {
visible : false,
};
},
setVisible : function(visible) {
this.setState({
visible
});
},
save : function() {
this.props.save();
this.setVisible(false);
},
render : function () {
const { children, header } = this.props;
const { visible } = this.state;
return <React.Fragment>
{visible ? <div className={'bg-cover'}>
<div className={'modal'}>
<h1>{header}</h1>
<hr/>
{children}
<div className={'action-row'}>
<button id={'save'} onClick={()=>this.save()}>Save</button>
<button id={'cancel'} onClick={()=>this.setVisible(false)}>Cancel</button>
</div>
</div>
</div> : null}
</React.Fragment>;
}
});
module.exports = {
/*
* Requirements:
* - modalRef member variable
* - should be re-rendered via `this.state.modalRoot?.render` in `componentDidUpdate`
*/
Modal,
modalHelpers : {
// should be called in `componentDidMount`
// `self` should be passed as the component instance (`this`)
mount : (self)=>{
const el = document.createElement('div');
const root = ReactDOMClient.createRoot(el);
document.querySelector('body').append(el);
self.setState({
el,
modalRoot : root
});
},
// should be called in `componentWillUnmount`
// `self` should be passed as the component instance (`this`)
unmount : (self)=>{
self.state.el.remove();
}
}
};

View File

@@ -0,0 +1,58 @@
@import 'naturalcrit/styles/colors.less';
.bg-cover {
width: 100vw;
height: 100vh;
position: absolute;
z-index: 10000000;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, .5);
}
.modal {
position: absolute;
top: 10vh;
left: 25vw;
width: 50vw;
min-height: 50vh;
max-height: 80vh;
background-color: #fff;
border-radius: 10px;
box-shadow: 5px 5px 50px black;
display: flex;
flex-direction: column;
justify-content: space-between;
h1 {
margin-top: 5px;
margin-left: 5px;
font-size: 2em;
}
>* {
flex: 0 0 auto;
}
.action-row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: left;
>* {
flex: 0 0 auto;
margin-left: 5px;
}
}
button {
&#cancel {
background-color: @redLight;
&:hover {
background-color: @red;
}
}
}
}

View File

@@ -0,0 +1,165 @@
require('./text.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const { NUMBER_PATTERN, HINT_TYPE, PATTERNS, STYLE_FN, FIELD_TYPE, SNIPPET_TYPE } = require('../constants');
const CodeMirror = require('../../../code-mirror.js');
const Text = createClass({
fieldRef : {},
getDefaultProps : function() {
return {
field : {},
text : '',
n : 0,
setHints : ()=>{},
onChange : ()=>{},
getStyleHints : ()=>{},
def : false,
snippetType : -1
};
},
getInitialState : function() {
return {
value : '',
id : ''
};
},
componentDidUpdate : function({ text }, { value }) {
if(this.props.text !== text) {
const { field, n } = this.props;
const pattern = PATTERNS.field[field.type](field.name);
const [_, __, value] = this.props.text.match(pattern) ?? [];
this.setState({
value : value,
id : `${field?.name}-${n}`
});
}
if(this.state.value !== value) {
const { field } = this.props;
this.props.setHints(this, field.hints ? this.props.getStyleHints(field, this.state.value) : []);
}
},
componentDidMount : function() {
const { field, text, n } = this.props;
const id = `${field?.name}-${n}`;
const pattern = PATTERNS.field[field.type](field.name);
const [_, __, value] = text.match(pattern) ?? [];
this.setState({
value : value,
id
});
this.fieldRef[id] = React.createRef();
},
componentWillUnmount : function() {
this.fieldRef = undefined;
this.fieldRef = {};
this.fieldRef[this.state.id]?.remove();
},
setFocus : function(e) {
const { type } = e;
const { field } = this.props;
this.props.setHints(this, type === 'focus' && field.hints ? this.props.getStyleHints(field, this.state.value) : []);
},
hintSelected : function(h, e) {
let value;
if(h?.type === HINT_TYPE.VALUE) {
value = h.hint;
} else if(h?.type === HINT_TYPE.NUMBER_SUFFIX) {
const match = this.state.value.match(NUMBER_PATTERN);
let suffix = match?.at(4) ?? '';
for (const char of h.hint) {
if(suffix.at(0) === char) {
suffix = suffix.slice(1);
}
}
value = `${match?.at(1) ?? ''}${match?.at(2) ?? ''}${h.hint}${suffix}`;
}
this.onChange({
target : {
value
}
});
},
keyDown : function(e) {
const { code } = e;
const { field } = this.props;
const { value } = this.state;
const match = value?.match(NUMBER_PATTERN);
if(code === 'ArrowDown') {
if(match && CSS.supports(field.name, value)) {
e.preventDefault();
this.onChange({
target : {
value : `${match.at(1) ?? ''}${Number(match[2]) - field.increment}${match[3] ?? ''}${match.at(4) ?? ''}`
}
});
}
} else if(code === 'ArrowUp') {
if(match && CSS.supports(field.name, value)) {
e.preventDefault();
this.onChange({
target : {
value : `${match.at(1) ?? ''}${Number(match[2]) + field.increment}${match[3] ?? ''}${match.at(4) ?? ''}`
}
});
}
}
},
onChange : function (e){
const { cm, text, field, n, snippetType } = this.props;
const pattern = PATTERNS.field[field.type](field.name);
const [_, label, current] = text.match(pattern) ?? [null, field.name, ''];
let index = text.indexOf(`${label}:${current}`);
let value = e.target.value;
if(index === -1) {
if(snippetType === SNIPPET_TYPE.INLINE) {
index = text.indexOf('}');
}
index = index === -1 ? text.length : index;
value = `,${field.name}:${value}`;
} else {
index = index + 1 + field.name.length;
}
cm.replaceRange(value, CodeMirror.Pos(n, index), CodeMirror.Pos(n, index + current.length), '+insert');
this.setState({
value : e.target.value,
});
},
render : function() {
const { value, id } = this.state;
const { field, text, def } = this.props;
const style = STYLE_FN(value);
const pattern = PATTERNS.field[field.type](field.name);
const [_, label, __] = text.match(pattern) ?? [null, undefined, ''];
let className = 'widget-field';
if(!label) {
className += ' suggested';
}
if(def) {
className += ' default';
}
return <React.Fragment>
<div className={className}>
<label htmlFor={id}>{field.name}:</label>
<input id={id} type='text' value={value}
style={style}
ref={this.fieldRef[id]}
onChange={this.onChange}
onFocus={this.setFocus}
onBlur={this.setFocus}
onKeyDown={this.keyDown}/>
</div>
</React.Fragment>;
}
});
module.exports = Text;

View File

@@ -0,0 +1,37 @@
.widget-field {
display: inline-block;
flex: 0 0 auto;
background-color: #ddd;
border-radius: 10px;
padding: 4px 2px;
>label {
display: inline;
width: 50px;
margin: 0 0;
}
>input {
background-color: #ddd;
border: none;
}
>.hints {
position: relative;
left: 30px;
max-height: 100px;
overflow-y: scroll;
background-color: white;
>.hint {
margin: 0 0;
padding: 2px;
cursor: default;
&:hover,
&.active {
background-color: rgba(0, 0, 0, 0.1);
}
}
}
}

View File

@@ -0,0 +1,188 @@
const React = require('react');
const ReactDOMClient = require('react-dom/client');
const { PATTERNS, FIELD_TYPE, HINT_TYPE, UNITS } = require('./widget-elements/constants');
require('./widget-elements/hints/hints.jsx');
const { Text, Checkbox, ImageSelector, ColorSelector } = require('./widget-elements');
const CodeMirror = require('../code-mirror.js');
// See https://codemirror.net/5/addon/hint/css-hint.js for code reference
const pseudoClasses = { 'active' : 1, 'after' : 1, 'before' : 1, 'checked' : 1, 'default' : 1,
'disabled' : 1, 'empty' : 1, 'enabled' : 1, 'first-child' : 1, 'first-letter' : 1,
'first-line' : 1, 'first-of-type' : 1, 'focus' : 1, 'hover' : 1, 'in-range' : 1,
'indeterminate' : 1, 'invalid' : 1, 'lang' : 1, 'last-child' : 1, 'last-of-type' : 1,
'link' : 1, 'not' : 1, 'nth-child' : 1, 'nth-last-child' : 1, 'nth-last-of-type' : 1,
'nth-of-type' : 1, 'only-of-type' : 1, 'only-child' : 1, 'optional' : 1, 'out-of-range' : 1,
'placeholder' : 1, 'read-only' : 1, 'read-write' : 1, 'required' : 1, 'root' : 1,
'selection' : 1, 'target' : 1, 'valid' : 1, 'visited' : 1
};
const genKey = (...args)=>args.join('-');
module.exports = function(widgets, cm, setHints) {
const roots = {};
const spec = CodeMirror.resolveMode('text/css');
const headless = CodeMirror(()=>{});
const makeTempCSSDoc = (value)=>CodeMirror.Doc(`.selector {\n${value}\n}`, 'text/css');
// See https://codemirror.net/5/addon/hint/css-hint.js for code reference
const getStyleHints = (field, value)=>{
const tempDoc = makeTempCSSDoc(`${field.name}:${value?.replaceAll(`'"`, '') ?? ''}`);
headless.swapDoc(tempDoc);
const pos = CodeMirror.Pos(1, field.name.length + 1 + (value?.length ?? 0), false);
const token = headless.getTokenAt(pos);
const inner = CodeMirror.innerMode(tempDoc.getMode(), token?.state);
if(inner.mode.name !== 'css') return;
if(token.type === 'keyword' && '!important'.indexOf(token.string) === 0)
return { list : ['!important'], from : CodeMirror.Pos(pos.line, token.start),
to : CodeMirror.Pos(pos.line, token.end) };
let start = token.start, end = pos.ch, word = token.string.slice(0, end - start);
if(/[^\w$_-]/.test(word)) {
word = ''; start = end = pos.ch;
}
let result = [];
const add = (keywords)=>{
for (const name in keywords)
if(!word || name.lastIndexOf(word, 0) === 0)
result.push(name);
};
const st = inner.state.state;
if(st === 'pseudo' || token.type === 'variable-3') {
add(pseudoClasses);
} else if(st === 'block' || st === 'maybeprop') {
add(spec.propertyKeywords);
} else if(st === 'prop' || st === 'parens' || st === 'at' || st === 'params') {
add(spec.valueKeywords);
add(spec.colorKeywords);
} else if(st === 'media' || st === 'media_parens') {
add(spec.mediaTypes);
add(spec.mediaFeatures);
}
result = result.map((h)=>({ hint: h, type: HINT_TYPE.VALUE }))
.filter((h)=>CSS.supports(field.name, h.hint) && h.hint.includes(value ?? ''));
const numberSuffix = word.slice(-4).replaceAll(/\d/g, '');
if(token.type === 'number' && !UNITS.includes(numberSuffix)) {
result.push(...UNITS
.filter((u)=>u.includes(numberSuffix) && CSS.supports(field.name, `${value.replaceAll(/\D/g, '') ?? ''}${u}`))
.map((u)=>({ hint: u, type: HINT_TYPE.NUMBER_SUFFIX }))
);
}
return result;
};
const widgetOptions = widgets.map((widget)=>({
name : widget.name,
pattern : PATTERNS.snippet[widget.type](widget.name),
renderWidget : (n, node)=>{
roots[n] = roots[n] ?? {};
const parent = document.createElement('div');
const id = `${widget.name}-${n}`;
parent.id = id;
const textFieldNames = (widget.fields || []).filter((f)=>f.type === FIELD_TYPE.TEXT || f.type === FIELD_TYPE.COLOR_SELECTOR).map((f)=>f.name);
const { text } = cm.lineInfo(n);
const fields = (widget.fields || []).map((field)=>{
const key = genKey(widget.name, n, field.name);
if(field.type === FIELD_TYPE.CHECKBOX) {
return <Checkbox key={key} cm={cm} n={n} prefix={widget.name} value={field.name} def={true}/>;
} else if(field.type === FIELD_TYPE.TEXT) {
return <Text key={key} field={field} cm={cm} n={n} text={text} setHints={(f, h)=>setHints(h, f)} getStyleHints={getStyleHints} def={true} snippetType={widget.type}/>;
} else if(field.type === FIELD_TYPE.IMAGE_SELECTOR) {
return <ImageSelector key={key} field={field} cm={cm} n={n}/>;
} else if(field.type === FIELD_TYPE.COLOR_SELECTOR) {
return <ColorSelector key={key} field={field} cm={cm} n={n} text={text} def={true} snippetType={widget.type}/>;
} else {
return null;
}
}).filter(Boolean);
const styles = [...text.matchAll(PATTERNS.collectStyles)].map(([_, style])=>{
if(textFieldNames.includes(style)) return false;
const field = {
name : style,
type : FIELD_TYPE.TEXT,
increment : 5,
hints : true,
};
const key = genKey(widget.name, n, style);
if(style.includes('color')) {
return <ColorSelector key={key} field={field} cm={cm} n={n} text={text} snippetType={widget.type}/>;
}
return <Text key={key} field={field} cm={cm} n={n} text={text} setHints={(f, h)=>setHints(h, f)} getStyleHints={getStyleHints} snippetType={widget.type}/>;
}).filter(Boolean);
const root = roots[n][id] ?? ReactDOMClient.createRoot(node || parent);
root.render(<React.Fragment>
{fields}
{styles}
</React.Fragment>);
roots[n][id] = root;
return node || parent;
}
}));
const updateLineWidgets = (n)=>{
const { text, widgets } = cm.lineInfo(n);
const widgetOption = widgetOptions.find((option)=>!!text.match(option.pattern));
if(!widgetOption) return;
if(!!widgets) {
for (const widget of widgets) {
widgetOption.renderWidget(n, widget.node);
}
} else {
return cm.addLineWidget(n, widgetOption.renderWidget(n), {
above : false,
coverGutter : false,
noHScroll : true,
className : `snippet-options-widget ${widgetOption.name}-widget ${widgetOption.name}-widget-${n}`
});
}
};
return {
roots,
removeLineWidget : (n, widget)=>{
roots[n][widget.node.id]?.unmount();
delete roots[n][widget.node.id];
widget?.clear();
},
updateLineWidgets,
updateAllLineWidgets : ()=>{
for (let i = 0; i < cm.lineCount(); i++) {
const { widgets } = cm.lineInfo(i);
if(!!widgets) {
updateLineWidgets(i);
}
}
},
updateWidgetGutter : ()=>{
cm.operation(()=>{
for (let i = 0; i < cm.lineCount(); i++) {
const { text, widgets } = cm.lineInfo(i);
if(widgetOptions.some((option)=>text.match(option.pattern))) {
if(widgets) {
continue;
}
const optionsMarker = document.createElement('div');
optionsMarker.style.color = '#822';
optionsMarker.style.cursor = 'pointer';
optionsMarker.innerHTML = '●';
cm.setGutterMarker(i, 'widget-gutter', optionsMarker);
} else {
cm.setGutterMarker(i, 'widget-gutter', null);
}
}
});
}
};
};

View File

@@ -2,6 +2,7 @@
const _ = require('lodash');
const Marked = require('marked');
const MarkedExtendedTables = require('marked-extended-tables');
const { markedSmartypantsLite: MarkedSmartypantsLite } = require('marked-smartypants-lite');
const { gfmHeadingId: MarkedGFMHeadingId } = require('marked-gfm-heading-id');
const renderer = new Marked.Renderer();
@@ -237,49 +238,10 @@ const definitionLists = {
}
};
const MarkedSmartyPantsLite = ()=>{
return {
tokenizer : {
inlineText(src) {
// don't escape inlineText
const cap = this.rules.inline.text.exec(src);
/* istanbul ignore next */
if(!cap) {
// should never happen
return;
}
const text = cap[0]
// em-dashes
.replace(/---/g, '\u2014')
// en-dashes
.replace(/--/g, '\u2013')
// opening singles
.replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
// closing singles & apostrophes
.replace(/'/g, '\u2019')
// opening doubles
.replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
// closing doubles
.replace(/"/g, '\u201d')
// ellipses
.replace(/\.{3}/g, '\u2026');
return {
type : 'text',
raw : cap[0],
text : text
};
}
}
};
};
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] });
Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartyPantsLite());
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) {
@@ -351,12 +313,6 @@ const escape = function (html, encode) {
return html;
};
const sanatizeScriptTags = (content)=>{
return content
.replace(/<script/ig, '&lt;script')
.replace(/<\/script>/ig, '&lt;/script&gt;');
};
const tagTypes = ['div', 'span', 'a'];
const tagRegex = new RegExp(`(${
_.map(tagTypes, (type)=>{
@@ -387,7 +343,7 @@ module.exports = {
render : (rawBrewText)=>{
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`)
.replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`);
return Marked.parse(sanatizeScriptTags(rawBrewText));
return Marked.parse(rawBrewText);
},
validate : (rawBrewText)=>{

View File

@@ -90,12 +90,6 @@ const escape = function (html, encode) {
return html;
};
const sanatizeScriptTags = (content)=>{
return content
.replace(/<script/ig, '&lt;script')
.replace(/<\/script>/ig, '&lt;/script&gt;');
};
const tagTypes = ['div', 'span', 'a'];
const tagRegex = new RegExp(`(${
_.map(tagTypes, (type)=>{
@@ -113,7 +107,7 @@ module.exports = {
marked : Markdown,
render : (rawBrewText)=>{
return Markdown(
sanatizeScriptTags(rawBrewText),
rawBrewText,
{ renderer: renderer }
);
},

View File

@@ -1,5 +1,6 @@
require('./nav.less');
const React = require('react');
const { useState, useRef, useEffect } = React;
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
@@ -71,64 +72,49 @@ const Nav = {
}
}),
dropdown : createClass({
displayName : 'Nav.dropdown',
getDefaultProps : function() {
return {
trigger : 'hover'
};
},
getInitialState : function() {
return {
showDropdown : false
};
},
componentDidMount : function() {
if(this.props.trigger == 'click')
document.addEventListener('click', this.handleClickOutside);
},
componentWillUnmount : function() {
if(this.props.trigger == 'click')
document.removeEventListener('click', this.handleClickOutside);
},
handleClickOutside : function(e){
// Close dropdown when clicked outside
if(this.refs.dropdown && !this.refs.dropdown.contains(e.target)) {
this.handleDropdown(false);
}
},
handleDropdown : function(show){
this.setState({
showDropdown : show
});
},
renderDropdown : function(dropdownChildren){
if(!this.state.showDropdown) return null;
dropdown : function dropdown(props) {
props = Object.assign({}, props, {
trigger : 'hover click'
});
return (
<div className='navDropdown'>
{dropdownChildren}
</div>
);
},
render : function () {
const dropdownChildren = React.Children.map(this.props.children, (child, i)=>{
// Ignore the first child
if(i < 1) return;
return child;
});
return (
<div className={`navDropdownContainer ${this.props.className}`}
ref='dropdown'
onMouseEnter={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(true);} : undefined}
onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}
onMouseLeave={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(false);} : undefined}>
{this.props.children[0] || this.props.children /*children is not an array when only one child*/}
{this.renderDropdown(dropdownChildren)}
</div>
);
const myRef = useRef(null);
const [showDropdown, setShowDropdown] = useState(false);
useEffect(()=>{
document.addEventListener('click', handleClickOutside);
return ()=>{
document.removeEventListener('click', handleClickOutside);
};
}, []);
function handleClickOutside(e) {
// Close dropdown when clicked outside
if(!myRef.current?.contains(e.target)) {
handleDropdown(false);
}
}
})
function handleDropdown(show) {
setShowDropdown(show ?? !showDropdown);
}
const dropdownChildren = React.Children.map(props.children, (child, i)=>{
if(i < 1) return;
return child;
});
return (
<div className={`navDropdownContainer ${props.className}`}
ref={myRef}
onMouseEnter = { props.trigger.includes('hover') ? ()=>handleDropdown(true) : undefined }
onMouseLeave = { props.trigger.includes('hover') ? ()=>handleDropdown(false) : undefined }
onClick = { props.trigger.includes('click') ? ()=>handleDropdown(true) : undefined }
>
{props.children[0] || props.children /*children is not an array when only one child*/}
{showDropdown && <div className='navDropdown'>{dropdownChildren}</div>}
</div>
);
}
};

View File

@@ -79,6 +79,8 @@ nav{
left : 0px;
z-index : 10000;
width : 100%;
overflow : hidden auto;
max-height : calc(100vh - 28px);
.navItem{
animation-name: glideDropDown;
animation-duration: 0.4s;

View File

@@ -2,12 +2,6 @@
const Markdown = require('naturalcrit/markdown.js');
test('Escapes <script> tag', function() {
const source = '<script></script>';
const rendered = Markdown.render(source);
expect(rendered).toMatch('&lt;script&gt;&lt;/script&gt;');
});
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
const source = '<div>*Bold text*</div>';
const rendered = Markdown.render(source);

View File

@@ -47,8 +47,8 @@ const getTOC = (pages)=>{
return res;
};
module.exports = function(brew){
const pages = brew.text.split('\\page');
module.exports = function(props){
const pages = props.brew.text.split('\\page');
const TOC = getTOC(pages);
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`);

View File

@@ -19,16 +19,6 @@ module.exports = [
icon : 'fas fa-pencil-alt',
view : 'text',
snippets : [
{
name : 'Page Number',
icon : 'fas fa-bookmark',
gen : '{{pageNumber 1}}\n{{footnote PART 1 | SECTION NAME}}\n\n'
},
{
name : 'Auto-incrementing Page Number',
icon : 'fas fa-sort-numeric-down',
gen : '{{pageNumber,auto}}\n{{footnote PART 1 | SECTION NAME}}\n\n'
},
{
name : 'Table of Contents',
icon : 'fas fa-book',

View File

@@ -48,8 +48,8 @@ const getTOC = (pages)=>{
return res;
};
module.exports = function(brew){
const pages = brew.text.split('\\page');
module.exports = function(props){
const pages = props.brew.text.split('\\page');
const TOC = getTOC(pages);
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
if(g1.title !== null) {

154
themes/V3/5ePHB/widgets.js Normal file
View File

@@ -0,0 +1,154 @@
const _ = require('lodash');
const { SNIPPET_TYPE, FIELD_TYPE, fourDigitNumberFromValue } = require('../../../shared/naturalcrit/codeEditor/helpers/widget-elements/constants');
module.exports = [{
name : 'monster',
type : SNIPPET_TYPE.BLOCK,
fields : [{
name : 'frame',
type : FIELD_TYPE.CHECKBOX
}, {
name : 'wide',
type : FIELD_TYPE.CHECKBOX
}]
}, {
name : 'classTable',
type : SNIPPET_TYPE.BLOCK,
fields : [{
name : 'frame',
type : FIELD_TYPE.CHECKBOX
}, {
name : 'decoration',
type : FIELD_TYPE.CHECKBOX
}, {
name : 'wide',
type : FIELD_TYPE.CHECKBOX
}]
}, {
name : 'runeTable',
type : SNIPPET_TYPE.BLOCK,
fields : [{
name : 'frame',
type : FIELD_TYPE.CHECKBOX
}, {
name : 'wide',
type : FIELD_TYPE.CHECKBOX
}, {
name : 'font-family',
type : FIELD_TYPE.TEXT
}]
}, {
name : 'index',
type : SNIPPET_TYPE.BLOCK,
fields : [{
name : 'wide',
type : FIELD_TYPE.CHECKBOX
}, {
name : 'columns',
type : FIELD_TYPE.TEXT,
increment : 1
}]
}, {
name : 'image',
type : SNIPPET_TYPE.INJECTOR,
fields : []
}, {
name : 'artist',
type : SNIPPET_TYPE.BLOCK,
fields : [{
name : 'top',
type : FIELD_TYPE.TEXT,
increment : 5,
hints : true
}]
}, {
name : 'watercolor',
type : SNIPPET_TYPE.INLINE,
fields : [{
name : 'watercolor',
type : FIELD_TYPE.IMAGE_SELECTOR,
preview : (value)=>`/assets/watercolor/watercolor${value}.png`,
values : _.range(1, 13)
}, {
name : 'top',
type : FIELD_TYPE.TEXT,
increment : 5,
hints : true
}, {
name : 'left',
type : FIELD_TYPE.TEXT,
increment : 5,
hints : true
}, {
name : 'width',
type : FIELD_TYPE.TEXT,
increment : 5,
hints : true
}, {
name : 'opacity',
type : FIELD_TYPE.TEXT,
increment : 5
}, {
name : 'background-color',
type : FIELD_TYPE.COLOR_SELECTOR,
}]
}, {
name : 'imageMaskCenter',
type : SNIPPET_TYPE.INLINE,
fields : [{
name : 'imageMaskCenter',
type : FIELD_TYPE.IMAGE_SELECTOR,
preview : (value)=>`/assets/waterColorMasks/center/${fourDigitNumberFromValue(value)}.webp`,
values : _.range(1, 17)
}, {
name : '--offsetX',
type : FIELD_TYPE.TEXT,
increment : 5,
}, {
name : '--offsetY',
type : FIELD_TYPE.TEXT,
increment : 5,
}, {
name : '--rotation',
type : FIELD_TYPE.TEXT,
increment : 5,
}]
}, {
name : 'imageMaskEdge',
type : SNIPPET_TYPE.INLINE,
fields : [{
name : 'imageMaskEdge',
type : FIELD_TYPE.IMAGE_SELECTOR,
preview : (value)=>`/assets/waterColorMasks/edge/${fourDigitNumberFromValue(value)}.webp`,
values : _.range(1, 9)
}, {
name : '--offset',
type : FIELD_TYPE.TEXT,
increment : 5,
}, {
name : '--rotation',
type : FIELD_TYPE.TEXT,
increment : 5,
}]
}, {
name : 'imageMaskCorner',
type : SNIPPET_TYPE.INLINE,
fields : [{
name : 'imageMaskCorner',
type : FIELD_TYPE.IMAGE_SELECTOR,
preview : (value)=>`/assets/waterColorMasks/corner/${fourDigitNumberFromValue(value)}.webp`,
values : _.range(1, 38)
}, {
name : '--offsetX',
type : FIELD_TYPE.TEXT,
increment : 5,
}, {
name : '--offsetY',
type : FIELD_TYPE.TEXT,
increment : 5,
}, {
name : '--rotation',
type : FIELD_TYPE.TEXT,
increment : 5,
}]
}];

View File

@@ -2,6 +2,7 @@
const WatercolorGen = require('./snippets/watercolor.gen.js');
const ImageMaskGen = require('./snippets/imageMask.gen.js');
const FooterGen = require('./snippets/footer.gen.js');
const dedent = require('dedent-tabs').default;
module.exports = [
@@ -21,6 +22,53 @@ module.exports = [
icon : 'fas fa-file-alt',
gen : '\n\\page\n'
},
{
name : 'Page Number',
icon : 'fas fa-bookmark',
gen : '{{pageNumber 1}}\n'
},
{
name : 'Auto-incrementing Page Number',
icon : 'fas fa-sort-numeric-down',
gen : '{{pageNumber,auto}}\n'
},
{
name : 'Footer',
icon : 'fas fa-shoe-prints',
gen : FooterGen.createFooterFunc(),
subsnippets : [
{
name : 'Footer from H1',
icon : 'fas fa-dice-one',
gen : FooterGen.createFooterFunc(1)
},
{
name : 'Footer from H2',
icon : 'fas fa-dice-two',
gen : FooterGen.createFooterFunc(2)
},
{
name : 'Footer from H3',
icon : 'fas fa-dice-three',
gen : FooterGen.createFooterFunc(3)
},
{
name : 'Footer from H4',
icon : 'fas fa-dice-four',
gen : FooterGen.createFooterFunc(4)
},
{
name : 'Footer from H5',
icon : 'fas fa-dice-five',
gen : FooterGen.createFooterFunc(5)
},
{
name : 'Footer from H6',
icon : 'fas fa-dice-six',
gen : FooterGen.createFooterFunc(6)
}
]
},
{
name : 'Vertical Spacing',
icon : 'fas fa-arrows-alt-v',

View File

@@ -0,0 +1,17 @@
const Markdown = require('../../../../shared/naturalcrit/markdown.js');
module.exports = {
createFooterFunc : function(headerSize=1){
return (props)=>{
const cursorPos = props.cursorPos;
const markdownText = props.brew.text.split('\n').slice(0, cursorPos.line).join('\n');
const markdownTokens = Markdown.marked.lexer(markdownText);
const headerToken = markdownTokens.findLast((lexerToken)=>{ return lexerToken.type === 'heading' && lexerToken.depth === headerSize; });
const headerText = headerToken?.tokens.map((token)=>{ return token.text; }).join('');
const outputText = headerText || 'PART 1 | SECTION NAME';
return `\n{{footnote ${outputText}}}\n`;
};
}
};