diff --git a/.circleci/config.yml b/.circleci/config.yml index 13d339892..461a0dfa6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,7 +27,7 @@ jobs: # fallback to using the latest cache if no exact match is found - v1-dependencies- - - node/install-npm + - run: sudo npm install -g npm@8.10.0 - node/install-packages: app-dir: ~/homebrewery cache-path: node_modules @@ -48,22 +48,28 @@ jobs: - image: cimg/node:16.11.0 working_directory: ~/homebrewery - parallelism: 4 + parallelism: 1 steps: - attach_workspace: at: . # run tests! - - run: + - run: + name: Test - API Unit Tests + command: npm run test:api-unit + - run: name: Test - Basic command: npm run test:basic - - run: + - run: name: Test - Mustache Spans - command: npm run test:mustache-span - - run: + command: npm run test:mustache-syntax + - run: name: Test - Routes command: npm run test:route + - run: + name: Test - Coverage + command: npm run test:coverage workflows: build_and_test: @@ -71,4 +77,4 @@ workflows: - build - test: requires: - - build \ No newline at end of file + - build diff --git a/.eslintrc.js b/.eslintrc.js index bc8b5c8cd..dd4bcd0d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,7 +11,7 @@ module.exports = { browser : true, node : true }, - plugins : ['react'], + plugins : ['react', 'jest'], rules : { /** Errors **/ 'camelcase' : ['error', { properties: 'never' }], @@ -24,6 +24,7 @@ module.exports = { 'react/jsx-no-bind' : ['error', { allowArrowFunctions: true }], 'react/jsx-uses-react' : 'error', 'react/prefer-es6-class' : ['error', 'never'], + 'jest/valid-expect' : ['error', { maxArgs: 3 }], /** Warnings **/ 'max-lines' : ['warn', { diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 05eda75b4..b87b267e6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -12,10 +12,6 @@ body: description: The best feature requests provide an explanation of the current issue and then an explanation of how it could be improved. Screenshots/images can be pasted right in as well! validations: required: true - - type: checkboxes - id: terms + - type: markdown attributes: - label: "Please confirm:" - options: - - label: I have searched the Issues tracker for any duplicate requests and found none. - required: true \ No newline at end of file + value: "Please be sure to search for any close matches to your request in the GitHub Issues tracker before opening a new request, thanks!" diff --git a/.github/ISSUE_TEMPLATE/general_issue.yml b/.github/ISSUE_TEMPLATE/general_issue.yml index 33dd5b2bc..18c19254e 100644 --- a/.github/ISSUE_TEMPLATE/general_issue.yml +++ b/.github/ISSUE_TEMPLATE/general_issue.yml @@ -4,14 +4,15 @@ body: - type: markdown attributes: value: Please include as much information as possible. - - type: checkboxes + - type: dropdown id: renderer attributes: label: Renderer description: Which renderer does this issue occur on? If you are unsure, you can check the renderer in the Properties Editor (click the "i" in the Snippet Menu bar above the editor). options: - - label: Legacy - - label: v3 + - v3 + - Legacy + - Both validations: required: true - type: dropdown diff --git a/.gitignore b/.gitignore index 2f081e2dc..150d81008 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ config/docker.* todo.md startDB.bat startMViewer.bat +.vscode + +coverage diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 000000000..207dfda62 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,48 @@ +{ + "extends": [ + "stylelint-config-recess-order", + "stylelint-config-recommended"], + "plugins": [ + "stylelint-stylistic", + "./stylelint_plugins/declaration-colon-align.js", + "./stylelint_plugins/declaration-colon-min-space-before", + "./stylelint_plugins/declaration-block-multi-line-min-declarations" + ], + "customSyntax": "postcss-less", + "rules": { + "no-descending-specificity" : null, + "at-rule-no-unknown" : null, + "function-no-unknown" : null, + "font-family-no-missing-generic-family-keyword" : null, + "font-weight-notation" : "named-where-possible", + "font-family-name-quotes" : "always-unless-keyword", + "stylistic/indentation" : "tab", + "no-duplicate-selectors" : true, + "stylistic/color-hex-case" : "upper", + "color-hex-length" : "long", + "stylistic/selector-combinator-space-after" : "always", + "stylistic/selector-combinator-space-before" : "always", + "stylistic/selector-attribute-operator-space-before" : "never", + "stylistic/selector-attribute-operator-space-after" : "never", + "stylistic/selector-attribute-brackets-space-inside" : "never", + "selector-attribute-quotes" : "always", + "selector-pseudo-element-colon-notation" : "double", + "stylistic/selector-pseudo-class-parentheses-space-inside" : "never", + "stylistic/block-opening-brace-space-before" : "always", + "naturalcrit/declaration-colon-min-space-before" : 1, + "stylistic/declaration-block-trailing-semicolon" : "always", + "stylistic/declaration-colon-space-after" : "always", + "stylistic/number-leading-zero" : "always", + "function-url-quotes" : ["always", { "except": ["empty"] }], + "function-url-scheme-disallowed-list" : ["data","http"], + "comment-whitespace-inside" : "always", + "stylistic/string-quotes" : "single", + "stylistic/media-feature-range-operator-space-before" : "always", + "stylistic/media-feature-range-operator-space-after" : "always", + "stylistic/media-feature-parentheses-space-inside" : "never", + "stylistic/media-feature-colon-space-before" : "always", + "stylistic/media-feature-colon-space-after" : "always", + "naturalcrit/declaration-colon-align" : true, + "naturalcrit/declaration-block-multi-line-min-declarations": 1 + } +} diff --git a/Dockerfile b/Dockerfile index 33adea2b8..67b42b9dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.11-alpine +FROM node:16.13-alpine RUN apk --no-cache add git ENV NODE_ENV=docker @@ -10,11 +10,11 @@ WORKDIR /usr/src/app # This improves caching so we don't have to download the dependencies every time the code changes COPY package.json ./ # --ignore-scripts tells yarn not to run postbuild. We run it explicitly later -RUN yarn install --ignore-scripts +RUN npm install --ignore-scripts # Bundle app source and build application COPY . . -RUN yarn build +RUN npm run build EXPOSE 8000 -CMD [ "yarn", "start" ] +CMD [ "npm", "start" ] diff --git a/README.md b/README.md index 35f0150d1..df7f41503 100644 --- a/README.md +++ b/README.md @@ -21,24 +21,29 @@ below. First, install three programs that The Homebrewery requires to run and retrieve updates: -1. install [node](https://nodejs.org/en/) +1. install [node](https://nodejs.org/en/), version v16 or higher. 1. install [mongodb](https://www.mongodb.com/try/download/community) (Community version) For the easiest installation, follow these steps: 1. In the installer, uncheck the option to run as a service. 1. You can install MongoDB Compass if you want a GUI to view your database documents. + 1. If you install any version over 6.0, you will have to install [MongoDB Shell](https://www.mongodb.com/try/download/shell). 1. Go to the C:\ drive and create a folder called "data". 1. Inside the "data" folder, create a new folder called "db". - 1. Open a command prompt or other terminal and navigate to your MongoDB install folder (C:\Program Files\Mongo\Server\4.4\bin). + 1. Open a command prompt or other terminal and navigate to your MongoDB install folder (C:\Program Files\Mongo\Server\6.0\bin). 1. In the command prompt, run "mongod", which will start up your local database server. 1. While MongoD is running, open a second command prompt and navigate to the MongoDB install folder. - 1. In the second command prompt, run "mongo", which allows you to edit the database. - 1. Type `use homebrewery` to create The Homebrewery database. You should see `switched to db homebrewery`. - 1. Type `db.brews.insert({"title":"test"})` to create a blank document. You should see `WriteResult({ "nInserted" : 1 })`. 1. Search in Windows for "Advanced system settings" and open it. 1. Click "Environment variables", find the "path" variable, and double-click to open it. 1. Click "New" and paste in the path to the MongoDB "bin" folder. 1. Click "OK" three times to close all the windows. + 1. In the second command prompt, run "mongo", which allows you to edit the database. + 1. Type `use homebrewery` to create The Homebrewery database. You should see `switched to db homebrewery`. + 1. Type `db.brews.insertOne({"title":"test"})` to create a blank document. You should see `{ +acknowledged: true, +insertedId: ObjectId("63c2fce9e5ac5a94fe2410cf") +}` + 1. install [git](https://git-scm.com/downloads) (select the option that allows Git to run from the command prompt). Checkout the repo ([documentation][github-clone-repo-docs-url]): @@ -51,11 +56,19 @@ git clone https://github.com/naturalcrit/homebrewery.git Second, you will need to add the environment variable `NODE_ENV=local` to allow the project to run locally. -You can set this temporarily in your shell of choice: +You can set this **temporarily** (until you close the terminal) in your shell of choice with admin privileges: * Windows Powershell: `$env:NODE_ENV="local"` * Windows CMD: `set NODE_ENV=local` * Linux / macOS: `export NODE_ENV=local` +If you want to add this variable **permanently** the steps are as follows: + 1. Search in Windows for "Advanced system settings" and open it. + 1. Click "Environment variables". + 1. In System Variables, click "New" + 1. Click "New" and write `NODE_ENV` as a name and `local` as the value. + 1. Click "OK" three times to close all the windows. + This can be undone at any time if needed. + Third, you will need to install the Node dependencies, compile the app, and run it using the two commands: @@ -65,6 +78,13 @@ it using the two commands: You should now be able to go to [http://localhost:8000](http://localhost:8000) in your browser and use The Homebrewery offline. +If you had any issue at all, here are some links that may be useful: +- [Course](https://learn.mongodb.com/courses/m103-basic-cluster-administration) on cluster administration, useful for beginners +- [Mongo community forums](https://www.mongodb.com/community/forums/) +- Useful Stack Overflow links for your most probable errors: [1](https://stackoverflow.com/questions/44962540/mongod-and-mongo-commands-not-working-on-windows-10), [2](https://stackoverflow.com/questions/15053893/mongo-command-not-recognized-when-trying-to-connect-to-a-mongodb-server/41507803#41507803), [3](https://stackoverflow.com/questions/51224959/mongo-is-not-recognized-as-an-internal-or-external-command-operable-program-o) + +If you still have problems, post in [Our Subreddit](https://www.reddit.com/r/homebrewery/) and we will help you. + ### Running the application via Docker Please see the docs here: [README.DOCKER.md](./README.DOCKER.md) diff --git a/changelog.md b/changelog.md index fbf376035..bfdb278e4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,23 @@ ```css +.beta { + color : white; + padding : 4px 6px; + line-height : 1em; + background : grey; + border-radius : 12px; + font-family : monospace; + font-size : 10px; + font-weight : 800; + margin-top : -5px; + margin-bottom : -5px; +} + +.fac { + height: 1em; + line-height: 2em; + margin-bottom: -0.05cm +} + h5 { font-size: .35cm !important; } @@ -7,6 +26,11 @@ h5 { margin-left: 0px; } +.page .taskList { + display:block; + break-inside:auto; +} + .taskList li input { list-style-type : none; margin-left : -0.52cm; @@ -14,6 +38,11 @@ h5 { filter: brightness(1.1) drop-shadow(1px 2px 1px #222); } +.taskList ul { + margin-bottom: 0px; + margin-top: 0px; +} + .taskList li input[checked] { filter: sepia(100%) hue-rotate(60deg) saturate(3.5) contrast(4) brightness(1.1) drop-shadow(1px 2px 1px #222); } @@ -30,15 +59,374 @@ pre { margin-top : 0.1cm; } +.page ul + h5 { + margin-top: 0.25cm; +} + +.page p + h5 { + margin-top: 0.25cm; +} + .page .openSans { font-family: 'Open Sans'; font-size: 0.9em; } + +.page { + padding-bottom: 1.5cm; +} ``` ## changelog For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). +### Friday 02/06/2023 - v3.9.0 +{{taskList + +##### Calculuschild + +* [x] Fix some files not showing up on userpage when user has a large number of brews in Google Drive + +Fixes issue [#2408](https://github.com/naturalcrit/homebrewery/issues/2408) + +* [x] Pressing tab now indents with spaces instead of tab character; fixes several issues with Markdown lists + +Fixes issues [#2092](https://github.com/naturalcrit/homebrewery/issues/2092), [#1556](https://github.com/naturalcrit/homebrewery/issues/1556) + +* [x] Rename `naturalCritLogo.svg` to `naturalCritLogoRed.svg`. Those using the {{beta BETA}} coverPage snippet may need to update that text to make the NaturalCrit logo appear again. + +##### G-Ambatte + +* [x] Fix strange animation of image masks + +Fixes issue [#2790](https://github.com/naturalcrit/homebrewery/issues/2790) + +##### 5e-Cleric + +* [x] New {{openSans **PHB → {{fac,book-part-cover}} PART COVER PAGE** }} snippet for V3! + +* [x] New {{openSans **PHB → {{fac,book-back-cover}} BACK COVER PAGE** }} snippet for V3! (Thanks to /u/Kaiburr_Kath-Hound on Reddit for providing some of these resources!) + +* [x] New {{openSans **TEXT EDITOR → {{fas,fa-bars}} INDEX** }} snippet for V3! + +* [x] Fix highlighting of curly braces inside comments + +Fixes issue [#2784](https://github.com/naturalcrit/homebrewery/issues/2784) +}} + +### Wednesday 12/04/2023 - v3.8.0 +{{taskList + +##### calculuschild + +* [x] Rename `{{coverPage}}` to `{{frontCover}}`. Those using this {{beta BETA}} feature will need to update that text to make the cover page appear again. + +* [x] Several background fixes to test scripts + +##### Jeddai + +* [X] Add content negotiation to exclude image requests from our API calls + +Fixes issue [#2595](https://github.com/naturalcrit/homebrewery/issues/2595) + +##### G-Ambatte + +* [x] Update server build scripts to fix Admin page + +Fixes issues [#2657](https://github.com/naturalcrit/homebrewery/issues/2657) + +* [x] Fix internal links inside `<\div>` blocks not receiving the `target=_self` attribute + +Fixes issues [#2680](https://github.com/naturalcrit/homebrewery/issues/2680) + +* [x] See brew details on `/share` pages by clicking the brew title (author, last update, tags, etc.) + +Fixes issues [#1679](https://github.com/naturalcrit/homebrewery/issues/1679) + +* [x] Add local Windows install script via Chocolatey + +##### 5e-Cleric + +* [x] New {{openSans **TABLES → {{fas,fa-language}} RUNE TABLE**}} snippets for V3. Adds an alphabetic script translation table. + +* [x] New {{openSans **IMAGES → {{fac,mask-center}} WATERCOLOR CENTER** }} snippets for V3, which adds a stylish watercolor texture to the center of your images! + +* [x] New {{openSans **PHB → {{fac,book-inside-cover}} INSIDE COVER PAGE** }} snippet for V3! (Thanks to /u/Kaiburr_Kath-Hound on Reddit for providing some of these resources!) + +* [x] Add some missing characters {{font-family:scalySansRemake Ñ ñ ç Ç Ý ý # ^ ¿ ' " ¡ ·}} to the "scalySansRemake" font in V3. + +Fixes issues [#2280](https://github.com/naturalcrit/homebrewery/issues/2280) + +##### Gazook89 + +* [x] Add "Language" selector in {{fa,fa-info-circle}} **Properties** menu. Sets the HTML Lang attribute for your brew to better handle hyphenation or spellcheck. + +Fixes issues [#1343](https://github.com/naturalcrit/homebrewery/issues/1343) + +* [x] Fix a crash when multiple `{injection}` tags appear in sequence + +Fixes issues [#2712](https://github.com/naturalcrit/homebrewery/issues/2712) + +##### MichielDeMey + +* [x] Remove all-caps display on Account button since usernames are case-sensitive. + +Fixes issues [#2731](https://github.com/naturalcrit/homebrewery/issues/2731) + +}} + +\page + +### Monday 13/03/2023 - v3.7.2 +{{taskList + +##### Calculuschild + +* [x] Fix wide Monster Stat Blocks not spanning columns on Legacy +}} + +### Thursday 09/03/2023 - v3.7.1 +{{taskList + +##### Lucastucious (new contributor!) + +* [x] Changed `filter: drop-shadow` to `box-shadow` on text boxes, making PDF text selectable + +Fixes issues [#1569](https://github.com/naturalcrit/homebrewery/issues/1569) + +{{note +**NOTE:** If you create your PDF on a computer with an old version of Mac Preview (v10 or older) you may see shadows appear as solid gray. +}} + +##### MichielDeMey + +* [x] Updated the Google Drive icon +* [x] Backend fix to unit tests failing intermittently + +##### Calculuschild + +* [x] Fix PDF pixelation on CoverPage text outlines +}} + +### Tuesday 28/02/2023 - v3.7.0 +{{taskList + +{{note +**NOTE:** Some new snippets will now show a {{beta BETA}} tag. Feel free to use them, but be aware we may change how they work depending on your feedback. +}} + +##### Calculuschild + +* [x] New {{openSans **IMAGES → WATERCOLOR EDGE** {{fac,mask-edge}} }} and {{openSans **WATERCOLOR CORNER** {{fac,mask-corner}} }} snippets for V3, which adds a stylish watercolor texture to the edge of your images! (Thanks to /u/flamableconcrete on Reddit for providing these image masks!) + +* [x] Fix site not displaying on iOS devices + +##### 5e-Cleric + +* [x] New {{openSans **PHB → COVER PAGE** {{fac,book-front-cover}} }} snippet for V3, which adds a stylish coverpage to your brew! (Thanks to /u/Kaiburr_Kath-Hound on Reddit for providing some of these resources!) + +##### MichielDeMey (new contribuor!) + +* [x] Fix typo in testing scripts +* [x] Fix "mug" image not using HTTPS + +Fixes issues [#2687](https://github.com/naturalcrit/homebrewery/issues/2687) +}} + +### Saturday 18/02/2023 - v3.6.1 +{{taskList +##### G-Ambatte + +* [x] Fix users not being removed from Authors list + +Fixes issues [#2674](https://github.com/naturalcrit/homebrewery/issues/2674) +}} + +### Monday 23/01/2023 - v3.6.0 +{{taskList +##### calculuschild + +* [x] Fix Google Drive brews sometimes duplicating + +Fixes issues [#2603](https://github.com/naturalcrit/homebrewery/issues/2603) + +##### Jeddai + +* [x] Add unit tests with full coverage for the Homebrewery API + +* [x] Add message to refresh the browser if the user is missing an update to the Homebrewery + +Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583) + +##### G-Ambatte + +* [x] Auto-compile Themes CSS on development server + +##### 5e-Cleric + +* [x] Fix cloned brews inheriting the parent view count +}} + +\page + +### Friday 23/12/2022 - v3.5.0 +{{taskList + +##### Jeddai + +* [x] Only brew owners or invited authors can edit a brew + + - Visiting an `/edit` page of a brew that does not list you as an author will result in an error page. Authors can be added to any brew by opening its {{fa,fa-info-circle}} **Properties** menu and typing the author's username (case-sensitive) into the **Invited Authors** bubble. + - Warn user if a newer brew version has been saved on another device + +Fixes issues [#1987](https://github.com/naturalcrit/homebrewery/issues/1987) +}} + +### Saturday 10/12/2022 - v3.4.2 +{{taskList + +##### Jeddai + +* [x] Fix broken tags editor + +* [x] Reduce server load to fix some saving issues + +Fixes issues [#2322](https://github.com/naturalcrit/homebrewery/issues/2322) + +##### G-Ambatte + +* [x] Account page help link for Google Drive errors + +Fixes issues [#2520](https://github.com/naturalcrit/homebrewery/issues/2520) +}} + +### Monday 05/12/2022 - v3.4.1 +{{taskList + +##### G-Ambatte + +* [x] Fix Account page incorrect last login time + +Fixes issues [#2521](https://github.com/naturalcrit/homebrewery/issues/2521) + +##### Gazook + +* [x] Fix crashing on iOS and Safari browsers + +Fixes issues [#2531](https://github.com/naturalcrit/homebrewery/issues/2531) +}} + +### Monday 28/11/2022 - v3.4.0 +{{taskList + +##### G-Ambatte + +* [x] Fix for Chrome v108 handling of page size + +Fixes issues [#2445](https://github.com/naturalcrit/homebrewery/issues/2445), [#2516](https://github.com/naturalcrit/homebrewery/issues/2516) + +* [x] New account page with some user info, at {{openSans **USERNAME {{fa,fa-user}} → ACCOUNT {{fa,fa-user}}**}} + +Fixes issues [#2049](https://github.com/naturalcrit/homebrewery/issues/2049), [#2043](https://github.com/naturalcrit/homebrewery/issues/2043) + +* [x] Fix "Published/Private Brews" buttons on userpage + +Fixes issues [#2449](https://github.com/naturalcrit/homebrewery/issues/2449) + +##### Gazook + +* [x] Make autosave default on for new users + +* [x] Added link to our FAQ at {{openSans **NEED HELP? {{fa,fa-question-circle}} → FAQ {{fa,fa-question-circle}}**}} + +* [x] Fix curly blocks freezing with long property lists + +Fixes issues [#2393](https://github.com/naturalcrit/homebrewery/issues/2393) + +* [x] Items can now be removed from {{openSans **RECENT BREWS** {{fas,fa-history}} }} + +Fixes issues [#1918](https://github.com/naturalcrit/homebrewery/issues/1918) + +* [x] Curly injector syntax `{blue}` highlighting in editor + +Fixes issues [#1670](https://github.com/naturalcrit/homebrewery/issues/1670) + +}} + +### Thursday 28/10/2022 - v3.3.1 +{{taskList + +##### Calculuschild + +* [x] Fixes to several broken CSS styles from v3.3.0 + +Fixes issues [#2468](https://github.com/naturalcrit/homebrewery/issues/2468) + +##### Jeddai + +* [x] Reduce size of thumbnails on social media links + +}} + +### Friday 19/10/2022 - v3.3.0 +{{taskList + +##### Calculuschild + +* [x] Fix for tables broken by Chrome v106 + + +##### G-Ambatte: + +* [x] Fix Table of Contents broken by Chrome v106 + +Fixes issues [#2437](https://github.com/naturalcrit/homebrewery/issues/2437) + +* [x] Show brew thumbnails on user page + +Fixes issues [#2331](https://github.com/naturalcrit/homebrewery/issues/2331) + +* [x] Allow longer URLs for brew thumbnails + +Fixes issues [#2351](https://github.com/naturalcrit/homebrewery/issues/2351) + +* [x] Code no longer unfolds when inserting a snippet + +Fixes issues [#2135](https://github.com/naturalcrit/homebrewery/issues/2135) + +* [x] Fix brew settings being lost on first save + +Fixes issues [#2427](https://github.com/naturalcrit/homebrewery/issues/2427) + +##### Gazook: + +* [x] Several updates to bug reporting and error popups + +Fixes issues [#2376](https://github.com/naturalcrit/homebrewery/issues/2376) + +* [x] Fixes to userpage search bar + +Fixes issues [#1675](https://github.com/naturalcrit/homebrewery/issues/1675), [#2353](https://github.com/naturalcrit/homebrewery/issues/2353) + +* [x] Renderer *(legacy / V3)* now shown next to page # + +Fixes issues [#1928](https://github.com/naturalcrit/homebrewery/issues/1928) + +* [x] Prevent text selection when moving divider bar + +Fixes issues [#1632](https://github.com/naturalcrit/homebrewery/issues/1632) + +* [x] Tweak Monster Stat Block coloring + +Fixes issues [#2123](https://github.com/naturalcrit/homebrewery/issues/2123) + +* [x] Added dropdown button to toggle autosave + +Fixes issues [#1546](https://github.com/naturalcrit/homebrewery/issues/1546) + +}} + + ### Friday 08/09/2022 - v3.2.2 {{taskList @@ -59,6 +447,10 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [ Fixes issues: [#1797](https://github.com/naturalcrit/homebrewery/issues/1797), [#2315](https://github.com/naturalcrit/homebrewery/issues/2315), [#2326](https://github.com/naturalcrit/homebrewery/issues/2326), [#2328](https://github.com/naturalcrit/homebrewery/issues/2328) }} + + +\page + ### Wednesday 31/08/2022 - v3.2.1 {{taskList @@ -85,8 +477,6 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [ Fixes issues: [#2301](https://github.com/naturalcrit/homebrewery/issues/2301), [#2303](https://github.com/naturalcrit/homebrewery/issues/2303), [#2121](https://github.com/naturalcrit/homebrewery/issues/2121) }} -\page - ### Saturday 27/08/2022 - v3.2.0 {{taskList diff --git a/client/components/combobox.jsx b/client/components/combobox.jsx new file mode 100644 index 000000000..a6e699dcf --- /dev/null +++ b/client/components/combobox.jsx @@ -0,0 +1,129 @@ +const React = require('react'); +const createClass = require('create-react-class'); +const _ = require('lodash'); +const cx = require('classnames'); +require('./combobox.less'); + +const Combobox = createClass({ + displayName : 'Combobox', + getDefaultProps : function() { + return { + className : '', + trigger : 'hover', + default : '', + placeholder : '', + autoSuggest : { + clearAutoSuggestOnClick : true, + suggestMethod : 'includes', + filterOn : [] // should allow as array to filter on multiple attributes, or even custom filter + }, + }; + }, + getInitialState : function() { + return { + showDropdown : false, + value : '', + options : [...this.props.options], + inputFocused : false + }; + }, + componentDidMount : function() { + if(this.props.trigger == 'click') + document.addEventListener('click', this.handleClickOutside); + this.setState({ + value : this.props.default + }); + }, + 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, + inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false + }); + }, + handleInput : function(e){ + e.persist(); + this.setState({ + value : e.target.value, + inputFocused : false + }, ()=>{ + this.props.onEntry(e); + }); + }, + handleSelect : function(e){ + this.setState({ + value : e.currentTarget.getAttribute('data-value') + }, ()=>{this.props.onSelect(this.state.value);}); + ; + }, + renderTextInput : function(){ + return ( +
{this.handleDropdown(true);} : undefined} + onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}> + this.handleInput(e)} + value={this.state.value || ''} + placeholder={this.props.placeholder} + onBlur={(e)=>{ + if(!e.target.checkValidity()){ + this.setState({ + value : this.props.default + }, ()=>this.props.onEntry(e)); + } + }} + /> +
+ ); + }, + renderDropdown : function(dropdownChildren){ + if(!this.state.showDropdown) return null; + if(this.props.autoSuggest && !this.state.inputFocused){ + const suggestMethod = this.props.autoSuggest.suggestMethod; + const filterOn = _.isString(this.props.autoSuggest.filterOn) ? [this.props.autoSuggest.filterOn] : this.props.autoSuggest.filterOn; + const filteredArrays = filterOn.map((attr)=>{ + const children = dropdownChildren.filter((item)=>{ + if(suggestMethod === 'includes'){ + return item.props[attr]?.toLowerCase().includes(this.state.value.toLowerCase()); + } else if(suggestMethod === 'startsWith'){ + return item.props[attr]?.toLowerCase().startsWith(this.state.value.toLowerCase()); + } + }); + return children; + }); + dropdownChildren = _.uniq(filteredArrays.flat(1)); + } + + return ( +
+ {dropdownChildren} +
+ ); + }, + render : function () { + const dropdownChildren = this.state.options.map((child, i)=>{ + const clone = React.cloneElement(child, { onClick: (e)=>this.handleSelect(e) }); + return clone; + }); + return ( +
{this.handleDropdown(false);} : undefined}> + {this.renderTextInput()} + {this.renderDropdown(dropdownChildren)} +
+ ); + } +}); + +module.exports = Combobox; diff --git a/client/components/combobox.less b/client/components/combobox.less new file mode 100644 index 000000000..3810a874e --- /dev/null +++ b/client/components/combobox.less @@ -0,0 +1,50 @@ +.dropdown-container { + position:relative; + input { + width: 100%; + } + .dropdown-options { + position:absolute; + background-color: white; + z-index: 100; + width: 100%; + border: 1px solid gray; + overflow-y: auto; + max-height: 200px; + + &::-webkit-scrollbar { + width: 14px; + } + &::-webkit-scrollbar-track { + background: #ffffff; + } + &::-webkit-scrollbar-thumb { + background-color: #949494; + border-radius: 10px; + border: 3px solid #ffffff; + } + + .item { + position:relative; + font-size: 11px; + font-family: Open Sans; + padding: 5px; + cursor: default; + margin: 0 3px; + //border-bottom: 1px solid darkgray; + &:hover { + filter: brightness(120%); + background-color: rgb(163, 163, 163); + } + .detail { + width:100%; + text-align: left; + color: rgb(124, 124, 124); + font-style:italic; + font-size: 9px; + } + } + + } + +} diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 654806886..27fef7e16 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -27,6 +27,7 @@ const BrewRenderer = createClass({ style : '', renderer : 'legacy', theme : '5ePHB', + lang : '', errors : [] }; }, @@ -134,7 +135,8 @@ const BrewRenderer = createClass({ renderStyle : function() { if(!this.props.style) return; - return
${this.props.style} ` }} />; + //return
@layer styleTab {\n${this.props.style}\n} ` }} />; + return
\n${this.props.style}\n` }} />; }, renderPage : function(pageText, index){ @@ -189,7 +191,6 @@ const BrewRenderer = createClass({ const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy'; const themePath = this.props.theme ?? '5ePHB'; const baseThemePath = Themes[rendererPath][themePath].baseTheme; - return ( {!this.state.isMounted @@ -222,7 +223,7 @@ const BrewRenderer = createClass({ && <> {this.renderStyle()} -
+
{this.renderPages()}
diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx index f0a0f5f1f..3c706d6f7 100644 --- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx +++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx @@ -4,7 +4,7 @@ const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); //Unused variable -const DISMISS_KEY = 'dismiss_notification08-27-22'; +const DISMISS_KEY = 'dismiss_notification12-04-23'; const NotificationPopup = createClass({ displayName : 'NotificationPopup', @@ -25,21 +25,13 @@ const NotificationPopup = createClass({ return ( <>
  • - V3.2.0 Released!
    - We are happy to announce that after nearly a year of use by our many users, - we are making the V3 render mode the default setting for all new brews. - This mode has become quite popular, and has proven to be stable and powerful. - Of course, we will always keep the option to use the Legacy renderer for any - brew, which can still be accessed from the Properties menu. -
  • - -
  • - Change to Google Drive Storage!
    - We have made a change to the process of tranferring brews between Google - Drive and the Homebrewery storage. Starting now, any time a brew is - transferred, it will keep the same links instead of generating new ones! - We hope this change will help reduce issues where people "lost" their work - by trying to visit old links. + Broken default logo on CoverPage
    + If you have used the Cover Page snippet and notice the Naturalcrit + logo is showing as a broken image, this is due to some small tweaks + of this BETA feature. To fix the logo in your cover page, rename + the image link "/assets/naturalCritLogoRed.svg". Remember + that any snippet marked "BETA" may have a similar change in the + future as we encounter any bugs or reworks.
  • diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index de3a53a36..58e84e4fc 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -19,11 +19,6 @@ const DEFAULT_STYLE_TEXT = dedent` color: black; }`; -const splice = function(str, index, inject){ - return str.slice(0, index) + inject + str.slice(index); -}; - - const Editor = createClass({ displayName : 'Editor', @@ -37,6 +32,7 @@ const Editor = createClass({ onTextChange : ()=>{}, onStyleChange : ()=>{}, onMetaChange : ()=>{}, + reportError : ()=>{}, renderer : 'legacy' }; @@ -80,19 +76,7 @@ const Editor = createClass({ }, handleInject : function(injectText){ - let text; - if(this.isText()) text = this.props.brew.text; - if(this.isStyle()) text = this.props.brew.style ?? DEFAULT_STYLE_TEXT; - - const lines = text.split('\n'); - const cursorPos = this.refs.codeEditor.getCursorPosition(); - lines[cursorPos.line] = splice(lines[cursorPos.line], cursorPos.ch, injectText); - - const injectLines = injectText.split('\n'); - this.refs.codeEditor.setCursorPosition(cursorPos.line + injectLines.length, cursorPos.ch + injectLines[injectLines.length - 1].length); - - if(this.isText()) this.props.onTextChange(lines.join('\n')); - if(this.isStyle()) this.props.onStyleChange(lines.join('\n')); + this.refs.codeEditor?.injectText(injectText, false); }, handleViewChange : function(newView){ @@ -154,9 +138,17 @@ const Editor = createClass({ codeMirror.addLineClass(lineNumber, 'text', 'columnSplit'); } + // Highlight injectors {style} + if(line.includes('{') && line.includes('}')){ + const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm; + let match; + while ((match = regex.exec(line)) != null) { + codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' }); + } + } // Highlight inline spans {{content}} if(line.includes('{{') && line.includes('}}')){ - const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g; + const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g; let match; let blockCount = 0; while ((match = regex.exec(line)) != null) { @@ -175,7 +167,7 @@ const Editor = createClass({ // Highlight block divs {{\n Content \n}} let endCh = line.length+1; - const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/); + const match = line.match(/^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/); if(match) endCh = match.index+match[0].length; codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); @@ -300,7 +292,8 @@ const Editor = createClass({ rerenderParent={this.rerenderParent} /> + onChange={this.props.onMetaChange} + reportError={this.props.reportError}/> ; } }, diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index 810ee1710..86e523a13 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -19,16 +19,20 @@ background-color : fade(#299, 15%); border-bottom : #299 solid 1px; } - .block{ + .block:not(.cm-comment){ color : purple; font-weight : bold; //font-style: italic; } - .inline-block{ + .inline-block:not(.cm-comment){ color : red; font-weight : bold; //font-style: italic; } + .injection:not(.cm-comment){ + color : green; + font-weight : bold; + } } .brewJump{ diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 25b4daac8..074879b05 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -4,16 +4,24 @@ const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); -const request = require('superagent'); +const request = require('../../utils/request-middleware.js'); const Nav = require('naturalcrit/nav/nav.jsx'); +const Combobox = require('client/components/combobox.jsx'); const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx'); const Themes = require('themes/themes.json'); +const validations = require('./validations.js'); const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']; const homebreweryThumbnail = require('../../thumbnail.png'); +const callIfExists = (val, fn, ...args)=>{ + if(val[fn]) { + val[fn](...args); + } +}; + const MetadataEditor = createClass({ displayName : 'MetadataEditor', getDefaultProps : function() { @@ -22,14 +30,17 @@ const MetadataEditor = createClass({ editId : null, title : '', description : '', + thumbnail : '', tags : [], published : false, authors : [], systems : [], renderer : 'legacy', - theme : '5ePHB' + theme : '5ePHB', + lang : 'en' }, - onChange : ()=>{} + onChange : ()=>{}, + reportError : ()=>{} }; }, @@ -51,11 +62,28 @@ const MetadataEditor = createClass({ }, handleFieldChange : function(name, e){ - this.props.onChange({ - ...this.props.metadata, - [name] : e.target.value - }); + // load validation rules, and check input value against them + const inputRules = validations[name] ?? []; + const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean); + + // if no validation rules, save to props + if(validationErr.length === 0){ + callIfExists(e.target, 'setCustomValidity', ''); + this.props.onChange({ + ...this.props.metadata, + [name] : e.target.value + }); + } else { + // if validation issues, display built-in browser error popup with each error. + const errMessage = validationErr.map((err)=>{ + return `- ${err}`; + }).join('\n'); + + callIfExists(e.target, 'setCustomValidity', errMessage); + callIfExists(e.target, 'reportValidity'); + } }, + handleSystem : function(system, e){ if(e.target.checked){ this.props.metadata.systems.push(system); @@ -64,6 +92,7 @@ const MetadataEditor = createClass({ } this.props.onChange(this.props.metadata); }, + handleRenderer : function(renderer, e){ if(e.target.checked){ this.props.metadata.renderer = renderer; @@ -85,6 +114,11 @@ const MetadataEditor = createClass({ this.props.onChange(this.props.metadata); }, + handleLanguage : function(languageCode){ + this.props.metadata.lang = languageCode; + this.props.onChange(this.props.metadata); + }, + handleDelete : function(){ if(this.props.metadata.authors && this.props.metadata.authors.length <= 1){ if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return; @@ -96,8 +130,12 @@ const MetadataEditor = createClass({ request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`) .send() - .end(function(err, res){ - window.location.href = '/'; + .end((err, res)=>{ + if(err) { + this.props.reportError(err); + } else { + window.location.href = '/'; + } }); }, @@ -159,6 +197,10 @@ const MetadataEditor = createClass({ return
    this.handleTheme(theme)} title={''}> {`${theme.renderer} : ${theme.name}`} +
    +
    {`${theme.name}`} preview
    + +
    ; }); }; @@ -168,14 +210,14 @@ const MetadataEditor = createClass({ if(this.props.metadata.renderer == 'legacy') { dropdown = - +
    {`Themes are not supported in the Legacy Renderer`}
    ; } else { dropdown = - +
    {`${_.upperFirst(currentTheme.renderer)} : ${currentTheme.name}`}
    @@ -190,6 +232,47 @@ const MetadataEditor = createClass({
  • ; }, + renderLanguageDropdown : function(){ + const langCodes = ['en', 'de', 'de-ch', 'fr', 'ja', 'es', 'it', 'sv', 'ru', 'zh-Hans', 'zh-Hant']; + const listLanguages = ()=>{ + return _.map(langCodes.sort(), (code, index)=>{ + const localName = new Intl.DisplayNames([code], { type: 'language' }); + const englishName = new Intl.DisplayNames('en', { type: 'language' }); + return
    + {`${code}`} +
    {`${localName.of(code)}`}
    +
    ; + }); + }; + + const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500); + + return
    + +
    + this.handleLanguage(value)} + onEntry={(e)=>{ + e.target.setCustomValidity(''); //Clear the validation popup while typing + debouncedHandleFieldChange('lang', e); + }} + options={listLanguages()} + autoSuggest={{ + suggestMethod : 'startsWith', + clearAutoSuggestOnClick : true, + filterOn : ['data-value', 'data-detail', 'title'] + }} + > + + Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck. +
    + +
    ; + }, + renderRenderOptions : function(){ if(!global.enable_v3) return; @@ -225,24 +308,26 @@ const MetadataEditor = createClass({ render : function(){ return
    +

    Brew

    +
    this.handleFieldChange('title', e)} />
    -