Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
844b6ef563 |
@@ -5,12 +5,12 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
node: circleci/node@5.1.0
|
||||
node: circleci/node@3.0.0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cimg/node:20.8.0
|
||||
- image: cimg/node:16.11.0
|
||||
- image: mongo:4.4
|
||||
|
||||
working_directory: ~/homebrewery
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
|
||||
- run: sudo npm install -g npm@10.2.0
|
||||
- run: sudo npm install -g npm@8.10.0
|
||||
- node/install-packages:
|
||||
app-dir: ~/homebrewery
|
||||
cache-path: node_modules
|
||||
@@ -45,40 +45,25 @@ jobs:
|
||||
|
||||
test:
|
||||
docker:
|
||||
- image: cimg/node:20.8.0
|
||||
- image: cimg/node:16.11.0
|
||||
|
||||
working_directory: ~/homebrewery
|
||||
parallelism: 1
|
||||
parallelism: 4
|
||||
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
|
||||
# run tests!
|
||||
- run:
|
||||
name: Test - API Unit Tests
|
||||
command: npm run test:api-unit
|
||||
- run:
|
||||
name: Test - Basic
|
||||
command: npm run test:basic
|
||||
- run:
|
||||
name: Test - Mustache Spans
|
||||
command: npm run test:mustache-syntax
|
||||
- run:
|
||||
name: Test - Definition Lists
|
||||
command: npm run test:definition-lists
|
||||
- run:
|
||||
name: Test - Hard Breaks
|
||||
command: npm run test:hard-breaks
|
||||
- run:
|
||||
name: Test - Variables
|
||||
command: npm run test:variables
|
||||
command: npm run test:mustache-span
|
||||
- run:
|
||||
name: Test - Routes
|
||||
command: npm run test:route
|
||||
- run:
|
||||
name: Test - Coverage
|
||||
command: npm run test:coverage
|
||||
|
||||
workflows:
|
||||
build_and_test:
|
||||
|
||||
78
.eslintrc.js
Normal file
@@ -0,0 +1,78 @@
|
||||
module.exports = {
|
||||
root : true,
|
||||
parserOptions : {
|
||||
ecmaVersion : 2021,
|
||||
sourceType : 'module',
|
||||
ecmaFeatures : {
|
||||
jsx : true
|
||||
}
|
||||
},
|
||||
env : {
|
||||
browser : true,
|
||||
node : true
|
||||
},
|
||||
plugins : ['react'],
|
||||
rules : {
|
||||
/** Errors **/
|
||||
'camelcase' : ['error', { properties: 'never' }],
|
||||
'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
|
||||
'no-array-constructor' : 'error',
|
||||
'no-iterator' : 'error',
|
||||
'no-nested-ternary' : 'error',
|
||||
'no-new-object' : 'error',
|
||||
'no-proto' : 'error',
|
||||
'react/jsx-no-bind' : ['error', { allowArrowFunctions: true }],
|
||||
'react/jsx-uses-react' : 'error',
|
||||
'react/prefer-es6-class' : ['error', 'never'],
|
||||
|
||||
/** Warnings **/
|
||||
'max-lines' : ['warn', {
|
||||
max : 200,
|
||||
skipComments : true,
|
||||
skipBlankLines : true,
|
||||
}],
|
||||
'max-depth' : ['warn', { max: 4 }],
|
||||
'max-params' : ['warn', { max: 5 }],
|
||||
'no-restricted-syntax' : ['warn', 'ClassDeclaration', 'SwitchStatement'],
|
||||
'no-unused-vars' : ['warn', {
|
||||
vars : 'all',
|
||||
args : 'none',
|
||||
varsIgnorePattern : 'config|_|cx|createClass'
|
||||
}],
|
||||
'react/jsx-uses-vars' : 'warn',
|
||||
|
||||
/** Fixable **/
|
||||
'arrow-parens' : ['warn', 'always'],
|
||||
'brace-style' : ['warn', '1tbs', { allowSingleLine: true }],
|
||||
'jsx-quotes' : ['warn', 'prefer-single'],
|
||||
'no-var' : 'warn',
|
||||
'prefer-const' : 'warn',
|
||||
'prefer-template' : 'warn',
|
||||
'quotes' : ['warn', 'single', { 'allowTemplateLiterals': true }],
|
||||
'semi' : ['warn', 'always'],
|
||||
|
||||
/** Whitespace **/
|
||||
'array-bracket-spacing' : ['warn', 'never'],
|
||||
'arrow-spacing' : ['warn', { before: false, after: false }],
|
||||
'comma-spacing' : ['warn', { before: false, after: true }],
|
||||
'indent' : ['warn', 'tab', { 'MemberExpression': 'off' }],
|
||||
'keyword-spacing' : ['warn', {
|
||||
before : true,
|
||||
after : true,
|
||||
overrides : {
|
||||
if : { 'before': false, 'after': false }
|
||||
}
|
||||
}],
|
||||
'key-spacing' : ['warn', {
|
||||
multiLine : { beforeColon: true, afterColon: true, align: 'colon' },
|
||||
singleLine : { beforeColon: false, afterColon: true }
|
||||
}],
|
||||
'linebreak-style' : 'off',
|
||||
'no-trailing-spaces' : 'warn',
|
||||
'no-whitespace-before-property' : 'warn',
|
||||
'object-curly-spacing' : ['warn', 'always'],
|
||||
'react/jsx-indent-props' : ['warn', 'tab'],
|
||||
'space-in-parens' : ['warn', 'never'],
|
||||
'template-curly-spacing' : ['warn', 'never'],
|
||||
}
|
||||
};
|
||||
103
.github/actions/limit-pull-requests/action.yml
vendored
@@ -1,103 +0,0 @@
|
||||
name: Limit pull requests
|
||||
description: >
|
||||
Limit the number of open pull requests to the repository created by a user
|
||||
author: ZhongRuoyu (from Homebrew repository)
|
||||
branding:
|
||||
icon: alert-triangle
|
||||
color: yellow
|
||||
|
||||
inputs:
|
||||
token:
|
||||
description: GitHub token
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
except-users:
|
||||
description: The users exempted from the limit, one per line
|
||||
required: false
|
||||
# https://docs.github.com/en/graphql/reference/enums#commentauthorassociation
|
||||
except-author-associations:
|
||||
description: The author associations exempted from the limit, one per line
|
||||
required: false
|
||||
comment-limit:
|
||||
description: >
|
||||
Post the comment when the user's number of open pull requests exceeds this
|
||||
number and `comment` is not empty
|
||||
required: true
|
||||
default: "10"
|
||||
comment:
|
||||
description: The comment to post when the limit is reached
|
||||
required: false
|
||||
close-limit:
|
||||
description: >
|
||||
Close the pull request when the user's number of open pull requests
|
||||
exceeds this number and `close` is set to `true`
|
||||
required: true
|
||||
default: "50"
|
||||
close:
|
||||
description: Whether to close the pull request when the limit is reached
|
||||
required: true
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Check the number of pull requests
|
||||
id: count-pull-requests
|
||||
run: |
|
||||
# If the user is exempted, assume they have no pull requests.
|
||||
if grep -Fiqx '${{ github.actor }}' <<<"$EXCEPT_USERS"; then
|
||||
echo "::notice::@${{ github.actor }} is exempted from the limit."
|
||||
echo "count=0" >>"$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if grep -Fiqx '${{ github.event.pull_request.author_association }}' <<<"$EXCEPT_AUTHOR_ASSOCIATIONS"; then
|
||||
echo "::notice::@{{ github.actor }} is a ${{ github.event.pull_request.author_association }} exempted from the limit."
|
||||
echo "count=0" >>"$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
count="$(
|
||||
gh api \
|
||||
--method GET \
|
||||
--header 'Accept: application/vnd.github+json' \
|
||||
--header 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
--field state=open \
|
||||
--paginate \
|
||||
'/repos/{owner}/{repo}/pulls' |
|
||||
jq \
|
||||
--raw-output \
|
||||
--arg USER '${{ github.actor }}' \
|
||||
'map(select(.user.login == $USER)) | length'
|
||||
)"
|
||||
echo "::notice::@${{ github.actor }} has $count open pull request(s)."
|
||||
echo "count=$count" >>"$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
EXCEPT_USERS: ${{ inputs.except-users }}
|
||||
EXCEPT_AUTHOR_ASSOCIATIONS: ${{ inputs.except-author-associations }}
|
||||
shell: bash
|
||||
|
||||
- name: Comment on pull request
|
||||
if: >
|
||||
fromJSON(steps.count-pull-requests.outputs.count) > fromJSON(inputs.comment-limit) &&
|
||||
inputs.comment != ''
|
||||
run: |
|
||||
gh pr comment '${{ github.event.pull_request.number }}' \
|
||||
--body="${COMMENT_BODY}"
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
COMMENT_BODY: ${{ inputs.comment }}
|
||||
shell: bash
|
||||
|
||||
- name: Close pull request
|
||||
if: >
|
||||
fromJSON(steps.count-pull-requests.outputs.count) > fromJSON(inputs.close-limit) &&
|
||||
inputs.close == 'true'
|
||||
run: |
|
||||
gh pr close '${{ github.event.pull_request.number }}'
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
shell: bash
|
||||
29
.github/workflows/pr-check.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: PR Check
|
||||
on: pull_request_target
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_NO_UPDATE_NOTIFIER: 1
|
||||
GH_PROMPT_DISABLED: 1
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
jobs:
|
||||
limit-pull-requests:
|
||||
if: always() && github.repository_owner == 'naturalcrit'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name : Run limit-pull-requests action
|
||||
uses: ./.github/actions/limit-pull-requests
|
||||
with:
|
||||
except-users: |
|
||||
dependabot
|
||||
comment-limit: 3
|
||||
comment: |
|
||||
Hi, thanks for your contribution to the Homebrewery! You already have >=3 open pull requests. Consider completing some of your existing PRs before opening new ones. Thanks!
|
||||
close-limit: 5
|
||||
close: false
|
||||
2
.gitignore
vendored
@@ -12,5 +12,3 @@ todo.md
|
||||
startDB.bat
|
||||
startMViewer.bat
|
||||
.vscode
|
||||
|
||||
coverage
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-config-recess-order",
|
||||
"stylelint-config-recommended"],
|
||||
"plugins": [
|
||||
"@stylistic/stylelint-plugin",
|
||||
"./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
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:16.11-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 npm install --ignore-scripts
|
||||
RUN yarn install --ignore-scripts
|
||||
|
||||
# Bundle app source and build application
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
RUN yarn build
|
||||
|
||||
EXPOSE 8000
|
||||
CMD [ "npm", "start" ]
|
||||
CMD [ "yarn", "start" ]
|
||||
|
||||
32
README.md
@@ -21,29 +21,24 @@ below.
|
||||
First, install three programs that The Homebrewery requires to run and retrieve
|
||||
updates:
|
||||
|
||||
1. install [node](https://nodejs.org/en/), version v16 or higher.
|
||||
1. install [node](https://nodejs.org/en/)
|
||||
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\6.0\bin).
|
||||
1. Open a command prompt or other terminal and navigate to your MongoDB install folder (C:\Program Files\Mongo\Server\4.4\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]):
|
||||
@@ -56,19 +51,11 @@ 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** (until you close the terminal) in your shell of choice with admin privileges:
|
||||
You can set this temporarily in your shell of choice:
|
||||
* 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:
|
||||
|
||||
@@ -78,13 +65,6 @@ 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)
|
||||
|
||||
909
changelog.md
@@ -1,36 +1,13 @@
|
||||
```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;
|
||||
margin-top: 0.3cm;
|
||||
}
|
||||
|
||||
.page ul ul {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.page .taskList {
|
||||
display:block;
|
||||
break-inside:auto;
|
||||
}
|
||||
|
||||
.taskList li input {
|
||||
list-style-type : none;
|
||||
margin-left : -0.52cm;
|
||||
@@ -59,884 +36,19 @@ 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;
|
||||
}
|
||||
|
||||
.varSyntaxTable th:first-of-type {
|
||||
width:6cm;
|
||||
}
|
||||
```
|
||||
|
||||
## changelog
|
||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||
|
||||
### Wednesday 9/04/2024 - v3.15.0
|
||||
|
||||
{{taskList
|
||||
##### 5e-Cleric, abquintic, calculuschild, Gazook89, G-Ambatte, Ericsheid, Kaiburr
|
||||
|
||||
* [x] New {{openSans **VAULT** {{fas,fa-dungeon}}}} page 🎉🎉🎉
|
||||
:
|
||||
All **PUBLISHED** brews ({{openSans :fas_circle_info: **Properties**}} menu) will be searchable, by title or author, and filtered by renderer. More features and adjustments will be coming.
|
||||
:
|
||||
Note: If any of your own brews are not showing up in search (particularly if stored on Google Drive), please edit and re-save to ensure our database has the data needed from document to be searchable.
|
||||
|
||||
Fixes issue [#697](https://github.com/naturalcrit/homebrewery/issues/697)
|
||||
|
||||
##### Gazook89
|
||||
|
||||
* [x] Auto-focus on text editor when switching editor tabs
|
||||
}}
|
||||
|
||||
### Wednesday 8/28/2024 - v3.14.3
|
||||
|
||||
{{taskList
|
||||
##### calculuschild, G-Ambatte
|
||||
|
||||
* [x] New {{openSans **IMAGES → {{fac,image-wrap-left}} IMAGE WRAP LEFT/RIGHT**}} snippets
|
||||
|
||||
Fixes issue [#380](https://github.com/naturalcrit/homebrewery/issues/380)
|
||||
|
||||
* [x] Fix v3.14.2 bug with `꞉꞉꞉꞉` failing after tables
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] Fix Account page crash when not logged in
|
||||
|
||||
Fixes issue [#3605](https://github.com/naturalcrit/homebrewery/issues/3605)
|
||||
|
||||
##### abquintic
|
||||
|
||||
* [x] Fix jump hotkeys conflicting with `CTRL + SHIFT`. Preview and Source movement shortcuts now use `CTRL + SHIFT + META + LEFT\RIGHTARROW`
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Fix display issue with image wrap icons
|
||||
}}
|
||||
|
||||
|
||||
### Tuesday 8/27/2024 - v3.14.2
|
||||
|
||||
{{taskList
|
||||
##### calculuschild
|
||||
|
||||
* [x] Reroute invalid urls to homepage
|
||||
|
||||
Fixes issues [#3269](https://github.com/naturalcrit/homebrewery/issues/3629)
|
||||
|
||||
* [x] Background dependency updates
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Add route to get brew styling via `/css/shareId`
|
||||
|
||||
Fixes issues [#1097](https://github.com/naturalcrit/homebrewery/issues/1097)
|
||||
|
||||
* [x] Fix `:emojis:` preventing code folding
|
||||
|
||||
Fixes issues [#3604](https://github.com/naturalcrit/homebrewery/issues/3604)
|
||||
|
||||
* [x] Fix mask image warping when rotated and stretched
|
||||
|
||||
Fixes issues [#3636](https://github.com/naturalcrit/homebrewery/issues/3636)
|
||||
|
||||
* [x] Fix Table of Contents uppercasing
|
||||
|
||||
Fixes issues [#3572](https://github.com/naturalcrit/homebrewery/issues/3572)
|
||||
|
||||
##### abquintic
|
||||
|
||||
* [x] Create globally unique Header IDs across pages
|
||||
|
||||
Fixes issues [#1430](https://github.com/naturalcrit/homebrewery/issues/1430)
|
||||
|
||||
* [x] Fix colon `꞉꞉꞉꞉` being parsed in codeblocks
|
||||
|
||||
* [x] Prevent crashes when loading undefined renderer or theme bundle
|
||||
|
||||
* [x] Add Jump-To hotkeys
|
||||
|
||||
* Use `CTRL/META + SHIFT + LEFTARROW` to brewJump
|
||||
* Use `CTRL/META + SHIFT + RIGHTARROW` to sourceJump
|
||||
|
||||
* [x] Prevent reload from clobbering modified fresh clones
|
||||
|
||||
##### 5e-Cleric, Gazook89
|
||||
|
||||
* [x] Viewer tools for zoom/page navigation
|
||||
}}
|
||||
|
||||
### Tuesday 8/13/2024 - v3.14.1
|
||||
|
||||
{{taskList
|
||||
##### abquintic
|
||||
|
||||
* [x] Allow Table of Contents to flow across columns
|
||||
|
||||
Fixes issues [#2563](https://github.com/naturalcrit/homebrewery/issues/2563)
|
||||
|
||||
* [x] Fix unusual margin spacing for adjacent `.descriptive` and `.wide` blocks
|
||||
|
||||
Fixes issues [#2688](https://github.com/naturalcrit/homebrewery/issues/2688)
|
||||
|
||||
* [x] Add code folding to :fas_paintbrush: {{openSans **STYLE**}} tab
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Fix edge case where Table of Contents generator changed capitalization of headings
|
||||
|
||||
Fixes issues [#3572](https://github.com/naturalcrit/homebrewery/issues/3572)
|
||||
|
||||
* [x] Fix **Ink Friendly** snippet causing unselectable PDF text
|
||||
|
||||
Fixes issues [#3563](https://github.com/naturalcrit/homebrewery/issues/3563)
|
||||
|
||||
* [x] Prevent brews selecting themselves as a theme
|
||||
|
||||
Fixes issues [#3614](https://github.com/naturalcrit/homebrewery/issues/3614)
|
||||
|
||||
* [x] Fix info pages (`/faq`, `/migrate`, etc.) showing blank authorship info
|
||||
|
||||
Fixes issues [#3568](https://github.com/naturalcrit/homebrewery/issues/3568)
|
||||
|
||||
* [x] Add `abs()`, `sign()` and `signed()` functions to variable syntax math handler
|
||||
|
||||
Fixes issues [#3537](https://github.com/naturalcrit/homebrewery/issues/3537)
|
||||
|
||||
* [x] Fix variable math handler not processing commas (i.e., in `$[max(varA,varB)]`
|
||||
|
||||
Fixes issues [#3613](https://github.com/naturalcrit/homebrewery/issues/3613)
|
||||
|
||||
* [x] Fix variable math handler scrambling variables with names that are subsets of other variables
|
||||
|
||||
Fixes issues [#3622](https://github.com/naturalcrit/homebrewery/issues/3622)
|
||||
|
||||
##### calculuschild
|
||||
|
||||
* [x] Fix `/migrate` page using an editor context instead of share context
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] Fix Monster Stat Blocks losing color in Safari
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Monday 7/29/2024 - v3.14.0
|
||||
{{taskList
|
||||
|
||||
##### abquintic, calculuschild
|
||||
|
||||
* [x] Alternative Brew Themes, including importing other brews as a base theme.
|
||||
|
||||
- In the :fas_circle_info: **Properties** menu, find the new {{openSans **THEME**}} dropdown. It lists Brew Themes, including a new **Blank** theme as a simpler basis for custom styling.
|
||||
- Brews tagged with `meta:theme` will appear in the Brew Themes list. Selecting one loads its :fas_paintbrush: **Style** tab contents as the CSS basis for the current brew, allowing one brew to style multiple documents.
|
||||
- Brews with `meta:theme` can also select their own Theme, i.e. layering Themes on top of each other.
|
||||
- The next goal is to make **Published** Themes shareable between users.
|
||||
|
||||
|
||||
Fixes issues [#1899](https://github.com/naturalcrit/homebrewery/issues/1899), [#3085](https://github.com/naturalcrit/homebrewery/issues/3085)
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Fix Drop-cap font becoming corrupted when Bold
|
||||
|
||||
Fixes issues [#3551](https://github.com/naturalcrit/homebrewery/issues/3551)
|
||||
|
||||
* [x] Fixes to UI styling
|
||||
|
||||
Fixes issues [#3568](https://github.com/naturalcrit/homebrewery/issues/3568)
|
||||
|
||||
}}
|
||||
|
||||
|
||||
### Saturday 6/7/2024 - v3.13.1
|
||||
{{taskList
|
||||
|
||||
##### calculuschild, G-Ambatte
|
||||
|
||||
* [x] Hotfixes for issues with v3.13.0
|
||||
|
||||
Fixes issues [#3559](https://github.com/naturalcrit/homebrewery/issues/3559), [#3552](https://github.com/naturalcrit/homebrewery/issues/3552), [#3554](https://github.com/naturalcrit/homebrewery/issues/3554)
|
||||
}}
|
||||
|
||||
### Friday 28/6/2024 - v3.13.0
|
||||
{{taskList
|
||||
|
||||
##### calculuschild
|
||||
|
||||
* [x] Add `:emoji:` Markdown syntax, with autosuggest; start typing after the first `:` for matching emojis from
|
||||
:fab_font_awesome: FontAwesome, :df_d20: DiceFont, :ei_action: ElderberryInn, and a subset of :gi_broadsword: GameIcons
|
||||
|
||||
* [x] Fix `{curly injection}` to append to, rather than erase and replace target CSS
|
||||
* [x] {{openSans **GET PDF**}} {{fa,fa-file-pdf}} now opens the print dialog directly, rather than redirecting to a separate page
|
||||
|
||||
##### Gazook
|
||||
|
||||
* [x] Several small style tweaks to the UI
|
||||
* [x] Cleaning and refactoring several large pieces of code
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] For error pages, add links to user account and `/share` page if available
|
||||
|
||||
Fixes issue [#3298](https://github.com/naturalcrit/homebrewery/issues/3298)
|
||||
|
||||
* [x] Change FrontCover title to use stroke outline instead of faking it with dozens of shadows
|
||||
* [x] Cleaning and refactoring several large pieces of CSS
|
||||
|
||||
##### abquintic
|
||||
|
||||
* [x] Added additional {{openSans **TABLE OF CONTENTS**}} snippet options. Explicitly include or exclude items from the ToC generation via CSS properties
|
||||
`--TOC:exclude` or `--TOC:include`, or change the included header depth from 3 to 6 (default 3) with `tocDepthH6`
|
||||
|
||||
##### MurdoMaclachlan *(new contributor!)*
|
||||
|
||||
* [x] Added "proficiency bonus" to Monster Stat Block snippet.
|
||||
|
||||
Fixes issue [#3397](https://github.com/naturalcrit/homebrewery/issues/3397)
|
||||
}}
|
||||
|
||||
### Monday 18/3/2024 - v3.12.0
|
||||
{{taskList
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] Fix language-specific hyphenation on print page
|
||||
|
||||
Fixes issue [#3294](https://github.com/naturalcrit/homebrewery/issues/3294)
|
||||
|
||||
* [x] Upgrade Font-Awesome to v6.51
|
||||
|
||||
* [x] Allow downloaded files to be uploaded via {{openSans **NEW {{fa,fa-plus-square}} → FROM UPLOAD {{fa,fa-upload}}**}}
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Fix an edge case crash with empty documents
|
||||
|
||||
Fixes issue [#3315](https://github.com/naturalcrit/homebrewery/issues/3315)
|
||||
|
||||
* [x] Brews on the user page can be searched by tag; clicking a tag adds it to the filter
|
||||
|
||||
Fixes issue [#3164](https://github.com/naturalcrit/homebrewery/issues/3164)
|
||||
|
||||
* [x] Add *DiceFont* icons {{df,d20-20}} `{{df,icon-name}}`
|
||||
|
||||
##### abquintic
|
||||
|
||||
* [x] Fix ^super^ and ^^sub^^ highlighting in the text editor
|
||||
|
||||
* [x] Add new syntax for multiline Definition Lists:
|
||||
|
||||
|
||||
```
|
||||
Term
|
||||
::Definition 1
|
||||
::Definition 2
|
||||
with more text
|
||||
```
|
||||
|
||||
produces:
|
||||
|
||||
Term
|
||||
::Definition 1
|
||||
::Definition 2
|
||||
with more text
|
||||
|
||||
Fixes issue [#2340](https://github.com/naturalcrit/homebrewery/issues/2340)
|
||||
|
||||
##### RKuerten :
|
||||
* [x] Fix monster stat block backgrounds on print page
|
||||
|
||||
Fixes issue [#3275](https://github.com/naturalcrit/homebrewery/issues/3275)
|
||||
|
||||
* [x] Added new text editor theme: "Darkvision".
|
||||
|
||||
##### calculuschild, G-Ambatte, 5e-Cleric
|
||||
|
||||
* [x] Codebase and UI cleanup
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
|
||||
### Friday 21/2/2024 - v3.11.0
|
||||
{{taskList
|
||||
|
||||
##### Gazook89
|
||||
|
||||
* [x] Brew view count no longer increases when viewed by owner
|
||||
|
||||
Fixes issue [#3037](https://github.com/naturalcrit/homebrewery/issues/3037)
|
||||
|
||||
* [x] Small tweak to PHB H3 sizing
|
||||
|
||||
Fixes issue [#2989](https://github.com/naturalcrit/homebrewery/issues/2989)
|
||||
|
||||
* [x] Add **Fold/Unfold All** {{fas,fa-compress-alt}} / {{fas,fa-expand-alt}} buttons to editor bar
|
||||
|
||||
Fixes issue [#2965](https://github.com/naturalcrit/homebrewery/issues/2965)
|
||||
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Share link added to Editor Access error page
|
||||
|
||||
Fixes issue [#3086](https://github.com/naturalcrit/homebrewery/issues/3086)
|
||||
|
||||
* [x] Add Darkbrewery theme to Editor theme selector {{fas,fa-palette}}
|
||||
|
||||
Fixes issue [#3034](https://github.com/naturalcrit/homebrewery/issues/3034)
|
||||
|
||||
* [x] Fix Firefox prints with alternating blank pages
|
||||
|
||||
Fixes issue [#3115](https://github.com/naturalcrit/homebrewery/issues/3115)
|
||||
|
||||
* [x] Admin page working again
|
||||
|
||||
Fixes issue [#2657](https://github.com/naturalcrit/homebrewery/issues/2657)
|
||||
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] Fix indenting issue with Monster Blocks and italics in Class Feature
|
||||
|
||||
Fixes issues [#527](https://github.com/naturalcrit/homebrewery/issues/527),
|
||||
[#3247](https://github.com/naturalcrit/homebrewery/issues/3247)
|
||||
|
||||
* [x] Allow CSS vars in curly syntax to be formatted as strings using single quotes
|
||||
|
||||
`{{--customVar:"'a string'"}}`
|
||||
|
||||
Fixes issue [#3066](https://github.com/naturalcrit/homebrewery/issues/3066)
|
||||
|
||||
* [x] Add *Elderberry Inn* icons {{ei,action}} `{{ei,icon-name}}`
|
||||
|
||||
Fixes issue [#3171](https://github.com/naturalcrit/homebrewery/issues/3171)
|
||||
|
||||
* [x] New {{openSans **{{fas,fa-keyboard}} FONTS** }} snippets!
|
||||
|
||||
Fixes issue [#3171](https://github.com/naturalcrit/homebrewery/issues/3171)
|
||||
|
||||
* [x] New page now opens in a new tab
|
||||
|
||||
|
||||
##### abquintic (new contributor!)
|
||||
|
||||
* [x] Add ^super^ `^abc^` and ^^sub^^ `^^abc^^` syntax.
|
||||
|
||||
Fixes issue [#2171](https://github.com/naturalcrit/homebrewery/issues/2171)
|
||||
|
||||
* [x] Add HTML tag assignment to curly syntax `{{tag=value}}`
|
||||
|
||||
Fixes issue [1488](https://github.com/naturalcrit/homebrewery/issues/1488)
|
||||
|
||||
* [x] {{openSans **Brew → Clone to New**}} now clones tags
|
||||
|
||||
Fixes issue [1488](https://github.com/naturalcrit/homebrewery/issues/1488)
|
||||
|
||||
##### calculuschild
|
||||
|
||||
* [x] Better error messages for "Out of Google Drive Storage" and "Not logged in to edit"
|
||||
|
||||
Fixes issues [2510](https://github.com/naturalcrit/homebrewery/issues/2510),
|
||||
[2975](https://github.com/naturalcrit/homebrewery/issues/2975)
|
||||
|
||||
* [x] Brew Variables
|
||||
}}
|
||||
|
||||
\
|
||||
|
||||
{{wide
|
||||
|
||||
### Brew Variable Syntax
|
||||
|
||||
You may already be familiar with `[link](url)` and `` synax. We have expanded this to include a third `$[variable](text)` syntax. All three of these syntaxes now share a common set of features:
|
||||
|
||||
{{varSyntaxTable
|
||||
| syntax | description |
|
||||
|:-------|-------------|
|
||||
| `[var]:content` | Assigns a variable (must start on a line by itself, and ends at the next blank line) |
|
||||
| `[var](content)` | Assigns a variable and outputs it (can be inline) |
|
||||
| `[var]` | Outputs the variable contents as a link, if formatted as a valid link |
|
||||
| `![var]` | Outputs as an image, if formatted as a valid image |
|
||||
| `$[var]` | Outputs as Markdown |
|
||||
| `$[var1 + var2 - 2 * var3]` | Performs math operations and outputs result if all variables are valid numbers |
|
||||
}}
|
||||
|
||||
}}
|
||||
|
||||
{{wide,margin-top:0,margin-bottom:0
|
||||
### Examples
|
||||
}}
|
||||
|
||||
{{wide,columns:2,margin-top:0,margin-bottom:0
|
||||
|
||||
```
|
||||
[first]: Bob
|
||||
|
||||
[last]: Jones
|
||||
|
||||
My name is $[first] $[last].
|
||||
```
|
||||
|
||||
\column
|
||||
|
||||
[first]: Bob
|
||||
|
||||
[last]: Jones
|
||||
|
||||
My name is $[first] $[last].
|
||||
|
||||
}}
|
||||
|
||||
{{wide,columns:2,margin-top:0,margin-bottom:0
|
||||
|
||||
```
|
||||
[myTable]:
|
||||
| h1 | h2 |
|
||||
|----|----|
|
||||
| c1 | c2 |
|
||||
|
||||
Here is my table:
|
||||
$[myTable]
|
||||
```
|
||||
|
||||
\column
|
||||
|
||||
[myTable]:
|
||||
| h1 | h2 |
|
||||
|----|----|
|
||||
| c1 | c2 |
|
||||
|
||||
Here is my table:
|
||||
$[myTable]
|
||||
}}
|
||||
|
||||
{{wide,columns:2,margin-top:0,margin-bottom:0
|
||||
|
||||
```
|
||||
There are $[TableNum] tables total.
|
||||
|
||||
#### Table $[TableNum](1): Horses
|
||||
|
||||
#### Table $[TableNum]($[TableNum + 1]): Cows
|
||||
```
|
||||
|
||||
\column
|
||||
|
||||
There are $[TableNum] tables in this document. *(note: final value of `$[TableNum]` gets hoisted up if available)*
|
||||
|
||||
|
||||
#### Table $[TableNum](1): Horses
|
||||
|
||||
#### Table $[TableNum]($[TableNum + 1]): Cows
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Friday 13/10/2023 - v3.10.0
|
||||
{{taskList
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Fix user preferred save location being ignored
|
||||
|
||||
Fixes issue [#2993](https://github.com/naturalcrit/homebrewery/issues/2993)
|
||||
|
||||
* [x] Fix crash to white screen when starting new brews while not signed in
|
||||
|
||||
Fixes issue [#2999](https://github.com/naturalcrit/homebrewery/issues/2999)
|
||||
|
||||
* [x] Fix FreeBSD install script
|
||||
|
||||
Fixes issue [#3005](https://github.com/naturalcrit/homebrewery/issues/3005)
|
||||
|
||||
* [x] Fix *"This brew has been changed on another device"* triggering when manually saving during auto-save
|
||||
|
||||
Fixes issue [#2641](https://github.com/naturalcrit/homebrewery/issues/2641)
|
||||
|
||||
* [x] Fix Firefox different column-flow behavior
|
||||
|
||||
Fixes issue [#2982](https://github.com/naturalcrit/homebrewery/issues/2982)
|
||||
|
||||
* [x] Fix brew titles being mis-sorted on user page
|
||||
|
||||
Fixes issue [#2775](https://github.com/naturalcrit/homebrewery/issues/2775)
|
||||
|
||||
* [x] Text Editor themes now available via new drop-down
|
||||
|
||||
Fixes issue [#362](https://github.com/naturalcrit/homebrewery/issues/362)
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] New {{openSans **PHB → {{fas,fa-quote-right}} QUOTE** }} snippet for V3!
|
||||
|
||||
Fixes issue [#2920](https://github.com/naturalcrit/homebrewery/issues/2920)
|
||||
|
||||
* [x] Several updates and fixes to FAQ and Welcome page
|
||||
|
||||
Fixes issue [#2729](https://github.com/naturalcrit/homebrewery/issues/2729),
|
||||
[#2787](https://github.com/naturalcrit/homebrewery/issues/2787)
|
||||
|
||||
##### Gazook89
|
||||
|
||||
* [x] Add syntax highlighting for Definition Lists <code>:\:</code>
|
||||
}}
|
||||
|
||||
|
||||
### Thursday 17/08/2023 - v3.9.2
|
||||
{{taskList
|
||||
|
||||
##### calculuschild
|
||||
|
||||
* [x] Fix links to certain old Google Drive files
|
||||
|
||||
Fixes issue [#2917](https://github.com/naturalcrit/homebrewery/issues/2917)
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Menus now open on click, and internally consistent
|
||||
|
||||
Fixes issue [#2702](https://github.com/naturalcrit/homebrewery/issues/2702), [#2782](https://github.com/naturalcrit/homebrewery/issues/2782)
|
||||
|
||||
* [x] Add smarter footer snippet
|
||||
|
||||
Fixes issue [#2289](https://github.com/naturalcrit/homebrewery/issues/2289)
|
||||
|
||||
* [x] Add sanitization in Style editor
|
||||
|
||||
Fixes issue [#1437](https://github.com/naturalcrit/homebrewery/issues/1437)
|
||||
|
||||
* [x] Rework class table snippets to remove unnecessary randomness
|
||||
|
||||
Fixes issue [#2964](https://github.com/naturalcrit/homebrewery/issues/2964)
|
||||
|
||||
* [x] Add User Page link to Google Drive file for file owners, add icons for additional storage locations
|
||||
|
||||
Fixes issue [#2954](https://github.com/naturalcrit/homebrewery/issues/2954)
|
||||
|
||||
* [x] Add default save location selection to Account Page
|
||||
|
||||
Fixes issue [#2943](https://github.com/naturalcrit/homebrewery/issues/2943)
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] Exclude cover pages from Table of Content generation (editing on mobile is still not recommended)
|
||||
|
||||
Fixes issue [#2920](https://github.com/naturalcrit/homebrewery/issues/2920)
|
||||
|
||||
##### Gazook89
|
||||
|
||||
* [x] Adjustments to improve mobile viewing
|
||||
}}
|
||||
|
||||
|
||||
|
||||
### 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
|
||||
|
||||
##### 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)
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### 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)
|
||||
|
||||
}}
|
||||
|
||||
### 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)
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
{{taskList
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Auto-compile Themes CSS on development server
|
||||
|
||||
##### 5e-Cleric
|
||||
|
||||
* [x] Fix cloned brews inheriting the parent view count
|
||||
}}
|
||||
|
||||
|
||||
### 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
|
||||
##### Calculuschild
|
||||
|
||||
* [x] Fixes to several broken CSS styles from v3.3.0
|
||||
|
||||
@@ -951,7 +63,7 @@ Fixes issues [#2468](https://github.com/naturalcrit/homebrewery/issues/2468)
|
||||
### Friday 19/10/2022 - v3.3.0
|
||||
{{taskList
|
||||
|
||||
##### calculuschild
|
||||
##### Calculuschild
|
||||
|
||||
* [x] Fix for tables broken by Chrome v106
|
||||
|
||||
@@ -978,6 +90,7 @@ Fixes issues [#2135](https://github.com/naturalcrit/homebrewery/issues/2135)
|
||||
|
||||
Fixes issues [#2427](https://github.com/naturalcrit/homebrewery/issues/2427)
|
||||
|
||||
|
||||
##### Gazook:
|
||||
|
||||
* [x] Several updates to bug reporting and error popups
|
||||
@@ -1027,14 +140,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
|
||||
|
||||
##### calculuschild
|
||||
##### Calculuschild
|
||||
|
||||
* [x] Reference Links should now work inside tables
|
||||
|
||||
@@ -1057,10 +166,12 @@ 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
|
||||
|
||||
##### calculuschild
|
||||
##### Calculuschild
|
||||
|
||||
* [x] The V3 renderer is now the default for new brews.
|
||||
|
||||
@@ -1087,7 +198,7 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [
|
||||
### Thursday 09/06/2022 - v3.1.1
|
||||
{{taskList
|
||||
|
||||
##### calculuschild:
|
||||
##### Calculuschild:
|
||||
|
||||
* [x] Fixed class table decorations appearing on top of the table in PDF output.
|
||||
|
||||
@@ -1804,7 +915,7 @@ myStyle {color: black}
|
||||
### Sunday, 29/05/2016 - v2.1.0
|
||||
- Finally added a syntax for doing spell lists. A bit in-depth about why this took so long. Essentially I'm running out of syntax to use in stardard Markdown. There are too many unique elements in the PHB-style to be mapped. I solved this earlier by stacking certain elements together (eg. an `<hr>` before a `blockquote` turns it into moster state block), but those are getting unweildly. I would like to simply wrap these in `div`s with classes, but unfortunately Markdown stops processing when within HTML blocks. To get around this I wrote my own override to the Markdown parser and lexer to process Markdown within a simple div class wrapper. This should open the door for more unique syntaxes in the future. Big step!
|
||||
- Override Ctrl+P (and cmd+P) to launch to the print page. Many people try to just print either the editing or share page to get a PDF. While this dones;t make much sense, I do get a ton of issues about it. So now if you try to do this, it'll just bring you imediately to the print page. Everybody wins!
|
||||
- The onboarding flow has also been confusing a few users (Homepage → new → save → edit page). If you edit the Homepage text now, a Call to Action to save your work will pop-up.
|
||||
- The onboarding flow has also been confusing a few users (Homepage -> new -> save -> edit page). If you edit the Homepage text now, a Call to Action to save your work will pop-up.
|
||||
- Added a 'Recently Edited' and 'Recently Viewed' nav item to the edit and share page respectively. Each will remember the last 8 items you edited or viewed and when you viewed it. Makes use of the new title attribute of brews to easy navigatation.
|
||||
- Paragraphs now indent properly after lists (thanks u/slitjen!)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require('./brewCleanup.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const cx = require('classnames');
|
||||
|
||||
const request = require('superagent');
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require('./brewCompress.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const cx = require('classnames');
|
||||
|
||||
const request = require('superagent');
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
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 (
|
||||
<div className='dropdown-input item'
|
||||
onMouseEnter={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(true);} : undefined}
|
||||
onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}>
|
||||
<input
|
||||
type='text'
|
||||
onChange={(e)=>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));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
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 (
|
||||
<div className='dropdown-options'>
|
||||
{dropdownChildren}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render : function () {
|
||||
const dropdownChildren = this.state.options.map((child, i)=>{
|
||||
const clone = React.cloneElement(child, { onClick: (e)=>this.handleSelect(e) });
|
||||
return clone;
|
||||
});
|
||||
return (
|
||||
<div className={`dropdown-container ${this.props.className}`}
|
||||
ref='dropdown'
|
||||
onMouseLeave={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(false);} : undefined}>
|
||||
{this.renderTextInput()}
|
||||
{this.renderDropdown(dropdownChildren)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Combobox;
|
||||
@@ -1,50 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Dialog box, for popups and modal blocking messages
|
||||
const React = require('react');
|
||||
const { useRef, useEffect } = React;
|
||||
|
||||
function Dialog({ dismissKey, closeText = 'Close', blocking = false, ...rest }) {
|
||||
const dialogRef = useRef(null);
|
||||
|
||||
useEffect(()=>{
|
||||
if(!dismissKey || !localStorage.getItem(dismissKey)) {
|
||||
blocking ? dialogRef.current?.showModal() : dialogRef.current?.show();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const dismiss = ()=>{
|
||||
dismissKey && localStorage.setItem(dismissKey, true);
|
||||
dialogRef.current?.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<dialog ref={dialogRef} onCancel={dismiss} {...rest}>
|
||||
{rest.children}
|
||||
<button className='dismiss' onClick={dismiss}>
|
||||
{closeText}
|
||||
</button>
|
||||
</dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dialog;
|
||||
@@ -1,238 +1,239 @@
|
||||
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
|
||||
require('./brewRenderer.less');
|
||||
const React = require('react');
|
||||
const { useState, useRef, useEffect } = React;
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||
const ToolBar = require('./toolBar/toolBar.jsx');
|
||||
|
||||
//TODO: move to the brew renderer
|
||||
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
|
||||
const NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
|
||||
const Frame = require('react-frame-component').default;
|
||||
const dedent = require('dedent-tabs').default;
|
||||
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||
|
||||
const DOMPurify = require('dompurify');
|
||||
const purifyConfig = { FORCE_BODY: true, SANITIZE_DOM: false };
|
||||
const Themes = require('themes/themes.json');
|
||||
|
||||
const PAGE_HEIGHT = 1056;
|
||||
const PPR_THRESHOLD = 50;
|
||||
|
||||
const INITIAL_CONTENT = dedent`
|
||||
<!DOCTYPE html><html><head>
|
||||
<link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link href='/homebrew/bundle.css' type="text/css" rel='stylesheet' />
|
||||
<base target=_blank>
|
||||
</head><body style='overflow: hidden'><div></div></body></html>`;
|
||||
const BrewRenderer = createClass({
|
||||
displayName : 'BrewRenderer',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
text : '',
|
||||
style : '',
|
||||
renderer : 'legacy',
|
||||
theme : '5ePHB',
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
let pages;
|
||||
if(this.props.renderer == 'legacy') {
|
||||
pages = this.props.text.split('\\page');
|
||||
} else {
|
||||
pages = this.props.text.split(/^\\page$/gm);
|
||||
}
|
||||
|
||||
//v=====----------------------< Brew Page Component >---------------------=====v//
|
||||
const BrewPage = (props)=>{
|
||||
props = {
|
||||
contents : '',
|
||||
index : 0,
|
||||
...props
|
||||
};
|
||||
const cleanText = props.contents; //DOMPurify.sanitize(props.contents, purifyConfig);
|
||||
return <div className={props.className} id={`p${props.index + 1}`} >
|
||||
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
|
||||
</div>;
|
||||
};
|
||||
return {
|
||||
viewablePageNumber : 0,
|
||||
height : 0,
|
||||
isMounted : false,
|
||||
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD,
|
||||
visibility : 'hidden',
|
||||
initialContent : `<!DOCTYPE html><html><head>
|
||||
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link href='/homebrew/bundle.css' rel='stylesheet' />
|
||||
<base target=_blank>
|
||||
</head><body style='overflow: hidden'><div></div></body></html>`
|
||||
};
|
||||
},
|
||||
height : 0,
|
||||
lastRender : <div></div>,
|
||||
|
||||
//v=====--------------------< Brew Renderer Component >-------------------=====v//
|
||||
const renderedPages = [];
|
||||
let rawPages = [];
|
||||
componentWillUnmount : function() {
|
||||
window.removeEventListener('resize', this.updateSize);
|
||||
},
|
||||
|
||||
const BrewRenderer = (props)=>{
|
||||
props = {
|
||||
text : '',
|
||||
style : '',
|
||||
renderer : 'legacy',
|
||||
theme : '5ePHB',
|
||||
lang : '',
|
||||
errors : [],
|
||||
currentEditorPage : 0,
|
||||
themeBundle : {},
|
||||
...props
|
||||
};
|
||||
componentDidUpdate : function(prevProps) {
|
||||
if(prevProps.text !== this.props.text) {
|
||||
let pages;
|
||||
if(this.props.renderer == 'legacy') {
|
||||
pages = this.props.text.split('\\page');
|
||||
} else {
|
||||
pages = this.props.text.split(/^\\page$/gm);
|
||||
}
|
||||
this.setState({
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
const [state, setState] = useState({
|
||||
height : PAGE_HEIGHT,
|
||||
isMounted : false,
|
||||
visibility : 'hidden',
|
||||
zoom : 100,
|
||||
currentPageNumber : 1,
|
||||
});
|
||||
updateSize : function() {
|
||||
this.setState({
|
||||
height : this.refs.main.parentNode.clientHeight,
|
||||
});
|
||||
},
|
||||
|
||||
const mainRef = useRef(null);
|
||||
|
||||
if(props.renderer == 'legacy') {
|
||||
rawPages = props.text.split('\\page');
|
||||
} else {
|
||||
rawPages = props.text.split(/^\\page$/gm);
|
||||
}
|
||||
|
||||
useEffect(()=>{ // Unmounting steps
|
||||
return ()=>{window.removeEventListener('resize', updateSize);};
|
||||
}, []);
|
||||
|
||||
const updateSize = ()=>{
|
||||
setState((prevState)=>({
|
||||
...prevState,
|
||||
height : mainRef.current.parentNode.clientHeight,
|
||||
handleScroll : function(e){
|
||||
const target = e.target;
|
||||
this.setState((prevState)=>({
|
||||
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * prevState.pages.length)
|
||||
}));
|
||||
};
|
||||
},
|
||||
|
||||
const getCurrentPage = (e)=>{
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.target;
|
||||
const totalScrollableHeight = scrollHeight - clientHeight;
|
||||
const currentPageNumber = Math.ceil((scrollTop / totalScrollableHeight) * rawPages.length);
|
||||
shouldRender : function(pageText, index){
|
||||
if(!this.state.isMounted) return false;
|
||||
|
||||
setState((prevState)=>({
|
||||
...prevState,
|
||||
currentPageNumber : currentPageNumber || 1
|
||||
}));
|
||||
};
|
||||
const viewIndex = this.state.viewablePageNumber;
|
||||
if(index == viewIndex - 3) return true;
|
||||
if(index == viewIndex - 2) return true;
|
||||
if(index == viewIndex - 1) return true;
|
||||
if(index == viewIndex) return true;
|
||||
if(index == viewIndex + 1) return true;
|
||||
if(index == viewIndex + 2) return true;
|
||||
if(index == viewIndex + 3) return true;
|
||||
|
||||
const isInView = (index)=>{
|
||||
if(!state.isMounted)
|
||||
return false;
|
||||
|
||||
if(index == props.currentEditorPage) //Already rendered before this step
|
||||
return false;
|
||||
|
||||
if(Math.abs(index - state.currentPageNumber) <= 3)
|
||||
return true;
|
||||
//Check for style tages
|
||||
if(pageText.indexOf('<style>') !== -1) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
},
|
||||
|
||||
const renderDummyPage = (index)=>{
|
||||
renderPageInfo : function(){
|
||||
return <div className='pageInfo' ref='main'>
|
||||
<div>
|
||||
{this.props.renderer}
|
||||
</div>
|
||||
<div>
|
||||
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderPPRmsg : function(){
|
||||
if(!this.state.usePPR) return;
|
||||
|
||||
return <div className='ppr_msg'>
|
||||
Partial Page Renderer is enabled, because your brew is so large. May affect rendering.
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderDummyPage : function(index){
|
||||
return <div className='phb page' id={`p${index + 1}`} key={index}>
|
||||
<i className='fas fa-spinner fa-spin' />
|
||||
</div>;
|
||||
};
|
||||
},
|
||||
|
||||
const renderStyle = ()=>{
|
||||
const cleanStyle = props.style; //DOMPurify.sanitize(props.style, purifyConfig);
|
||||
const themeStyles = props.themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `${themeStyles} \n\n <style> ${cleanStyle} </style>` }} />;
|
||||
};
|
||||
renderStyle : function() {
|
||||
if(!this.props.style) return;
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />;
|
||||
},
|
||||
|
||||
const renderPage = (pageText, index)=>{
|
||||
if(props.renderer == 'legacy') {
|
||||
const html = MarkdownLegacy.render(pageText);
|
||||
return <BrewPage className='page phb' index={index} key={index} contents={html} />;
|
||||
} else {
|
||||
renderPage : function(pageText, index){
|
||||
if(this.props.renderer == 'legacy')
|
||||
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }} key={index} />;
|
||||
else {
|
||||
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||
const html = Markdown.render(pageText, index);
|
||||
return <BrewPage className='page' index={index} key={index} contents={html} />;
|
||||
return (
|
||||
<div className='page' id={`p${index + 1}`} key={index} >
|
||||
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
const renderPages = ()=>{
|
||||
if(props.errors && props.errors.length)
|
||||
return renderedPages;
|
||||
|
||||
if(rawPages.length != renderedPages.length) // Re-render all pages when page count changes
|
||||
renderedPages.length = 0;
|
||||
|
||||
// Render currently-edited page first so cross-page effects (variables, links) can propagate out first
|
||||
renderedPages[props.currentEditorPage] = renderPage(rawPages[props.currentEditorPage], props.currentEditorPage);
|
||||
|
||||
_.forEach(rawPages, (page, index)=>{
|
||||
if((isInView(index) || !renderedPages[index]) && typeof window !== 'undefined'){
|
||||
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
|
||||
renderPages : function(){
|
||||
if(this.state.usePPR){
|
||||
return _.map(this.state.pages, (page, index)=>{
|
||||
if(this.shouldRender(page, index) && typeof window !== 'undefined'){
|
||||
return this.renderPage(page, index);
|
||||
} else {
|
||||
return this.renderDummyPage(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
if(this.props.errors && this.props.errors.length) return this.lastRender;
|
||||
this.lastRender = _.map(this.state.pages, (page, index)=>{
|
||||
if(typeof window !== 'undefined') {
|
||||
return this.renderPage(page, index);
|
||||
} else {
|
||||
return this.renderDummyPage(index);
|
||||
}
|
||||
});
|
||||
return renderedPages;
|
||||
};
|
||||
return this.lastRender;
|
||||
},
|
||||
|
||||
const handleControlKeys = (e)=>{
|
||||
if(!(e.ctrlKey || e.metaKey)) return;
|
||||
const P_KEY = 80;
|
||||
if(e.keyCode == P_KEY && props.allowPrint) printCurrentBrew();
|
||||
if(e.keyCode == P_KEY) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
|
||||
frameDidMount : function(){ //This triggers when iFrame finishes internal "componentDidMount"
|
||||
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
|
||||
updateSize();
|
||||
window.addEventListener('resize', updateSize);
|
||||
renderPages(); //Make sure page is renderable before showing
|
||||
setState((prevState)=>({
|
||||
...prevState,
|
||||
this.updateSize();
|
||||
window.addEventListener('resize', this.updateSize);
|
||||
this.renderPages(); //Make sure page is renderable before showing
|
||||
this.setState({
|
||||
isMounted : true,
|
||||
visibility : 'visible'
|
||||
}));
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
},
|
||||
|
||||
const emitClick = ()=>{ // Allow clicks inside iFrame to interact with dropdowns, etc. from outside
|
||||
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.
|
||||
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
|
||||
const themePath = this.props.theme ?? '5ePHB';
|
||||
const baseThemePath = Themes[rendererPath][themePath].baseTheme;
|
||||
|
||||
//Toolbar settings:
|
||||
const handleZoom = (newZoom)=>{
|
||||
setState((prevState)=>({
|
||||
...prevState,
|
||||
zoom : newZoom
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*render dummy page while iFrame is mounting.*/}
|
||||
{!state.isMounted
|
||||
? <div className='brewRenderer' onScroll={getCurrentPage}>
|
||||
<div className='pages'>
|
||||
{renderDummyPage(1)}
|
||||
return (
|
||||
<React.Fragment>
|
||||
{!this.state.isMounted
|
||||
? <div className='brewRenderer' onScroll={this.handleScroll}>
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderDummyPage(1)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
: null}
|
||||
|
||||
<ErrorBar errors={props.errors} />
|
||||
<div className='popups' ref={mainRef}>
|
||||
<RenderWarnings />
|
||||
<NotificationPopup />
|
||||
</div>
|
||||
<Frame id='BrewRenderer' initialContent={this.state.initialContent}
|
||||
style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
|
||||
contentDidMount={this.frameDidMount}>
|
||||
<div className={'brewRenderer'}
|
||||
onScroll={this.handleScroll}
|
||||
style={{ height: this.state.height }}>
|
||||
|
||||
<ToolBar onZoomChange={handleZoom} currentPage={state.currentPageNumber} totalPages={rawPages.length}/>
|
||||
|
||||
{/*render in iFrame so broken code doesn't crash the site.*/}
|
||||
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
|
||||
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||
contentDidMount={frameDidMount}
|
||||
onClick={()=>{emitClick();}}
|
||||
>
|
||||
<div className={'brewRenderer'}
|
||||
onScroll={getCurrentPage}
|
||||
onKeyDown={handleControlKeys}
|
||||
tabIndex={-1}
|
||||
style={{ height: state.height }}>
|
||||
|
||||
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
||||
{state.isMounted
|
||||
&&
|
||||
<>
|
||||
{renderStyle()}
|
||||
<div className='pages' lang={`${props.lang || 'en'}`} style={{ zoom: `${state.zoom}%` }}>
|
||||
{renderPages()}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</Frame>
|
||||
</>
|
||||
);
|
||||
};
|
||||
<ErrorBar errors={this.props.errors} />
|
||||
<div className='popups'>
|
||||
<RenderWarnings />
|
||||
<NotificationPopup />
|
||||
</div>
|
||||
<link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/>
|
||||
{baseThemePath &&
|
||||
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
|
||||
}
|
||||
<link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/>
|
||||
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
||||
{this.state.isMounted
|
||||
&&
|
||||
<>
|
||||
{this.renderStyle()}
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</Frame>
|
||||
{this.renderPageInfo()}
|
||||
{this.renderPPRmsg()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = BrewRenderer;
|
||||
|
||||
@@ -1,45 +1,46 @@
|
||||
@import (multiple, less) 'shared/naturalcrit/styles/reset.less';
|
||||
|
||||
.brewRenderer {
|
||||
overflow-y : scroll;
|
||||
will-change : transform;
|
||||
padding-top : 30px;
|
||||
:where(.pages) {
|
||||
.brewRenderer{
|
||||
will-change : transform;
|
||||
overflow-y : scroll;
|
||||
.pages{
|
||||
margin : 30px 0px;
|
||||
& > :where(.page) {
|
||||
width : 215.9mm;
|
||||
height : 279.4mm;
|
||||
&>.page{
|
||||
margin-right : auto;
|
||||
margin-bottom : 30px;
|
||||
margin-left : auto;
|
||||
box-shadow : 1px 4px 14px #000000;
|
||||
box-shadow : 1px 4px 14px #000;
|
||||
}
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width : 20px;
|
||||
&:horizontal {
|
||||
width : auto;
|
||||
height : 20px;
|
||||
}
|
||||
&-thumb {
|
||||
background : linear-gradient(90deg, #D3C1AF 15px, #00000000 15px);
|
||||
&:horizontal { background : linear-gradient(0deg, #D3C1AF 15px, #00000000 15px); }
|
||||
}
|
||||
&-corner { visibility : hidden; }
|
||||
}
|
||||
}
|
||||
|
||||
.pane { position : relative; }
|
||||
|
||||
@media print {
|
||||
.toolBar { display : none; }
|
||||
.brewRenderer {
|
||||
height : 100%;
|
||||
padding-top : unset;
|
||||
overflow-y : unset;
|
||||
.pages {
|
||||
margin : 0px;
|
||||
& > .page { box-shadow : unset; }
|
||||
.pane{
|
||||
position : relative;
|
||||
}
|
||||
.pageInfo{
|
||||
position : absolute;
|
||||
right : 17px;
|
||||
bottom : 0;
|
||||
z-index : 1000;
|
||||
background-color : #333;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
div {
|
||||
display: inline-block;
|
||||
padding : 8px 10px;
|
||||
&:not(:last-child){
|
||||
border-right: 1px solid #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ppr_msg{
|
||||
position : absolute;
|
||||
left : 0px;
|
||||
bottom : 0;
|
||||
z-index : 1000;
|
||||
padding : 8px 10px;
|
||||
background-color : #333;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ require('./errorBar.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const ErrorBar = createClass({
|
||||
displayName : 'ErrorBar',
|
||||
|
||||
@@ -1,46 +1,92 @@
|
||||
require('./notificationPopup.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames'); //Unused variable
|
||||
|
||||
import Dialog from '../../../components/dialog.jsx';
|
||||
const DISMISS_KEY = 'dismiss_notification08-27-22';
|
||||
|
||||
const DISMISS_KEY = 'dismiss_notification04-09-24';
|
||||
const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
|
||||
const NotificationPopup = createClass({
|
||||
displayName : 'NotificationPopup',
|
||||
getInitialState : function() {
|
||||
return {
|
||||
notifications : {}
|
||||
};
|
||||
},
|
||||
componentDidMount : function() {
|
||||
this.checkNotifications();
|
||||
window.addEventListener('resize', this.checkNotifications);
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
window.removeEventListener('resize', this.checkNotifications);
|
||||
},
|
||||
notifications : {
|
||||
psa : function(){
|
||||
return (
|
||||
<>
|
||||
<li key='psa'>
|
||||
<em>V3.2.0 Released!</em> <br />
|
||||
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.
|
||||
</li>
|
||||
|
||||
const NotificationPopup = ()=>{
|
||||
return <Dialog className='notificationPopup' dismissKey={DISMISS_KEY} closeText={DISMISS_BUTTON} >
|
||||
<div className='header'>
|
||||
<i className='fas fa-info-circle info'></i>
|
||||
<h3>Notice</h3>
|
||||
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||
</div>
|
||||
<ul>
|
||||
<li key='Vault'>
|
||||
<em>Search brews with our new page!</em><br />
|
||||
We have been working very hard in making this possible, now you can share your work and look at it in the new <a href="/vault">Vault</a> page!
|
||||
All PUBLISHED brews will be available to anyone searching there, by title or author, and filtering by renderer.
|
||||
<li key='stubs'>
|
||||
<em>Change to Google Drive Storage!</em> <br />
|
||||
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.
|
||||
</li>
|
||||
|
||||
More features will be coming.
|
||||
</li>
|
||||
<li key='googleDriveFolder'>
|
||||
<em>Don't delete your Homebrewery folder on Google Drive!</em> <br />
|
||||
We have had several reports of users losing their brews, not realizing
|
||||
that they had deleted the files on their Google Drive. If you have a Homebrewery folder
|
||||
on your Google Drive with *.txt files inside, <em>do not delete it</em>!
|
||||
We cannot help you recover files that you have deleted from your own
|
||||
Google Drive.
|
||||
</li>
|
||||
|
||||
<li key='googleDriveFolder'>
|
||||
<em>Don't delete your Homebrewery folder on Google Drive!</em> <br />
|
||||
We have had several reports of users losing their brews, not realizing
|
||||
that they had deleted the files on their Google Drive. If you have a Homebrewery folder
|
||||
on your Google Drive with *.txt files inside, <em>do not delete it</em>!
|
||||
We cannot help you recover files that you have deleted from your own
|
||||
Google Drive.
|
||||
</li>
|
||||
<li key='faq'>
|
||||
<em>Protect your work! </em> <br />
|
||||
If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews!
|
||||
<a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'>
|
||||
See the FAQ
|
||||
</a> to learn how to avoid losing your work!
|
||||
</li>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
checkNotifications : function(){
|
||||
const hideDismiss = localStorage.getItem(DISMISS_KEY);
|
||||
if(hideDismiss) return this.setState({ notifications: {} });
|
||||
|
||||
<li key='faq'>
|
||||
<em>Protect your work! </em> <br />
|
||||
If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews!
|
||||
<a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'>
|
||||
See the FAQ
|
||||
</a> to learn how to avoid losing your work!
|
||||
</li>
|
||||
</ul>
|
||||
</Dialog>;
|
||||
};
|
||||
this.setState({
|
||||
notifications : _.mapValues(this.notifications, (fn)=>{ return fn(); }) //Convert notification functions into their return text value
|
||||
});
|
||||
},
|
||||
dismiss : function(){
|
||||
localStorage.setItem(DISMISS_KEY, true);
|
||||
this.checkNotifications();
|
||||
},
|
||||
render : function(){
|
||||
if(_.isEmpty(this.state.notifications)) return null;
|
||||
|
||||
return <div className='notificationPopup'>
|
||||
<i className='fas fa-times dismiss' onClick={this.dismiss}/>
|
||||
<i className='fas fa-info-circle info' />
|
||||
<div className='header'>
|
||||
<h3>Notice</h3>
|
||||
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||
</div>
|
||||
<ul>{_.values(this.state.notifications)}</ul>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NotificationPopup;
|
||||
|
||||
@@ -1,61 +1,64 @@
|
||||
.popups {
|
||||
.popups{
|
||||
position : fixed;
|
||||
top : calc(@navbarHeight + @viewerToolsHeight);
|
||||
right : 24px;
|
||||
top : @navbarHeight;
|
||||
right : 15px;
|
||||
z-index : 10001;
|
||||
width : 450px;
|
||||
margin-top : 5px;
|
||||
}
|
||||
|
||||
.notificationPopup {
|
||||
.notificationPopup{
|
||||
position : relative;
|
||||
display : inline-block;
|
||||
width : 100%;
|
||||
padding : 15px;
|
||||
padding-bottom : 10px;
|
||||
padding-left : 25px;
|
||||
color : white;
|
||||
background-color : @blue;
|
||||
border : none;
|
||||
&[open] { display : inline-block; }
|
||||
a {
|
||||
color : white;
|
||||
a{
|
||||
color : #e0e5c1;
|
||||
font-weight : 800;
|
||||
color : #E0E5C1;
|
||||
}
|
||||
i.info {
|
||||
i.info{
|
||||
position : absolute;
|
||||
top : 12px;
|
||||
left : 12px;
|
||||
font-size : 2.5em;
|
||||
opacity : 0.8;
|
||||
font-size : 2.5em;
|
||||
}
|
||||
button.dismiss {
|
||||
position : absolute;
|
||||
top : 10px;
|
||||
right : 10px;
|
||||
cursor : pointer;
|
||||
background-color : transparent;
|
||||
opacity : 0.6;
|
||||
&:hover { opacity : 1; }
|
||||
i.dismiss{
|
||||
position : absolute;
|
||||
top : 10px;
|
||||
right : 10px;
|
||||
cursor : pointer;
|
||||
opacity : 0.6;
|
||||
&:hover{
|
||||
opacity : 1;
|
||||
}
|
||||
}
|
||||
.header { padding-left : 50px; }
|
||||
small {
|
||||
font-size : 0.6em;
|
||||
.header {
|
||||
padding-left : 50px;
|
||||
}
|
||||
small{
|
||||
opacity : 0.7;
|
||||
font-size : 0.6em;
|
||||
}
|
||||
h3 {
|
||||
h3{
|
||||
font-size : 1.1em;
|
||||
font-weight : 800;
|
||||
}
|
||||
ul {
|
||||
ul{
|
||||
margin-top : 15px;
|
||||
font-size : 0.8em;
|
||||
list-style-position : outside;
|
||||
list-style-type : disc;
|
||||
li {
|
||||
margin-top : 1.4em;
|
||||
li{
|
||||
font-size : 0.8em;
|
||||
line-height : 1.4em;
|
||||
em { font-weight : 800; }
|
||||
margin-top : 1.4em;
|
||||
em{
|
||||
font-weight : 800;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
require('./toolBar.less');
|
||||
const React = require('react');
|
||||
const { useState, useEffect } = React;
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
const MAX_ZOOM = 300;
|
||||
const MIN_ZOOM = 10;
|
||||
|
||||
const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
|
||||
|
||||
const [zoomLevel, setZoomLevel] = useState(100);
|
||||
const [pageNum, setPageNum] = useState(currentPage);
|
||||
|
||||
useEffect(()=>{
|
||||
onZoomChange(zoomLevel);
|
||||
}, [zoomLevel]);
|
||||
|
||||
useEffect(()=>{
|
||||
setPageNum(currentPage);
|
||||
}, [currentPage]);
|
||||
|
||||
const handleZoomButton = (zoom)=>{
|
||||
setZoomLevel(_.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
|
||||
};
|
||||
|
||||
const handlePageInput = (pageInput)=>{
|
||||
if(/[0-9]/.test(pageInput))
|
||||
setPageNum(parseInt(pageInput)); // input type is 'text', so `page` comes in as a string, not number.
|
||||
};
|
||||
|
||||
const scrollToPage = (pageNumber)=>{
|
||||
pageNumber = _.clamp(pageNumber, 1, totalPages);
|
||||
const iframe = document.getElementById('BrewRenderer');
|
||||
const brewRenderer = iframe?.contentWindow?.document.querySelector('.brewRenderer');
|
||||
const page = brewRenderer?.querySelector(`#p${pageNumber}`);
|
||||
page?.scrollIntoView({ block: 'start' });
|
||||
setPageNum(pageNumber);
|
||||
};
|
||||
|
||||
|
||||
const calculateChange = (mode)=>{
|
||||
const iframe = document.getElementById('BrewRenderer');
|
||||
const iframeWidth = iframe.getBoundingClientRect().width;
|
||||
const iframeHeight = iframe.getBoundingClientRect().height;
|
||||
const pages = iframe.contentWindow.document.getElementsByClassName('page');
|
||||
|
||||
let desiredZoom = 0;
|
||||
|
||||
if(mode == 'fill'){
|
||||
// find widest page, in case pages are different widths, so that the zoom is adapted to not cut the widest page off screen.
|
||||
const widestPage = _.maxBy([...pages], 'offsetWidth').offsetWidth;
|
||||
|
||||
desiredZoom = (iframeWidth / widestPage) * 100;
|
||||
|
||||
} else if(mode == 'fit'){
|
||||
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
|
||||
const minDimRatio = [...pages].reduce((minRatio, page) => Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
|
||||
|
||||
desiredZoom = minDimRatio * 100;
|
||||
}
|
||||
|
||||
const margin = 5; // extra space so page isn't edge to edge (not truly "to fill")
|
||||
|
||||
const deltaZoom = (desiredZoom - zoomLevel) - margin;
|
||||
return deltaZoom;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='toolBar'>
|
||||
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
|
||||
<div className='group'>
|
||||
<button
|
||||
id='fill-width'
|
||||
className='tool'
|
||||
onClick={()=>handleZoomButton(zoomLevel + calculateChange('fill'))}
|
||||
>
|
||||
<i className='fac fit-width' />
|
||||
</button>
|
||||
<button
|
||||
id='zoom-to-fit'
|
||||
className='tool'
|
||||
onClick={()=>handleZoomButton(zoomLevel + calculateChange('fit'))}
|
||||
>
|
||||
<i className='fac zoom-to-fit' />
|
||||
</button>
|
||||
<button
|
||||
id='zoom-out'
|
||||
className='tool'
|
||||
onClick={()=>handleZoomButton(zoomLevel - 20)}
|
||||
disabled={zoomLevel <= MIN_ZOOM}
|
||||
>
|
||||
<i className='fas fa-magnifying-glass-minus' />
|
||||
</button>
|
||||
<input
|
||||
id='zoom-slider'
|
||||
className='range-input tool'
|
||||
type='range'
|
||||
name='zoom'
|
||||
list='zoomLevels'
|
||||
min={MIN_ZOOM}
|
||||
max={MAX_ZOOM}
|
||||
step='1'
|
||||
value={zoomLevel}
|
||||
onChange={(e)=>handleZoomButton(parseInt(e.target.value))}
|
||||
/>
|
||||
<datalist id='zoomLevels'>
|
||||
<option value='100' />
|
||||
</datalist>
|
||||
|
||||
<button
|
||||
id='zoom-in'
|
||||
className='tool'
|
||||
onClick={()=>handleZoomButton(zoomLevel + 20)}
|
||||
disabled={zoomLevel >= MAX_ZOOM}
|
||||
>
|
||||
<i className='fas fa-magnifying-glass-plus' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/*v=====----------------------< Page Controls >---------------------=====v*/}
|
||||
<div className='group'>
|
||||
<button
|
||||
id='previous-page'
|
||||
className='previousPage tool'
|
||||
onClick={()=>scrollToPage(pageNum - 1)}
|
||||
disabled={pageNum <= 1}
|
||||
>
|
||||
<i className='fas fa-arrow-left'></i>
|
||||
</button>
|
||||
|
||||
<div className='tool'>
|
||||
<input
|
||||
id='page-input'
|
||||
className='text-input'
|
||||
type='text'
|
||||
name='page'
|
||||
inputMode='numeric'
|
||||
pattern='[0-9]'
|
||||
value={pageNum}
|
||||
onClick={(e)=>e.target.select()}
|
||||
onChange={(e)=>handlePageInput(e.target.value)}
|
||||
onBlur={()=>scrollToPage(pageNum)}
|
||||
onKeyDown={(e)=>e.key == 'Enter' && scrollToPage(pageNum)}
|
||||
/>
|
||||
<span id='page-count'>/ {totalPages}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id='next-page'
|
||||
className='tool'
|
||||
onClick={()=>scrollToPage(pageNum + 1)}
|
||||
disabled={pageNum >= totalPages}
|
||||
>
|
||||
<i className='fas fa-arrow-right'></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = ToolBar;
|
||||
@@ -1,103 +0,0 @@
|
||||
@import (less) './client/icons/customIcons.less';
|
||||
|
||||
.toolBar {
|
||||
position : absolute;
|
||||
z-index : 1;
|
||||
box-sizing : border-box;
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
gap : 8px 30px;
|
||||
align-items : center;
|
||||
justify-content : center;
|
||||
width : 100%;
|
||||
height : auto;
|
||||
padding : 2px 0;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
color : #CCCCCC;
|
||||
background-color : #555555;
|
||||
|
||||
.group {
|
||||
box-sizing : border-box;
|
||||
display : flex;
|
||||
gap : 0 3px;
|
||||
align-items : center;
|
||||
justify-content : center;
|
||||
height : 28px;
|
||||
}
|
||||
|
||||
.tool {
|
||||
display : flex;
|
||||
align-items : center;
|
||||
}
|
||||
|
||||
input {
|
||||
position : relative;
|
||||
height : 1.5em;
|
||||
padding : 2px 5px;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
color : #000000;
|
||||
background : #EEEEEE;
|
||||
border : 1px solid gray;
|
||||
&:focus { outline : 1px solid #D3D3D3; }
|
||||
|
||||
// `.range-input` if generic to all range inputs, or `#zoom-slider` if only for zoom slider
|
||||
&.range-input {
|
||||
padding : 2px 0;
|
||||
color : #D3D3D3;
|
||||
accent-color : #D3D3D3;
|
||||
|
||||
&::-webkit-slider-thumb, &::-moz-slider-thumb {
|
||||
width : 5px;
|
||||
height : 5px;
|
||||
cursor : pointer;
|
||||
outline : none;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
position : absolute;
|
||||
bottom : -30px;
|
||||
left : 50%;
|
||||
z-index : 1;
|
||||
display : grid;
|
||||
place-items : center;
|
||||
width : 4ch;
|
||||
height : 1.2lh;
|
||||
pointer-events : none;
|
||||
content : attr(value);
|
||||
background-color : #555555;
|
||||
border : 1px solid #A1A1A1;
|
||||
transform : translate(-50%, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
// `.text-input` if generic to all range inputs, or `#page-input` if only for current page input
|
||||
&#page-input {
|
||||
width : 4ch;
|
||||
margin-right : 1ch;
|
||||
text-align : center;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
box-sizing : content-box;
|
||||
display : flex;
|
||||
align-items : center;
|
||||
justify-content : center;
|
||||
width : auto;
|
||||
min-width : 46px;
|
||||
height : 100%;
|
||||
padding : 0 0px;
|
||||
font-weight : unset;
|
||||
color : inherit;
|
||||
background-color : unset;
|
||||
&:hover { background-color : #444444; }
|
||||
&:focus { outline : 1px solid #D3D3D3; }
|
||||
&:disabled {
|
||||
color : #777777;
|
||||
background-color : unset !important;
|
||||
}
|
||||
i {
|
||||
font-size:1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,11 @@ const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
const Markdown = require('../../../shared/naturalcrit/markdown.js');
|
||||
|
||||
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
||||
const SnippetBar = require('./snippetbar/snippetbar.jsx');
|
||||
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
||||
|
||||
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
|
||||
|
||||
const SNIPPETBAR_HEIGHT = 25;
|
||||
const DEFAULT_STYLE_TEXT = dedent`
|
||||
/*=======--- Example CSS styling ---=======*/
|
||||
@@ -35,22 +32,16 @@ const Editor = createClass({
|
||||
onTextChange : ()=>{},
|
||||
onStyleChange : ()=>{},
|
||||
onMetaChange : ()=>{},
|
||||
reportError : ()=>{},
|
||||
|
||||
editorTheme : 'default',
|
||||
renderer : 'legacy'
|
||||
renderer : 'legacy'
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
editorTheme : this.props.editorTheme,
|
||||
view : 'text' //'text', 'style', 'meta'
|
||||
view : 'text' //'text', 'style', 'meta'
|
||||
};
|
||||
},
|
||||
|
||||
editor : React.createRef(null),
|
||||
codeEditor : React.createRef(null),
|
||||
|
||||
isText : function() {return this.state.view == 'text';},
|
||||
isStyle : function() {return this.state.view == 'style';},
|
||||
isMeta : function() {return this.state.view == 'meta';},
|
||||
@@ -59,15 +50,6 @@ const Editor = createClass({
|
||||
this.updateEditorSize();
|
||||
this.highlightCustomMarkdown();
|
||||
window.addEventListener('resize', this.updateEditorSize);
|
||||
document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys);
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
|
||||
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
||||
if(editorTheme) {
|
||||
this.setState({
|
||||
editorTheme : editorTheme
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount : function() {
|
||||
@@ -84,43 +66,27 @@ const Editor = createClass({
|
||||
};
|
||||
},
|
||||
|
||||
handleControlKeys : function(e){
|
||||
if(!(e.ctrlKey && e.metaKey)) return;
|
||||
const LEFTARROW_KEY = 37;
|
||||
const RIGHTARROW_KEY = 39;
|
||||
if (e.shiftKey && (e.keyCode == RIGHTARROW_KEY)) this.brewJump();
|
||||
if (e.shiftKey && (e.keyCode == LEFTARROW_KEY)) this.sourceJump();
|
||||
if ((e.keyCode == LEFTARROW_KEY) || (e.keyCode == RIGHTARROW_KEY)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
updateEditorSize : function() {
|
||||
if(this.codeEditor.current) {
|
||||
let paneHeight = this.editor.current.parentNode.clientHeight;
|
||||
paneHeight -= SNIPPETBAR_HEIGHT;
|
||||
this.codeEditor.current.codeMirror.setSize(null, paneHeight);
|
||||
if(this.refs.codeEditor) {
|
||||
let paneHeight = this.refs.main.parentNode.clientHeight;
|
||||
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||
}
|
||||
},
|
||||
|
||||
handleInject : function(injectText){
|
||||
this.codeEditor.current?.injectText(injectText, false);
|
||||
this.refs.codeEditor?.injectText(injectText, false);
|
||||
},
|
||||
|
||||
handleViewChange : function(newView){
|
||||
this.props.setMoveArrows(newView === 'text');
|
||||
this.setState({
|
||||
view : newView
|
||||
}, ()=>{
|
||||
this.codeEditor.current?.codeMirror.focus();
|
||||
this.updateEditorSize();
|
||||
}); //TODO: not sure if updateeditorsize needed
|
||||
}, this.updateEditorSize); //TODO: not sure if updateeditorsize needed
|
||||
},
|
||||
|
||||
getCurrentPage : function(){
|
||||
const lines = this.props.brew.text.split('\n').slice(0, this.codeEditor.current.getCursorPosition().line + 1);
|
||||
const lines = this.props.brew.text.split('\n').slice(0, this.refs.codeEditor.getCursorPosition().line + 1);
|
||||
return _.reduce(lines, (r, line)=>{
|
||||
if(
|
||||
(this.props.renderer == 'legacy' && line.indexOf('\\page') !== -1)
|
||||
@@ -132,24 +98,13 @@ const Editor = createClass({
|
||||
},
|
||||
|
||||
highlightCustomMarkdown : function(){
|
||||
if(!this.codeEditor.current) return;
|
||||
if(!this.refs.codeEditor) return;
|
||||
if(this.state.view === 'text') {
|
||||
const codeMirror = this.codeEditor.current.codeMirror;
|
||||
const codeMirror = this.refs.codeEditor.codeMirror;
|
||||
|
||||
codeMirror.operation(()=>{ // Batch CodeMirror styling
|
||||
|
||||
const foldLines = [];
|
||||
|
||||
//reset custom text styles
|
||||
const customHighlights = codeMirror.getAllMarks().filter((mark)=>{
|
||||
// Record details of folded sections
|
||||
if(mark.__isFold) {
|
||||
const fold = mark.find();
|
||||
foldLines.push({from: fold.from?.line, to: fold.to?.line});
|
||||
}
|
||||
return !mark.__isFold;
|
||||
}); //Don't undo code folding
|
||||
|
||||
const customHighlights = codeMirror.getAllMarks().filter((mark)=>!mark.__isFold); //Don't undo code folding
|
||||
for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear();
|
||||
|
||||
let editorPageCount = 2; // start page count from page 2
|
||||
@@ -161,11 +116,6 @@ const Editor = createClass({
|
||||
codeMirror.removeLineClass(lineNumber, 'text');
|
||||
codeMirror.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash');
|
||||
|
||||
// Don't process lines inside folded text
|
||||
// If the current lineNumber is inside any folded marks, skip line styling
|
||||
if (foldLines.some(fold => lineNumber >= fold.from && lineNumber <= fold.to))
|
||||
return;
|
||||
|
||||
// Styling for \page breaks
|
||||
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
|
||||
(this.props.renderer == 'V3' && line.match(/^\\page$/))) {
|
||||
@@ -187,53 +137,17 @@ const Editor = createClass({
|
||||
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||
}
|
||||
|
||||
// definition lists
|
||||
if(line.includes('::')){
|
||||
if(/^:*$/.test(line) == true){ return };
|
||||
const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error.
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null){
|
||||
codeMirror.markText({ line: lineNumber, ch: match.indices[0][0] }, { line: lineNumber, ch: match.indices[0][1] }, { className: 'dl-highlight' });
|
||||
codeMirror.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' });
|
||||
codeMirror.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' });
|
||||
const ddIndex = match.indices[2][0];
|
||||
let colons = /::/g;
|
||||
let colonMatches = colons.exec(match[2]);
|
||||
if(colonMatches !== null){
|
||||
codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight'} )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subscript & Superscript
|
||||
if(line.includes('^')) {
|
||||
let startIndex = line.indexOf('^');
|
||||
const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy;
|
||||
const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy;
|
||||
|
||||
while (startIndex >= 0) {
|
||||
superRegex.lastIndex = subRegex.lastIndex = startIndex;
|
||||
let isSuper = false;
|
||||
let match = subRegex.exec(line) || superRegex.exec(line);
|
||||
if (match) {
|
||||
isSuper = !subRegex.lastIndex;
|
||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' });
|
||||
}
|
||||
startIndex = line.indexOf('^', Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight injectors {style}
|
||||
if(line.includes('{') && line.includes('}')){
|
||||
const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm;
|
||||
const regex = /(?<!{){(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
|
||||
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' });
|
||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'injection' });
|
||||
}
|
||||
}
|
||||
// Highlight inline spans {{content}}
|
||||
if(line.includes('{{') && line.includes('}}')){
|
||||
const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g;
|
||||
const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g;
|
||||
let match;
|
||||
let blockCount = 0;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
@@ -252,39 +166,11 @@ const Editor = createClass({
|
||||
// Highlight block divs {{\n Content \n}}
|
||||
let endCh = line.length+1;
|
||||
|
||||
const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/);
|
||||
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' });
|
||||
}
|
||||
|
||||
// Emojis
|
||||
if(line.match(/:[^\s:]+:/g)) {
|
||||
let startIndex = line.indexOf(':');
|
||||
const emojiRegex = /:[^\s:]+:/gy;
|
||||
|
||||
while (startIndex >= 0) {
|
||||
emojiRegex.lastIndex = startIndex;
|
||||
let match = emojiRegex.exec(line);
|
||||
if (match) {
|
||||
let tokens = Markdown.marked.lexer(match[0]);
|
||||
tokens = tokens[0].tokens.filter(t => t.type == 'emoji')
|
||||
if (!tokens.length)
|
||||
return;
|
||||
|
||||
let startPos = { line: lineNumber, ch: match.index };
|
||||
let endPos = { line: lineNumber, ch: match.index + match[0].length };
|
||||
|
||||
// Iterate over conflicting marks and clear them
|
||||
var marks = codeMirror.findMarks(startPos, endPos);
|
||||
marks.forEach(function(marker) {
|
||||
if(!marker.__isFold) marker.clear();
|
||||
});
|
||||
codeMirror.markText(startPos, endPos, { className: 'emoji' });
|
||||
}
|
||||
startIndex = line.indexOf(':', Math.max(startIndex + 1, emojiRegex.lastIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -339,23 +225,23 @@ const Editor = createClass({
|
||||
|
||||
targetLine = lineCount - 1; //Scroll to `\page`, which is one line back.
|
||||
|
||||
let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top;
|
||||
let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
|
||||
let currentY = this.refs.codeEditor.codeMirror.getScrollInfo().top;
|
||||
let targetY = this.refs.codeEditor.codeMirror.heightAtLine(targetLine, 'local', true);
|
||||
|
||||
//Scroll 1/10 of the way every 10ms until 1px off.
|
||||
const incrementalScroll = setInterval(()=>{
|
||||
currentY += (targetY - currentY) / 10;
|
||||
this.codeEditor.current.codeMirror.scrollTo(null, currentY);
|
||||
this.refs.codeEditor.codeMirror.scrollTo(null, currentY);
|
||||
|
||||
// Update target: target height is not accurate until within +-10 lines of the visible window
|
||||
if(Math.abs(targetY - currentY > 100))
|
||||
targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
|
||||
targetY = this.refs.codeEditor.codeMirror.heightAtLine(targetLine, 'local', true);
|
||||
|
||||
// End when close enough
|
||||
if(Math.abs(targetY - currentY) < 1) {
|
||||
this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference
|
||||
this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 });
|
||||
this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
|
||||
this.refs.codeEditor.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference
|
||||
this.refs.codeEditor.setCursorPosition({ line: targetLine + 1, ch: 0 });
|
||||
this.refs.codeEditor.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
|
||||
clearInterval(incrementalScroll);
|
||||
}
|
||||
}, 10);
|
||||
@@ -365,14 +251,7 @@ const Editor = createClass({
|
||||
|
||||
//Called when there are changes to the editor's dimensions
|
||||
update : function(){
|
||||
this.codeEditor.current?.updateSize();
|
||||
},
|
||||
|
||||
updateEditorTheme : function(newTheme){
|
||||
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
||||
this.setState({
|
||||
editorTheme : newTheme
|
||||
});
|
||||
this.refs.codeEditor?.updateSize();
|
||||
},
|
||||
|
||||
//Called by CodeEditor after document switch, so Snippetbar can refresh UndoHistory
|
||||
@@ -384,25 +263,23 @@ const Editor = createClass({
|
||||
if(this.isText()){
|
||||
return <>
|
||||
<CodeEditor key='codeEditor'
|
||||
ref={this.codeEditor}
|
||||
ref='codeEditor'
|
||||
language='gfm'
|
||||
view={this.state.view}
|
||||
value={this.props.brew.text}
|
||||
onChange={this.props.onTextChange}
|
||||
editorTheme={this.state.editorTheme}
|
||||
rerenderParent={this.rerenderParent} />
|
||||
</>;
|
||||
}
|
||||
if(this.isStyle()){
|
||||
return <>
|
||||
<CodeEditor key='codeEditor'
|
||||
ref={this.codeEditor}
|
||||
ref='codeEditor'
|
||||
language='css'
|
||||
view={this.state.view}
|
||||
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
|
||||
onChange={this.props.onStyleChange}
|
||||
enableFolding={true}
|
||||
editorTheme={this.state.editorTheme}
|
||||
enableFolding={false}
|
||||
rerenderParent={this.rerenderParent} />
|
||||
</>;
|
||||
}
|
||||
@@ -414,36 +291,26 @@ const Editor = createClass({
|
||||
rerenderParent={this.rerenderParent} />
|
||||
<MetadataEditor
|
||||
metadata={this.props.brew}
|
||||
onChange={this.props.onMetaChange}
|
||||
reportError={this.props.reportError}
|
||||
userThemes={this.props.userThemes}/>
|
||||
onChange={this.props.onMetaChange} />
|
||||
</>;
|
||||
}
|
||||
},
|
||||
|
||||
redo : function(){
|
||||
return this.codeEditor.current?.redo();
|
||||
return this.refs.codeEditor?.redo();
|
||||
},
|
||||
|
||||
historySize : function(){
|
||||
return this.codeEditor.current?.historySize();
|
||||
return this.refs.codeEditor?.historySize();
|
||||
},
|
||||
|
||||
undo : function(){
|
||||
return this.codeEditor.current?.undo();
|
||||
},
|
||||
|
||||
foldCode : function(){
|
||||
return this.codeEditor.current?.foldAllCode();
|
||||
},
|
||||
|
||||
unfoldCode : function(){
|
||||
return this.codeEditor.current?.unfoldAllCode();
|
||||
return this.refs.codeEditor?.undo();
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return (
|
||||
<div className='editor' ref={this.editor}>
|
||||
<div className='editor' ref='main'>
|
||||
<SnippetBar
|
||||
brew={this.props.brew}
|
||||
view={this.state.view}
|
||||
@@ -454,13 +321,7 @@ const Editor = createClass({
|
||||
theme={this.props.brew.theme}
|
||||
undo={this.undo}
|
||||
redo={this.redo}
|
||||
foldCode={this.foldCode}
|
||||
unfoldCode={this.unfoldCode}
|
||||
historySize={this.historySize()}
|
||||
currentEditorTheme={this.state.editorTheme}
|
||||
updateEditorTheme={this.updateEditorTheme}
|
||||
snippetBundle={this.props.snippetBundle}
|
||||
cursorPos={this.codeEditor.current?.getCursorPosition() || {}} />
|
||||
historySize={this.historySize()} />
|
||||
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
|
||||
@@ -1,105 +1,65 @@
|
||||
@import 'themes/codeMirror/customEditorStyles.less';
|
||||
.editor {
|
||||
|
||||
.editor{
|
||||
position : relative;
|
||||
width : 100%;
|
||||
|
||||
.codeEditor {
|
||||
.codeEditor{
|
||||
height : 100%;
|
||||
.pageLine {
|
||||
.pageLine{
|
||||
background : #33333328;
|
||||
border-top : #333399 solid 1px;
|
||||
border-top : #339 solid 1px;
|
||||
}
|
||||
.editor-page-count {
|
||||
float : right;
|
||||
.editor-page-count{
|
||||
color : grey;
|
||||
float : right;
|
||||
}
|
||||
.columnSplit {
|
||||
font-style : italic;
|
||||
color : grey;
|
||||
background-color : fade(#229999, 15%);
|
||||
border-bottom : #229999 solid 1px;
|
||||
.columnSplit{
|
||||
font-style : italic;
|
||||
color : grey;
|
||||
background-color : fade(#299, 15%);
|
||||
border-bottom : #299 solid 1px;
|
||||
}
|
||||
.define {
|
||||
&:not(.term):not(.definition) {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
background : #E5E5E5;
|
||||
border-radius : 3px;
|
||||
}
|
||||
&.term { color : rgb(96, 117, 143); }
|
||||
&.definition { color : rgb(97, 57, 178); }
|
||||
}
|
||||
.block:not(.cm-comment) {
|
||||
.block{
|
||||
color : purple;
|
||||
font-weight : bold;
|
||||
color : purple;
|
||||
//font-style: italic;
|
||||
}
|
||||
.inline-block:not(.cm-comment) {
|
||||
.inline-block{
|
||||
color : red;
|
||||
font-weight : bold;
|
||||
color : red;
|
||||
//font-style: italic;
|
||||
}
|
||||
.injection:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
.injection{
|
||||
color : green;
|
||||
font-weight : bold;
|
||||
}
|
||||
.emoji:not(.cm-comment) {
|
||||
margin-left : 2px;
|
||||
color : #360034;
|
||||
background : #ffc8ff;
|
||||
border-radius : 6px;
|
||||
font-weight : bold;
|
||||
padding-bottom : 1px;
|
||||
outline-offset : -2px;
|
||||
outline : solid 2px #ff96fc;
|
||||
}
|
||||
.superscript:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : goldenrod;
|
||||
vertical-align : super;
|
||||
font-size : 0.9em;
|
||||
}
|
||||
.subscript:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : rgb(123, 123, 15);
|
||||
vertical-align : sub;
|
||||
font-size : 0.9em;
|
||||
}
|
||||
.dl-highlight {
|
||||
&.dl-colon-highlight {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
background : #E5E5E5;
|
||||
border-radius : 3px;
|
||||
}
|
||||
&.dt-highlight { color : rgb(96, 117, 143); }
|
||||
&.dd-highlight { color : rgb(97, 57, 178); }
|
||||
}
|
||||
}
|
||||
|
||||
.brewJump {
|
||||
position : absolute;
|
||||
right : 20px;
|
||||
bottom : 20px;
|
||||
z-index : 1000000;
|
||||
display : flex;
|
||||
align-items : center;
|
||||
justify-content : center;
|
||||
width : 30px;
|
||||
height : 30px;
|
||||
cursor : pointer;
|
||||
background-color : @teal;
|
||||
.tooltipLeft('Jump to brew page');
|
||||
.brewJump{
|
||||
position : absolute;
|
||||
background-color : @teal;
|
||||
cursor : pointer;
|
||||
width : 30px;
|
||||
height : 30px;
|
||||
display : flex;
|
||||
align-items : center;
|
||||
bottom : 20px;
|
||||
right : 20px;
|
||||
z-index : 1000000;
|
||||
justify-content : center;
|
||||
.tooltipLeft("Jump to brew page");
|
||||
}
|
||||
|
||||
.editorToolbar {
|
||||
position : absolute;
|
||||
top : 5px;
|
||||
left : 50%;
|
||||
z-index : 9;
|
||||
font-size : 13px;
|
||||
color : black;
|
||||
span { padding : 2px 5px; }
|
||||
.editorToolbar{
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 50%;
|
||||
color: black;
|
||||
font-size: 13px;
|
||||
z-index: 9;
|
||||
span {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,32 +3,24 @@ require('./metadataEditor.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
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 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() {
|
||||
return {
|
||||
metadata : {
|
||||
editId : null,
|
||||
shareId : null,
|
||||
title : '',
|
||||
description : '',
|
||||
thumbnail : '',
|
||||
@@ -37,11 +29,9 @@ const MetadataEditor = createClass({
|
||||
authors : [],
|
||||
systems : [],
|
||||
renderer : 'legacy',
|
||||
theme : '5ePHB',
|
||||
lang : 'en'
|
||||
theme : '5ePHB'
|
||||
},
|
||||
onChange : ()=>{},
|
||||
reportError : ()=>{}
|
||||
onChange : ()=>{}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -63,26 +53,28 @@ const MetadataEditor = createClass({
|
||||
},
|
||||
|
||||
handleFieldChange : function(name, e){
|
||||
e.persist();
|
||||
|
||||
// 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', '');
|
||||
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.
|
||||
console.log(validationErr);
|
||||
const errMessage = validationErr.map((err)=>{
|
||||
return `- ${err}`;
|
||||
}).join('\n');
|
||||
|
||||
callIfExists(e.target, 'setCustomValidity', errMessage);
|
||||
callIfExists(e.target, 'reportValidity');
|
||||
}
|
||||
e.target.setCustomValidity(errMessage);
|
||||
e.target.reportValidity();
|
||||
};
|
||||
},
|
||||
|
||||
handleSystem : function(system, e){
|
||||
@@ -100,7 +92,7 @@ const MetadataEditor = createClass({
|
||||
if(renderer == 'legacy')
|
||||
this.props.metadata.theme = '5ePHB';
|
||||
}
|
||||
this.props.onChange(this.props.metadata, 'renderer');
|
||||
this.props.onChange(this.props.metadata);
|
||||
},
|
||||
handlePublish : function(val){
|
||||
this.props.onChange({
|
||||
@@ -112,11 +104,6 @@ const MetadataEditor = createClass({
|
||||
handleTheme : function(theme){
|
||||
this.props.metadata.renderer = theme.renderer;
|
||||
this.props.metadata.theme = theme.path;
|
||||
this.props.onChange(this.props.metadata, 'theme');
|
||||
},
|
||||
|
||||
handleLanguage : function(languageCode){
|
||||
this.props.metadata.lang = languageCode;
|
||||
this.props.onChange(this.props.metadata);
|
||||
},
|
||||
|
||||
@@ -131,12 +118,8 @@ const MetadataEditor = createClass({
|
||||
|
||||
request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`)
|
||||
.send()
|
||||
.end((err, res)=>{
|
||||
if(err) {
|
||||
this.props.reportError(err);
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
.end(function(err, res){
|
||||
window.location.href = '/';
|
||||
});
|
||||
},
|
||||
|
||||
@@ -193,42 +176,33 @@ const MetadataEditor = createClass({
|
||||
renderThemeDropdown : function(){
|
||||
if(!global.enable_themes) return;
|
||||
|
||||
const mergedThemes = _.merge(Themes, this.props.userThemes);
|
||||
|
||||
const listThemes = (renderer)=>{
|
||||
return _.map(_.values(mergedThemes[renderer]), (theme)=>{
|
||||
if(theme.path == this.props.metadata.shareId) return;
|
||||
const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`;
|
||||
const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`;
|
||||
return <div className='item' key={`${renderer}_${theme.name}`} onClick={()=>this.handleTheme(theme)} title={''}>
|
||||
{theme.author ?? renderer} : {theme.name}
|
||||
<div className='texture-container'>
|
||||
<img src={texture}/>
|
||||
</div>
|
||||
<div className='preview'>
|
||||
<h6>{theme.name} preview</h6>
|
||||
<img src={preview}/>
|
||||
</div>
|
||||
return _.map(_.values(Themes[renderer]), (theme)=>{
|
||||
return <div className='item' key={''} onClick={()=>this.handleTheme(theme)} title={''}>
|
||||
{`${theme.renderer} : ${theme.name}`}
|
||||
<img src={`/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`}/>
|
||||
</div>;
|
||||
});
|
||||
};
|
||||
|
||||
const currentRenderer = this.props.metadata.renderer;
|
||||
const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme]
|
||||
?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` };
|
||||
const currentTheme = Themes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme];
|
||||
let dropdown;
|
||||
|
||||
if(currentRenderer == 'legacy') {
|
||||
if(this.props.metadata.renderer == 'legacy') {
|
||||
dropdown =
|
||||
<Nav.dropdown className='disabled value' trigger='disabled'>
|
||||
<div> {`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i> </div>
|
||||
<Nav.dropdown className='disabled' trigger='disabled'>
|
||||
<div>
|
||||
{`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i>
|
||||
</div>
|
||||
</Nav.dropdown>;
|
||||
} else {
|
||||
dropdown =
|
||||
<Nav.dropdown className='value' trigger='click'>
|
||||
<div> {currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name} <i className='fas fa-caret-down'></i> </div>
|
||||
|
||||
{listThemes(currentRenderer)}
|
||||
<Nav.dropdown trigger='click'>
|
||||
<div>
|
||||
{`${_.upperFirst(currentTheme.renderer)} : ${currentTheme.name}`} <i className='fas fa-caret-down'></i>
|
||||
</div>
|
||||
{/*listThemes('Legacy')*/}
|
||||
{listThemes('V3')}
|
||||
</Nav.dropdown>;
|
||||
}
|
||||
|
||||
@@ -238,47 +212,6 @@ const MetadataEditor = createClass({
|
||||
</div>;
|
||||
},
|
||||
|
||||
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 <div className='item' title={`${englishName.of(code)}`} key={`${index}`} data-value={`${code}`} data-detail={`${localName.of(code)}`}>
|
||||
{`${code}`}
|
||||
<div className='detail'>{`${localName.of(code)}`}</div>
|
||||
</div>;
|
||||
});
|
||||
};
|
||||
|
||||
const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500);
|
||||
|
||||
return <div className='field language'>
|
||||
<label>language</label>
|
||||
<div className='value'>
|
||||
<Combobox trigger='click'
|
||||
className='language-dropdown'
|
||||
default={this.props.metadata.lang || ''}
|
||||
placeholder='en'
|
||||
onSelect={(value)=>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']
|
||||
}}
|
||||
>
|
||||
</Combobox>
|
||||
<small>Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.</small>
|
||||
</div>
|
||||
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderRenderOptions : function(){
|
||||
if(!global.enable_v3) return;
|
||||
|
||||
@@ -314,8 +247,6 @@ const MetadataEditor = createClass({
|
||||
|
||||
render : function(){
|
||||
return <div className='metadataEditor'>
|
||||
<h1 className='sectionHead'>Brew</h1>
|
||||
|
||||
<div className='field title'>
|
||||
<label>title</label>
|
||||
<input type='text' className='value'
|
||||
@@ -349,6 +280,8 @@ const MetadataEditor = createClass({
|
||||
values={this.props.metadata.tags}
|
||||
onChange={(e)=>this.handleFieldChange('tags', e)}/>
|
||||
|
||||
{this.renderAuthors()}
|
||||
|
||||
<div className='field systems'>
|
||||
<label>systems</label>
|
||||
<div className='value'>
|
||||
@@ -356,29 +289,10 @@ const MetadataEditor = createClass({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.renderLanguageDropdown()}
|
||||
|
||||
{this.renderThemeDropdown()}
|
||||
|
||||
{this.renderRenderOptions()}
|
||||
|
||||
<hr/>
|
||||
|
||||
<h1 className='sectionHead'>Authors</h1>
|
||||
|
||||
{this.renderAuthors()}
|
||||
|
||||
<StringArrayEditor label='invited authors' valuePatterns={[/.+/]}
|
||||
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
|
||||
placeholder='invite author' unique={true}
|
||||
values={this.props.metadata.invitedAuthors}
|
||||
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
|
||||
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h1 className='sectionHead'>Privacy</h1>
|
||||
|
||||
<div className='field publish'>
|
||||
<label>publish</label>
|
||||
<div className='value'>
|
||||
|
||||
@@ -1,309 +1,277 @@
|
||||
@import 'naturalcrit/styles/colors.less';
|
||||
|
||||
.metadataEditor {
|
||||
.metadataEditor{
|
||||
position : absolute;
|
||||
z-index : 5;
|
||||
z-index : 10000;
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
|
||||
padding : 25px;
|
||||
background-color : #999;
|
||||
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
|
||||
overflow-y : auto;
|
||||
background-color : #999999;
|
||||
|
||||
.sectionHead {
|
||||
margin : 20px 0;
|
||||
font-weight : 1000;
|
||||
|
||||
&:first-of-type { margin-top : 0; }
|
||||
& > div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
& > div { margin-bottom : 10px; }
|
||||
|
||||
.field-group {
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
gap : 10px;
|
||||
width : 100%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.field-column {
|
||||
display : flex;
|
||||
flex : 5 0 200px;
|
||||
flex-direction : column;
|
||||
gap : 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 5 0 200px;
|
||||
gap: 10px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.field {
|
||||
position : relative;
|
||||
.field{
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
width : 100%;
|
||||
min-width : 200px;
|
||||
& > label {
|
||||
&>label{
|
||||
width : 80px;
|
||||
font-size : 11px;
|
||||
font-weight : 800;
|
||||
line-height : 1.8em;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
& > .value {
|
||||
&>.value{
|
||||
flex : 1 1 auto;
|
||||
width : 50px;
|
||||
&:invalid { background : #FFB9B9; }
|
||||
&:invalid {
|
||||
background : #ffb9b9;
|
||||
}
|
||||
}
|
||||
input[type='text'], textarea {
|
||||
border : 1px solid gray;
|
||||
&:focus { outline : 1px solid #444444; }
|
||||
}
|
||||
&.thumbnail {
|
||||
&.thumbnail{
|
||||
height : 1.4em;
|
||||
label { line-height : 2.0em; }
|
||||
.value {
|
||||
overflow : hidden;
|
||||
text-overflow : ellipsis;
|
||||
label{
|
||||
line-height: 2.0em;
|
||||
}
|
||||
button {
|
||||
padding : 0px 5px;
|
||||
color : white;
|
||||
background-color : black;
|
||||
border : 1px solid #999999;
|
||||
&:hover { background-color : #777777; }
|
||||
.value{
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
button{
|
||||
border: 1px solid #999;
|
||||
color: white;
|
||||
padding: 0px 5px;
|
||||
background-color: black;
|
||||
&:hover{
|
||||
background-color: #777;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.description {
|
||||
flex : 1;
|
||||
flex: 1;
|
||||
textarea.value {
|
||||
resize : none;
|
||||
height : auto;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-size : 0.8em;
|
||||
resize : none;
|
||||
}
|
||||
}
|
||||
|
||||
&.language .language-dropdown {
|
||||
z-index : 200;
|
||||
max-width : 150px;
|
||||
}
|
||||
small {
|
||||
display : inline-block;
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
line-height : 1.4em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.thumbnail-preview {
|
||||
position : relative;
|
||||
flex : 1 1;
|
||||
justify-self : center;
|
||||
width : 80px;
|
||||
height : min-content;
|
||||
max-height : 115px;
|
||||
aspect-ratio : 1 / 1;
|
||||
object-fit : contain;
|
||||
background-color : #AAAAAA;
|
||||
position: relative;
|
||||
justify-self: center;
|
||||
width: 80px;
|
||||
height: min-content;
|
||||
flex: 1 1;
|
||||
max-height: 115px;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: contain;
|
||||
background-color: #AAA;
|
||||
}
|
||||
|
||||
.systems.field .value {
|
||||
label {
|
||||
display : inline-flex;
|
||||
align-items : center;
|
||||
.systems.field .value{
|
||||
label{
|
||||
vertical-align : middle;
|
||||
margin-right : 15px;
|
||||
cursor : pointer;
|
||||
font-size : 0.7em;
|
||||
font-weight : 800;
|
||||
white-space : nowrap;
|
||||
vertical-align : middle;
|
||||
cursor : pointer;
|
||||
user-select : none;
|
||||
white-space : nowrap;
|
||||
display : inline-flex;
|
||||
align-items : center;
|
||||
}
|
||||
a {
|
||||
display : inline-flex;
|
||||
font-size : 0.7em;
|
||||
font-weight : 800;
|
||||
display : inline-flex;
|
||||
}
|
||||
input {
|
||||
margin : 3px;
|
||||
input{
|
||||
vertical-align : middle;
|
||||
cursor : pointer;
|
||||
margin : 3px;
|
||||
}
|
||||
}
|
||||
.publish.field .value {
|
||||
position : relative;
|
||||
margin-bottom : 15px;
|
||||
button { width : 100%; }
|
||||
button.publish {
|
||||
.publish.field .value{
|
||||
position : relative;
|
||||
margin-bottom: 15px;
|
||||
button{
|
||||
width:100%;
|
||||
}
|
||||
button.publish{
|
||||
.button(@blueLight);
|
||||
}
|
||||
button.unpublish {
|
||||
button.unpublish{
|
||||
.button(@silver);
|
||||
}
|
||||
small{
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
}
|
||||
}
|
||||
|
||||
.delete.field .value {
|
||||
button {
|
||||
.delete.field .value{
|
||||
button{
|
||||
.button(@red);
|
||||
}
|
||||
}
|
||||
.authors.field .value {
|
||||
font-size : 0.8em;
|
||||
.authors.field .value{
|
||||
font-size: 0.8em;
|
||||
line-height : 1.5em;
|
||||
}
|
||||
|
||||
.themes.field {
|
||||
.themes.field{
|
||||
font-size : 13.33px;
|
||||
.navDropdownContainer {
|
||||
position : relative;
|
||||
z-index : 100;
|
||||
background-color : white;
|
||||
background-color : white;
|
||||
width : 100%;
|
||||
position : relative;
|
||||
z-index : 500;
|
||||
&.disabled {
|
||||
font-style : italic;
|
||||
color : dimgray;
|
||||
background-color : darkgray;
|
||||
font-style :italic;
|
||||
font-style : italic;
|
||||
background-color : darkgray;
|
||||
color : dimgray;
|
||||
}
|
||||
& > div:first-child {
|
||||
padding : 6px 3px;
|
||||
background-color : inherit;
|
||||
border : 2px solid rgb(118,118,118);
|
||||
i { float : right; }
|
||||
&>div:first-child {
|
||||
border : 2px solid rgb(118,118,118);
|
||||
padding : 6px 3px;
|
||||
background-color : inherit;
|
||||
i {
|
||||
float : right;
|
||||
}
|
||||
&:hover {
|
||||
color : white;
|
||||
background-color : @blue;
|
||||
background-color : @blue;
|
||||
color : white;
|
||||
}
|
||||
}
|
||||
.navDropdown .item > p {
|
||||
width : 45%;
|
||||
height : 1.1em;
|
||||
overflow : hidden;
|
||||
text-overflow : ellipsis;
|
||||
white-space : nowrap;
|
||||
}
|
||||
.navDropdown {
|
||||
position : absolute;
|
||||
width : 100%;
|
||||
box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3);
|
||||
position : absolute;
|
||||
width : 100%;
|
||||
.item {
|
||||
position : relative;
|
||||
padding : 3px 3px;
|
||||
overflow : visible;
|
||||
background-color : white;
|
||||
border-top : 1px solid rgb(118, 118, 118);
|
||||
.preview {
|
||||
position : absolute;
|
||||
top : 0;
|
||||
right : 0;
|
||||
z-index : 1;
|
||||
display : flex;
|
||||
flex-direction : column;
|
||||
width : 200px;
|
||||
overflow : hidden;
|
||||
color : black;
|
||||
background : #CCCCCC;
|
||||
border-radius : 5px;
|
||||
box-shadow : 0 0 5px black;
|
||||
opacity : 0;
|
||||
transition : opacity 250ms ease;
|
||||
h6 {
|
||||
padding-block : 0.5em;
|
||||
padding-inline : 1em;
|
||||
font-weight : 900;
|
||||
border-bottom : 2px solid hsl(0,0%,40%);
|
||||
}
|
||||
}
|
||||
padding : 3px 3px;
|
||||
border-top : 1px solid rgb(118, 118, 118);
|
||||
position : relative;
|
||||
overflow : hidden;
|
||||
background-color : white;
|
||||
&:hover {
|
||||
color : white;
|
||||
background-color : @blue;
|
||||
background-color : @blue;
|
||||
color : white;
|
||||
}
|
||||
&:hover > .preview { opacity : 1; }
|
||||
.texture-container {
|
||||
position : absolute;
|
||||
top : 0;
|
||||
left : 0;
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
min-height : 100%;
|
||||
overflow : hidden;
|
||||
> img {
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0;
|
||||
width : 50%;
|
||||
min-height : 100%;
|
||||
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||
mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||
}
|
||||
img {
|
||||
mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||
position : absolute;
|
||||
left : ~"max(100px, 100% - 300px)";
|
||||
top : 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.field .list {
|
||||
display : flex;
|
||||
flex : 1 0;
|
||||
flex-wrap : wrap;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * { flex : 0 0 auto; }
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
#groupedIcon {
|
||||
#backgroundColors;
|
||||
position : relative;
|
||||
top : -0.3em;
|
||||
right : -0.3em;
|
||||
display : inline-block;
|
||||
min-width : 20px;
|
||||
height : ~'calc(100% + 0.6em)';
|
||||
color : white;
|
||||
text-align : center;
|
||||
cursor : pointer;
|
||||
display: inline-block;
|
||||
height: ~"calc(100% + 0.6em)";
|
||||
position: relative;
|
||||
top: -0.3em;
|
||||
right: -0.3em;
|
||||
cursor: pointer;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
|
||||
i {
|
||||
position : relative;
|
||||
top : 50%;
|
||||
transform : translateY(-50%);
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&:not(:last-child) { border-right : 1px solid black; }
|
||||
&:not(:last-child) {
|
||||
border-right: 1px solid black;
|
||||
}
|
||||
|
||||
&:last-child { border-radius : 0 0.5em 0.5em 0; }
|
||||
&:last-child {
|
||||
border-radius: 0 0.5em 0.5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding : 0.3em;
|
||||
margin : 2px;
|
||||
font-size : 0.9em;
|
||||
background-color : #DDDDDD;
|
||||
border-radius : 0.5em;
|
||||
background-color: #dddddd;
|
||||
border-radius: .5em;
|
||||
font-size: .9em;
|
||||
margin: 2px;
|
||||
padding: .3em;
|
||||
|
||||
.icon {
|
||||
#groupedIcon; }
|
||||
#groupedIcon
|
||||
}
|
||||
}
|
||||
|
||||
.input-group {
|
||||
height : ~'calc(.9em + 4px + .6em)';
|
||||
height: ~"calc(.9em + 4px + .6em)";
|
||||
|
||||
input { border-radius : 0.5em 0 0 0.5em; }
|
||||
|
||||
input:last-child { border-radius : 0.5em; }
|
||||
|
||||
.value {
|
||||
width : 7.5vw;
|
||||
min-width : 75px;
|
||||
height : 100%;
|
||||
input {
|
||||
border-radius: .5em 0 0 .5em;
|
||||
}
|
||||
|
||||
.invalid:focus { background-color : pink; }
|
||||
input:last-child {
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
.value {
|
||||
width: 7.5vw;
|
||||
min-width: 75px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.invalid:focus {
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
.icon {
|
||||
#groupedIcon;
|
||||
top : -0.54em;
|
||||
right : 1px;
|
||||
height : 97%;
|
||||
font-size : 0.8em;
|
||||
height: 97%;
|
||||
font-size: .8em;
|
||||
right: 1px;
|
||||
top: -.54em;
|
||||
|
||||
i { font-size : 1.125em; }
|
||||
i {
|
||||
font-size: 1.125em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
],
|
||||
lang : [
|
||||
language : [
|
||||
(value)=>{
|
||||
return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null;
|
||||
return new RegExp(/[a-z]{2,3}(-.*)?/).test(value || '') === false ? 'Invalid language code.' : null;
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/*eslint max-lines: ["warn", {"max": 250, "skipBlankLines": true, "skipComments": true}]*/
|
||||
require('./snippetbar.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
@@ -6,6 +5,9 @@ const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
//Import all themes
|
||||
|
||||
const Themes = require('themes/themes.json');
|
||||
|
||||
const ThemeSnippets = {};
|
||||
ThemeSnippets['Legacy_5ePHB'] = require('themes/Legacy/5ePHB/snippets.js');
|
||||
ThemeSnippets['V3_5ePHB'] = require('themes/V3/5ePHB/snippets.js');
|
||||
@@ -13,10 +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 EditorThemes = require('build/homebrew/codeMirror/editorThemes.json');
|
||||
|
||||
const execute = function(val, props){
|
||||
if(_.isFunction(val)) return val(props);
|
||||
const execute = function(val, brew){
|
||||
if(_.isFunction(val)) return val(brew);
|
||||
return val;
|
||||
};
|
||||
|
||||
@@ -24,69 +24,68 @@ const Snippetbar = createClass({
|
||||
displayName : 'SnippetBar',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {},
|
||||
view : 'text',
|
||||
onViewChange : ()=>{},
|
||||
onInject : ()=>{},
|
||||
onToggle : ()=>{},
|
||||
showEditButtons : true,
|
||||
renderer : 'legacy',
|
||||
undo : ()=>{},
|
||||
redo : ()=>{},
|
||||
historySize : ()=>{},
|
||||
foldCode : ()=>{},
|
||||
unfoldCode : ()=>{},
|
||||
updateEditorTheme : ()=>{},
|
||||
cursorPos : {},
|
||||
snippetBundle : []
|
||||
brew : {},
|
||||
view : 'text',
|
||||
onViewChange : ()=>{},
|
||||
onInject : ()=>{},
|
||||
onToggle : ()=>{},
|
||||
showEditButtons : true,
|
||||
renderer : 'legacy',
|
||||
undo : ()=>{},
|
||||
redo : ()=>{},
|
||||
historySize : ()=>{}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
renderer : this.props.renderer,
|
||||
themeSelector : false,
|
||||
snippets : []
|
||||
renderer : this.props.renderer,
|
||||
snippets : []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : async function() {
|
||||
const snippets = this.compileSnippets();
|
||||
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
|
||||
const themePath = this.props.theme ?? '5ePHB';
|
||||
let snippets = _.cloneDeep(ThemeSnippets[`${rendererPath}_${themePath}`]);
|
||||
snippets = this.compileSnippets(rendererPath, themePath, snippets);
|
||||
this.setState({
|
||||
snippets : snippets
|
||||
});
|
||||
},
|
||||
|
||||
componentDidUpdate : async function(prevProps) {
|
||||
if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) {
|
||||
const snippets = this.compileSnippets();
|
||||
if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme) {
|
||||
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
|
||||
const themePath = this.props.theme ?? '5ePHB';
|
||||
let snippets = _.cloneDeep(ThemeSnippets[`${rendererPath}_${themePath}`]);
|
||||
snippets = this.compileSnippets(rendererPath, themePath, snippets);
|
||||
this.setState({
|
||||
snippets : snippets
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mergeCustomizer : function(oldValue, newValue, key) {
|
||||
mergeCustomizer : function(valueA, valueB, key) {
|
||||
if(key == 'snippets') {
|
||||
const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme
|
||||
const result = _.reverse(_.unionBy(_.reverse(valueB), _.reverse(valueA), 'name')); // Join snippets together, with preference for the current theme over the base theme
|
||||
return _.filter(result, 'gen'); //Only keep snippets with a 'gen' property.
|
||||
}
|
||||
},
|
||||
|
||||
compileSnippets : function() {
|
||||
let compiledSnippets = [];
|
||||
compileSnippets : function(rendererPath, themePath, snippets) {
|
||||
let compiledSnippets = snippets;
|
||||
const baseSnippetsPath = Themes[rendererPath][themePath].baseSnippets;
|
||||
|
||||
let oldSnippets = _.keyBy(compiledSnippets, 'groupName');
|
||||
const objB = _.keyBy(compiledSnippets, 'groupName');
|
||||
|
||||
for (let snippets of this.props.snippetBundle) {
|
||||
if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name
|
||||
snippets = ThemeSnippets[snippets];
|
||||
|
||||
const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName');
|
||||
compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer));
|
||||
|
||||
oldSnippets = _.keyBy(compiledSnippets, 'groupName');
|
||||
if(baseSnippetsPath) {
|
||||
const objA = _.keyBy(_.cloneDeep(ThemeSnippets[`${rendererPath}_${baseSnippetsPath}`]), 'groupName');
|
||||
compiledSnippets = _.values(_.mergeWith(objA, objB, this.mergeCustomizer));
|
||||
compiledSnippets = this.compileSnippets(rendererPath, baseSnippetsPath, _.cloneDeep(compiledSnippets));
|
||||
} else {
|
||||
const objA = _.keyBy(_.cloneDeep(ThemeSnippets[`${rendererPath}_Blank`]), 'groupName');
|
||||
compiledSnippets = _.values(_.mergeWith(objA, objB, this.mergeCustomizer));
|
||||
}
|
||||
return compiledSnippets;
|
||||
},
|
||||
@@ -95,33 +94,6 @@ const Snippetbar = createClass({
|
||||
this.props.onInject(injectedText);
|
||||
},
|
||||
|
||||
toggleThemeSelector : function(e){
|
||||
if(e.target.tagName != 'SELECT'){
|
||||
this.setState({
|
||||
themeSelector : !this.state.themeSelector
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
changeTheme : function(e){
|
||||
if(e.target.value == this.props.currentEditorTheme) return;
|
||||
this.props.updateEditorTheme(e.target.value);
|
||||
|
||||
this.setState({
|
||||
showThemeSelector : false,
|
||||
});
|
||||
},
|
||||
|
||||
renderThemeSelector : function(){
|
||||
return <div className='themeSelector'>
|
||||
<select value={this.props.currentEditorTheme} onChange={this.changeTheme} >
|
||||
{EditorThemes.map((theme, key)=>{
|
||||
return <option key={key} value={theme}>{theme}</option>;
|
||||
})}
|
||||
</select>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderSnippetGroups : function(){
|
||||
const snippets = this.state.snippets.filter((snippetGroup)=>snippetGroup.view === this.props.view);
|
||||
|
||||
@@ -133,7 +105,6 @@ const Snippetbar = createClass({
|
||||
snippets={snippetGroup.snippets}
|
||||
key={snippetGroup.groupName}
|
||||
onSnippetClick={this.handleSnippetClick}
|
||||
cursorPos={this.props.cursorPos}
|
||||
/>;
|
||||
});
|
||||
},
|
||||
@@ -141,22 +112,6 @@ const Snippetbar = createClass({
|
||||
renderEditorButtons : function(){
|
||||
if(!this.props.showEditButtons) return;
|
||||
|
||||
let foldButtons;
|
||||
if(this.props.view == 'text'){
|
||||
foldButtons =
|
||||
<>
|
||||
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
|
||||
onClick={this.props.foldCode} >
|
||||
<i className='fas fa-compress-alt' />
|
||||
</div>
|
||||
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
|
||||
onClick={this.props.unfoldCode} >
|
||||
<i className='fas fa-expand-alt' />
|
||||
</div>
|
||||
</>;
|
||||
|
||||
}
|
||||
|
||||
return <div className='editors'>
|
||||
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
|
||||
onClick={this.props.undo} >
|
||||
@@ -166,14 +121,6 @@ const Snippetbar = createClass({
|
||||
onClick={this.props.redo} >
|
||||
<i className='fas fa-redo' />
|
||||
</div>
|
||||
<div className='divider'></div>
|
||||
{foldButtons}
|
||||
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
|
||||
onClick={this.toggleThemeSelector} >
|
||||
<i className='fas fa-palette' />
|
||||
{this.state.themeSelector && this.renderThemeSelector()}
|
||||
</div>
|
||||
|
||||
<div className='divider'></div>
|
||||
<div className={cx('text', { selected: this.props.view === 'text' })}
|
||||
onClick={()=>this.props.onViewChange('text')}>
|
||||
@@ -216,23 +163,15 @@ const SnippetGroup = createClass({
|
||||
onSnippetClick : function(){},
|
||||
};
|
||||
},
|
||||
handleSnippetClick : function(e, snippet){
|
||||
e.stopPropagation();
|
||||
this.props.onSnippetClick(execute(snippet.gen, this.props));
|
||||
handleSnippetClick : function(snippet){
|
||||
this.props.onSnippetClick(execute(snippet.gen, this.props.brew));
|
||||
},
|
||||
renderSnippets : function(snippets){
|
||||
return _.map(snippets, (snippet)=>{
|
||||
return <div className='snippet' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)}>
|
||||
renderSnippets : function(){
|
||||
return _.map(this.props.snippets, (snippet)=>{
|
||||
return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}>
|
||||
<i className={snippet.icon} />
|
||||
<span className='name'title={snippet.name}>{snippet.name}</span>
|
||||
{snippet.experimental && <span className='beta'>beta</span>}
|
||||
{snippet.subsnippets && <>
|
||||
<i className='fas fa-caret-right'></i>
|
||||
<div className='dropdown side'>
|
||||
{this.renderSnippets(snippet.subsnippets)}
|
||||
</div></>}
|
||||
{snippet.name}
|
||||
</div>;
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
@@ -243,8 +182,9 @@ const SnippetGroup = createClass({
|
||||
<span className='groupName'>{this.props.groupName}</span>
|
||||
</div>
|
||||
<div className='dropdown'>
|
||||
{this.renderSnippets(this.props.snippets)}
|
||||
{this.renderSnippets()}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -1,192 +1,113 @@
|
||||
@import (less) './client/icons/customIcons.less';
|
||||
@import (less) '././././themes/fonts/5e/fonts.less';
|
||||
|
||||
.snippetBar {
|
||||
.snippetBar{
|
||||
@menuHeight : 25px;
|
||||
position : relative;
|
||||
height : @menuHeight;
|
||||
color : black;
|
||||
background-color : #DDDDDD;
|
||||
|
||||
.editors {
|
||||
background-color : #ddd;
|
||||
.editors{
|
||||
position : absolute;
|
||||
display : flex;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
display : flex;
|
||||
justify-content : space-between;
|
||||
height : @menuHeight;
|
||||
& > div {
|
||||
width : @menuHeight;
|
||||
width : 125px;
|
||||
justify-content : space-between;
|
||||
&>div{
|
||||
height : @menuHeight;
|
||||
width : @menuHeight;
|
||||
cursor : pointer;
|
||||
line-height : @menuHeight;
|
||||
text-align : center;
|
||||
cursor : pointer;
|
||||
&:hover,&.selected { background-color : #999999; }
|
||||
&.text {
|
||||
&:hover,&.selected{
|
||||
background-color : #999;
|
||||
}
|
||||
&.text{
|
||||
.tooltipLeft('Brew Editor');
|
||||
}
|
||||
&.style {
|
||||
&.style{
|
||||
.tooltipLeft('Style Editor');
|
||||
}
|
||||
&.meta {
|
||||
&.meta{
|
||||
.tooltipLeft('Properties');
|
||||
}
|
||||
&.undo {
|
||||
&.undo{
|
||||
.tooltipLeft('Undo');
|
||||
font-size : 0.75em;
|
||||
color : grey;
|
||||
&.active { color : inherit; }
|
||||
&.active{
|
||||
color : black;
|
||||
}
|
||||
}
|
||||
&.redo {
|
||||
&.redo{
|
||||
.tooltipLeft('Redo');
|
||||
font-size : 0.75em;
|
||||
color : grey;
|
||||
&.active { color : inherit; }
|
||||
}
|
||||
&.foldAll {
|
||||
.tooltipLeft('Fold All');
|
||||
font-size : 0.75em;
|
||||
color : inherit;
|
||||
}
|
||||
&.unfoldAll {
|
||||
.tooltipLeft('Unfold All');
|
||||
font-size : 0.75em;
|
||||
color : inherit;
|
||||
}
|
||||
&.editorTheme {
|
||||
.tooltipLeft('Editor Themes');
|
||||
font-size : 0.75em;
|
||||
color : black;
|
||||
&.active {
|
||||
position : relative;
|
||||
background-color : #999999;
|
||||
&.active{
|
||||
color : black;
|
||||
}
|
||||
}
|
||||
&.divider {
|
||||
width : 5px;
|
||||
background : linear-gradient(currentColor, currentColor) no-repeat center/1px 100%;
|
||||
&:hover { background-color : inherit; }
|
||||
background: linear-gradient(#000, #000) no-repeat center/1px 100%;
|
||||
width: 5px;
|
||||
&:hover{
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
.themeSelector {
|
||||
position : absolute;
|
||||
top : 25px;
|
||||
right : 0;
|
||||
z-index : 10;
|
||||
display : flex;
|
||||
align-items : center;
|
||||
justify-content : center;
|
||||
width : 170px;
|
||||
height : inherit;
|
||||
background-color : inherit;
|
||||
}
|
||||
}
|
||||
.snippetBarButton {
|
||||
display : inline-block;
|
||||
.snippetBarButton{
|
||||
height : @menuHeight;
|
||||
padding : 0px 5px;
|
||||
font-size : 0.625em;
|
||||
font-weight : 800;
|
||||
line-height : @menuHeight;
|
||||
display : inline-block;
|
||||
padding : 0px 5px;
|
||||
font-weight : 800;
|
||||
font-size : 0.625em;
|
||||
text-transform : uppercase;
|
||||
cursor : pointer;
|
||||
&:hover, &.selected { background-color : #999999; }
|
||||
i {
|
||||
&:hover, &.selected{
|
||||
background-color : #999;
|
||||
}
|
||||
i{
|
||||
vertical-align : middle;
|
||||
margin-right : 3px;
|
||||
font-size : 1.4em;
|
||||
vertical-align : middle;
|
||||
}
|
||||
}
|
||||
.toggleMeta {
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
border-left : 1px solid black;
|
||||
.tooltipLeft('Edit Brew Properties');
|
||||
.toggleMeta{
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
border-left : 1px solid black;
|
||||
.tooltipLeft("Edit Brew Properties");
|
||||
}
|
||||
.snippetGroup {
|
||||
border-right : 1px solid currentColor;
|
||||
&:hover {
|
||||
& > .dropdown { visibility : visible; }
|
||||
.snippetGroup{
|
||||
border-right : 1px solid black;
|
||||
&:hover{
|
||||
.dropdown{
|
||||
visibility : visible;
|
||||
}
|
||||
}
|
||||
.dropdown {
|
||||
.dropdown{
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
z-index : 1000;
|
||||
padding : 0px;
|
||||
margin-left : -5px;
|
||||
visibility : hidden;
|
||||
background-color : #DDDDDD;
|
||||
.snippet {
|
||||
position : relative;
|
||||
display : flex;
|
||||
align-items : center;
|
||||
min-width : max-content;
|
||||
padding : 5px;
|
||||
font-size : 10px;
|
||||
cursor : pointer;
|
||||
z-index : 1000;
|
||||
margin-left : -5px;
|
||||
padding : 0px;
|
||||
background-color : #ddd;
|
||||
.snippet{
|
||||
.animate(background-color);
|
||||
i {
|
||||
height : 1.2em;
|
||||
padding : 5px;
|
||||
cursor : pointer;
|
||||
font-size : 10px;
|
||||
i{
|
||||
margin-right : 8px;
|
||||
font-size : 1.2em;
|
||||
min-width: 25px;
|
||||
text-align: center;
|
||||
& ~ i {
|
||||
margin-right : 0;
|
||||
margin-left : 5px;
|
||||
}
|
||||
/* Fonts */
|
||||
&.font {
|
||||
height : auto;
|
||||
&::before {
|
||||
font-size : 1em;
|
||||
content : 'ABC';
|
||||
}
|
||||
|
||||
&.OpenSans {font-family : 'OpenSans';}
|
||||
&.CodeBold {font-family : 'CodeBold';}
|
||||
&.CodeLight {font-family : 'CodeLight';}
|
||||
&.ScalySansRemake {font-family : 'ScalySansRemake';}
|
||||
&.BookInsanityRemake {font-family : 'BookInsanityRemake';}
|
||||
&.MrEavesRemake {font-family : 'MrEavesRemake';}
|
||||
&.SolberaImitationRemake {font-family : 'SolberaImitationRemake';}
|
||||
&.ScalySansSmallCapsRemake {font-family : 'ScalySansSmallCapsRemake';}
|
||||
&.WalterTurncoat {font-family : 'WalterTurncoat';}
|
||||
&.Lato {font-family : 'Lato';}
|
||||
&.Courier {font-family : 'Courier';}
|
||||
&.NodestoCapsCondensed {font-family : 'NodestoCapsCondensed';}
|
||||
&.Overpass {font-family : 'Overpass';}
|
||||
&.Davek {font-family : 'Davek';}
|
||||
&.Iokharic {font-family : 'Iokharic';}
|
||||
&.Rellanic {font-family : 'Rellanic';}
|
||||
&.TimesNewRoman {font-family : 'Times New Roman';}
|
||||
}
|
||||
}
|
||||
.name { margin-right : auto; }
|
||||
.beta {
|
||||
align-self : center;
|
||||
padding : 4px 6px;
|
||||
margin-left : 5px;
|
||||
font-family : monospace;
|
||||
line-height : 1em;
|
||||
color : white;
|
||||
background : grey;
|
||||
border-radius : 12px;
|
||||
}
|
||||
&:hover {
|
||||
background-color : #999999;
|
||||
& > .dropdown {
|
||||
visibility : visible;
|
||||
&.side {
|
||||
top : 0%;
|
||||
left : 100%;
|
||||
margin-left : 0;
|
||||
box-shadow : -1px 1px 2px 0px #999999;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
background-color : #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ const StringArrayEditor = createClass({
|
||||
label : '',
|
||||
values : [],
|
||||
valuePatterns : null,
|
||||
validators : [],
|
||||
placeholder : '',
|
||||
notes : [],
|
||||
unique : false,
|
||||
cannotEdit : [],
|
||||
onChange : ()=>{}
|
||||
@@ -85,8 +83,7 @@ const StringArrayEditor = createClass({
|
||||
}
|
||||
const matchesPatterns = !this.props.valuePatterns || this.props.valuePatterns.some((pattern)=>!!(value || '').match(pattern));
|
||||
const uniqueIfSet = !this.props.unique || !values.includes(value);
|
||||
const passesValidators = !this.props.validators || this.props.validators.every((validator)=>validator(value));
|
||||
return matchesPatterns && uniqueIfSet && passesValidators;
|
||||
return matchesPatterns && uniqueIfSet;
|
||||
},
|
||||
|
||||
handleValueInputKeyDown : function(event, index) {
|
||||
@@ -126,21 +123,17 @@ const StringArrayEditor = createClass({
|
||||
</div>
|
||||
);
|
||||
|
||||
return <div className='field'>
|
||||
return <div className='field values'>
|
||||
<label>{this.props.label}</label>
|
||||
<div style={{ flex: '1 0' }}>
|
||||
<div className='list'>
|
||||
{valueElements}
|
||||
<div className='input-group'>
|
||||
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
|
||||
value={this.state.temporaryValue}
|
||||
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
|
||||
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
|
||||
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
|
||||
</div>
|
||||
<div className='list'>
|
||||
{valueElements}
|
||||
<div className='input-group'>
|
||||
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
|
||||
value={this.state.temporaryValue}
|
||||
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
|
||||
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
|
||||
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
|
||||
</div>
|
||||
|
||||
{this.props.notes ? this.props.notes.map((n, index)=><p key={index}><small>{n}</small></p>) : null}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
BIN
client/homebrew/googleDrive.png
Normal file
|
After Width: | Height: | Size: 305 KiB |
@@ -1,8 +0,0 @@
|
||||
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da"/>
|
||||
<path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47"/>
|
||||
<path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335"/>
|
||||
<path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d"/>
|
||||
<path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc"/>
|
||||
<path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 755 B |
BIN
client/homebrew/googleDriveMono.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -9,8 +9,8 @@ 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 VaultPage = require('./pages/vaultPage/vaultPage.jsx');
|
||||
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||
const AccountPage = require('./pages/accountPage/accountPage.jsx');
|
||||
|
||||
const WithRoute = (props)=>{
|
||||
@@ -47,7 +47,6 @@ const Homebrew = createClass({
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
lang : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -67,20 +66,19 @@ const Homebrew = createClass({
|
||||
<Router location={this.props.url}>
|
||||
<div className='homebrew'>
|
||||
<Routes>
|
||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={this.props.brew} userThemes={this.props.userThemes}/>} />
|
||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={this.props.brew} />} />
|
||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} userThemes={this.props.userThemes}/>} />
|
||||
<Route path='/new' element={<WithRoute el={NewPage} userThemes={this.props.userThemes}/> } />
|
||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} />} />
|
||||
<Route path='/new' element={<WithRoute el={NewPage}/>} />
|
||||
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
|
||||
<Route path='/vault' element={<WithRoute el={VaultPage}/>}/>
|
||||
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} disableMeta={true} />} />
|
||||
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} disableMeta={true} />} />
|
||||
<Route path='/migrate' element={<WithRoute el={SharePage} brew={this.props.brew} disableMeta={true} />} />
|
||||
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} accountDetails={this.props.brew.accountDetails} />} />
|
||||
<Route path='/print/:id' element={<WithRoute el={PrintPage} brew={this.props.brew} />} />
|
||||
<Route path='/print' element={<WithRoute el={PrintPage} />} />
|
||||
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<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} />} />
|
||||
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
@@ -88,4 +86,15 @@ const Homebrew = createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Homebrew;
|
||||
module.exports = Homebrew;
|
||||
|
||||
//TODO: Nicer Error page instead of just "cant get that"
|
||||
// '/share/:id' : (args)=>{
|
||||
// if(!this.props.brew.shareId){
|
||||
// return <ErrorPage errorId={args.id}/>;
|
||||
// }
|
||||
//
|
||||
// return <SharePage
|
||||
// id={args.id}
|
||||
// brew={this.props.brew} />;
|
||||
// },
|
||||
|
||||
@@ -15,23 +15,6 @@
|
||||
}
|
||||
&.listPage .content {
|
||||
overflow-y : scroll;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 20px;
|
||||
&:horizontal{
|
||||
height: 20px;
|
||||
width:auto;
|
||||
}
|
||||
&-thumb {
|
||||
background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px);
|
||||
&:horizontal{
|
||||
background: linear-gradient(0deg, #d3c1af 15px, #00000000 15px);
|
||||
}
|
||||
}
|
||||
&-corner {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ const Account = createClass({
|
||||
if(global.account){
|
||||
return <Nav.dropdown>
|
||||
<Nav.item
|
||||
className='account username'
|
||||
className='account'
|
||||
color='orange'
|
||||
icon='fas fa-user'
|
||||
>
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
require('./error-navitem.less');
|
||||
const React = require('react');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const createClass = require('create-react-class');
|
||||
|
||||
const ErrorNavItem = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
error : '',
|
||||
parent : null
|
||||
};
|
||||
},
|
||||
render : function() {
|
||||
const clearError = ()=>{
|
||||
const state = {
|
||||
error : null
|
||||
};
|
||||
if(this.props.parent.state.isSaving) {
|
||||
state.isSaving = false;
|
||||
}
|
||||
this.props.parent.setState(state);
|
||||
};
|
||||
|
||||
const error = this.props.error;
|
||||
const response = error.response;
|
||||
const status = response.status;
|
||||
const HBErrorCode = response.body?.HBErrorCode;
|
||||
const message = response.body?.message;
|
||||
let errMsg = '';
|
||||
try {
|
||||
errMsg += `${error.toString()}\n\n`;
|
||||
errMsg += `\`\`\`\n${error.stack}\n`;
|
||||
errMsg += `${JSON.stringify(response.error, null, ' ')}\n\`\`\``;
|
||||
console.log(errMsg);
|
||||
} catch (e){}
|
||||
|
||||
if(status === 409) {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
{message ?? 'Conflict: please refresh to get latest changes'}
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(status === 412) {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(HBErrorCode === '04') {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
You are no longer signed in as an author of
|
||||
this brew! Were you signed out from a different
|
||||
window? Visit our log in page, then try again!
|
||||
<br></br>
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(response.body?.errors?.[0].reason == 'storageQuotaExceeded') {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
Can't save because your Google Drive seems to be full!
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(response.req.url.match(/^\/api.*Google.*$/m)){
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
Looks like your Google credentials have
|
||||
expired! Visit our log in page to sign out
|
||||
and sign back in with Google,
|
||||
then try saving again!
|
||||
<br></br>
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(HBErrorCode === '09') {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
Looks like there was a problem retreiving
|
||||
the theme, or a theme that it inherits,
|
||||
for this brew. Verify that brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
|
||||
{response.body.brewId}</a> still exists!
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorNavItem;
|
||||
@@ -1,78 +0,0 @@
|
||||
.navItem.error {
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 1000;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
.lowercase {
|
||||
text-transform : none;
|
||||
}
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const Moment = require('moment');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
|
||||
const MetadataNav = createClass({
|
||||
displayName : 'MetadataNav',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
showMetaWindow : false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
},
|
||||
|
||||
toggleMetaWindow : function(){
|
||||
this.setState((prevProps)=>({
|
||||
showMetaWindow : !prevProps.showMetaWindow
|
||||
}));
|
||||
},
|
||||
|
||||
getAuthors : function(){
|
||||
if(!this.props.brew.authors || this.props.brew.authors.length == 0) return 'No authors';
|
||||
return <>
|
||||
{this.props.brew.authors.map((author, idx, arr)=>{
|
||||
const spacer = arr.length - 1 == idx ? <></> : <span>, </span>;
|
||||
return <span key={idx}><a className='userPageLink' href={`/user/${author}`}>{author}</a>{spacer}</span>;
|
||||
})}
|
||||
</>;
|
||||
},
|
||||
|
||||
getTags : function(){
|
||||
if(!this.props.brew.tags || this.props.brew.tags.length == 0) return 'No tags';
|
||||
return <>
|
||||
{this.props.brew.tags.map((tag, idx)=>{
|
||||
return <span className='tag' key={idx}>{tag}</span>;
|
||||
})}
|
||||
</>;
|
||||
},
|
||||
|
||||
getSystems : function(){
|
||||
if(!this.props.brew.systems || this.props.brew.systems.length == 0) return 'No systems';
|
||||
return this.props.brew.systems.join(', ');
|
||||
},
|
||||
|
||||
renderMetaWindow : function(){
|
||||
return <div className={`window ${this.state.showMetaWindow ? 'active' : 'inactive'}`}>
|
||||
<div className='row'>
|
||||
<h4>Description</h4>
|
||||
<p>{this.props.brew.description || 'No description.'}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Authors</h4>
|
||||
<p>{this.getAuthors()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Tags</h4>
|
||||
<p>{this.getTags()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Systems</h4>
|
||||
<p>{this.getSystems()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Updated</h4>
|
||||
<p>{Moment(this.props.brew.updatedAt).fromNow()}</p>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fas fa-info-circle' color='grey' className='metadata'
|
||||
onClick={()=>this.toggleMetaWindow()}>
|
||||
{this.props.children}
|
||||
{this.renderMetaWindow()}
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = MetadataNav;
|
||||
@@ -1,352 +1,190 @@
|
||||
@import 'naturalcrit/styles/colors.less';
|
||||
|
||||
@navbarHeight : 28px;
|
||||
@viewerToolsHeight : 32px;
|
||||
|
||||
@keyframes pinkColoring {
|
||||
0% { color : pink; }
|
||||
50% { color : pink; }
|
||||
75% { color : red; }
|
||||
100% { color : pink; }
|
||||
}
|
||||
|
||||
@keyframes glideDropDown {
|
||||
0% {
|
||||
background-color : #333333;
|
||||
opacity : 0;
|
||||
transform : translate(0px, -100%);
|
||||
}
|
||||
100% {
|
||||
background-color : #333333;
|
||||
opacity : 1;
|
||||
transform : translate(0px, 0px);
|
||||
}
|
||||
}
|
||||
|
||||
.homebrew nav {
|
||||
background-color : #333333;
|
||||
.navContent {
|
||||
position : relative;
|
||||
z-index : 2;
|
||||
display : flex;
|
||||
justify-content : space-between;
|
||||
}
|
||||
.navSection {
|
||||
display : flex;
|
||||
align-items : center;
|
||||
&:last-child .navItem { border-left : 1px solid #666666; }
|
||||
}
|
||||
// "NaturalCrit" logo
|
||||
.navLogo {
|
||||
display : block;
|
||||
margin-top : 0px;
|
||||
margin-right : 8px;
|
||||
margin-left : 8px;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
&:hover {
|
||||
.name { color : @orange; }
|
||||
svg { fill : @orange; }
|
||||
}
|
||||
svg {
|
||||
height : 13px;
|
||||
margin-right : 0.2em;
|
||||
cursor : pointer;
|
||||
fill : white;
|
||||
}
|
||||
span.name {
|
||||
font-family : 'CodeLight';
|
||||
font-size : 15px;
|
||||
span.crit { font-family : 'CodeBold'; }
|
||||
small {
|
||||
font-family : 'Open Sans';
|
||||
font-size : 0.3em;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
.navItem {
|
||||
#backgroundColorsHover;
|
||||
.animate(background-color);
|
||||
padding : 8px 12px;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
line-height : 13px;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
text-transform : uppercase;
|
||||
cursor : pointer;
|
||||
background-color : #333333;
|
||||
i {
|
||||
float : right;
|
||||
margin-left : 5px;
|
||||
font-size : 13px;
|
||||
}
|
||||
&.patreon {
|
||||
border-right : 1px solid #666666;
|
||||
border-left : 1px solid #666666;
|
||||
&:hover i { color : red; }
|
||||
i {
|
||||
color : pink;
|
||||
.animate(color);
|
||||
animation-name : pinkColoring;
|
||||
animation-duration : 2s;
|
||||
}
|
||||
}
|
||||
&.editTitle { // this is not needed at all currently - you used to be able to edit the title via the navbar.
|
||||
padding : 2px 12px;
|
||||
input {
|
||||
width : 250px;
|
||||
padding : 2px;
|
||||
margin : 0;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-size : 12px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-align : center;
|
||||
background-color : transparent;
|
||||
border : 1px solid @blue;
|
||||
outline : none;
|
||||
}
|
||||
.charCount {
|
||||
display : inline-block;
|
||||
margin-left : 8px;
|
||||
color : #666666;
|
||||
text-align : right;
|
||||
vertical-align : bottom;
|
||||
&.max { color : @red; }
|
||||
}
|
||||
}
|
||||
&.brewTitle {
|
||||
flex-grow : 1;
|
||||
font-size : 12px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-align : center;
|
||||
text-transform : initial;
|
||||
background-color : transparent;
|
||||
}
|
||||
// "The Homebrewery" logo
|
||||
&.homebrewLogo {
|
||||
.animate(color);
|
||||
font-family : 'CodeBold';
|
||||
font-size : 12px;
|
||||
color : white;
|
||||
div {
|
||||
margin-top : 2px;
|
||||
margin-bottom : -2px;
|
||||
}
|
||||
&:hover { color : @blue; }
|
||||
}
|
||||
&.metadata {
|
||||
position : relative;
|
||||
display : flex;
|
||||
flex-grow : 1;
|
||||
align-items : center;
|
||||
height : 100%;
|
||||
padding : 0;
|
||||
i { margin-right : 10px;}
|
||||
.window {
|
||||
position : absolute;
|
||||
bottom : 0;
|
||||
left : 50%;
|
||||
z-index : -1;
|
||||
display : flex;
|
||||
flex-flow : row wrap;
|
||||
align-content : baseline;
|
||||
justify-content : flex-start;
|
||||
width : 440px;
|
||||
max-height : ~'calc(100vh - 28px)';
|
||||
padding : 0 10px 5px;
|
||||
margin : 0 auto;
|
||||
background-color : #333333;
|
||||
border : 3px solid #444444;
|
||||
border-top : unset;
|
||||
border-radius : 0 0 5px 5px;
|
||||
box-shadow : inset 0 7px 9px -7px #111111;
|
||||
transition : transform 0.4s, opacity 0.4s;
|
||||
&.active {
|
||||
opacity : 1;
|
||||
transform : translateX(-50%) translateY(100%);
|
||||
}
|
||||
&.inactive {
|
||||
opacity : 0;
|
||||
transform : translateX(-50%) translateY(0%);
|
||||
}
|
||||
.row {
|
||||
display : flex;
|
||||
flex-flow : row wrap;
|
||||
width : 100%;
|
||||
h4 {
|
||||
box-sizing : border-box;
|
||||
display : block;
|
||||
flex-basis : 20%;
|
||||
flex-grow : 1;
|
||||
min-width : 76px;
|
||||
padding : 5px 0;
|
||||
color : #BBBBBB;
|
||||
text-align : center;
|
||||
}
|
||||
p {
|
||||
flex-basis : 80%;
|
||||
flex-grow : 1;
|
||||
padding : 5px 0;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-size : 10px;
|
||||
font-weight : normal;
|
||||
text-transform : initial;
|
||||
.tag {
|
||||
display : inline-block;
|
||||
padding : 2px;
|
||||
margin : 2px 2px;
|
||||
background-color : #444444;
|
||||
border : 2px solid grey;
|
||||
border-radius : 5px;
|
||||
}
|
||||
a.userPageLink {
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
&:hover { text-decoration : underline; }
|
||||
}
|
||||
}
|
||||
&:nth-of-type(even) { background-color : #555555; }
|
||||
}
|
||||
}
|
||||
}
|
||||
&.warning {
|
||||
position : relative;
|
||||
color : white;
|
||||
background-color : @orange;
|
||||
&:hover > .dropdown { visibility : visible; }
|
||||
.dropdown {
|
||||
position : absolute;
|
||||
top : 28px;
|
||||
left : 0;
|
||||
z-index : 10000;
|
||||
box-sizing : border-box;
|
||||
display : block;
|
||||
width : 100%;
|
||||
padding : 13px 5px;
|
||||
text-align : center;
|
||||
visibility : hidden;
|
||||
background-color : #333333;
|
||||
}
|
||||
}
|
||||
&.account {
|
||||
min-width : 100px;
|
||||
&.username { text-transform : none;}
|
||||
}
|
||||
}
|
||||
.navDropdownContainer {
|
||||
position : relative;
|
||||
.navDropdown {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
right: 0px;
|
||||
z-index: 10000;
|
||||
width: max-content;
|
||||
min-width:100%;
|
||||
max-height: calc(100vh - 28px);
|
||||
overflow: hidden auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
.navItem {
|
||||
position : relative;
|
||||
display : flex;
|
||||
justify-content : space-between;
|
||||
align-items : center;
|
||||
width : 100%;
|
||||
border : 1px solid #888888;
|
||||
border-bottom : 0;
|
||||
animation-name : glideDropDown;
|
||||
animation-duration : 0.4s;
|
||||
}
|
||||
}
|
||||
&.recent {
|
||||
position : relative;
|
||||
.navDropdown .navItem {
|
||||
#backgroundColorsHover;
|
||||
.animate(background-color);
|
||||
position : relative;
|
||||
box-sizing : border-box;
|
||||
display : block;
|
||||
max-width : 15em;
|
||||
max-height : ~'calc(100vh - 28px)';
|
||||
padding : 8px 5px 13px;
|
||||
overflow : hidden auto;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
background-color : #333333;
|
||||
border-top : 1px solid #888888;
|
||||
scrollbar-color : #666666 #333333;
|
||||
scrollbar-width : thin;
|
||||
.clear {
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
right : 0;
|
||||
display : none;
|
||||
width : 20px;
|
||||
height : 100%;
|
||||
background-color : #333333;
|
||||
border-radius : 3px;
|
||||
opacity : 70%;
|
||||
transform : translateY(-50%);
|
||||
&:hover { opacity : 100%; }
|
||||
i {
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
margin : 0;
|
||||
font-size : 10px;
|
||||
text-align : center;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color : @blue;
|
||||
.clear {
|
||||
display : grid;
|
||||
place-content : center;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
display : inline-block;
|
||||
width : 100%;
|
||||
overflow : hidden auto;
|
||||
text-overflow : ellipsis;
|
||||
white-space : nowrap;
|
||||
}
|
||||
.time {
|
||||
position : absolute;
|
||||
right : 2px;
|
||||
bottom : 2px;
|
||||
font-size : 0.7em;
|
||||
color : #888888;
|
||||
}
|
||||
&.header {
|
||||
box-sizing : border-box;
|
||||
display : block;
|
||||
padding : 5px 0;
|
||||
color : #BBBBBB;
|
||||
text-align : center;
|
||||
background-color : #333333;
|
||||
border-top : 1px solid #888888;
|
||||
&:nth-of-type(1) { background-color : darken(@teal, 20%); }
|
||||
&:nth-of-type(2) { background-color : darken(@purple, 30%); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this should likely be refactored into .navDropdownContainer
|
||||
.save-menu {
|
||||
.dropdown { z-index : 1000; }
|
||||
.navItem i.fa-power-off {
|
||||
color : red;
|
||||
&.active {
|
||||
color : rgb(0, 182, 52);
|
||||
filter : drop-shadow(0 0 2px rgba(0, 182, 52, 0.765));
|
||||
}
|
||||
}
|
||||
}
|
||||
@import 'naturalcrit/styles/colors.less';
|
||||
@navbarHeight : 28px;
|
||||
@keyframes pinkColoring {
|
||||
//from {color: white;}
|
||||
//to {color: red;}
|
||||
0% {color: pink;}
|
||||
50% {color: pink;}
|
||||
75% {color: red;}
|
||||
100% {color: pink;}
|
||||
}
|
||||
.homebrew nav{
|
||||
.homebrewLogo{
|
||||
.animate(color);
|
||||
font-family : CodeBold;
|
||||
font-size : 12px;
|
||||
color : white;
|
||||
div{
|
||||
margin-top : 2px;
|
||||
margin-bottom : -2px;
|
||||
}
|
||||
&:hover{
|
||||
color : @blue;
|
||||
}
|
||||
}
|
||||
.editTitle.navItem{
|
||||
padding : 2px 12px;
|
||||
input{
|
||||
width : 250px;
|
||||
margin : 0;
|
||||
padding : 2px;
|
||||
background-color : #444;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-size : 12px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-align : center;
|
||||
border : 1px solid @blue;
|
||||
outline : none;
|
||||
}
|
||||
.charCount{
|
||||
display : inline-block;
|
||||
vertical-align : bottom;
|
||||
margin-left : 8px;
|
||||
color : #666;
|
||||
text-align : right;
|
||||
&.max{
|
||||
color : @red;
|
||||
}
|
||||
}
|
||||
}
|
||||
.brewTitle.navItem{
|
||||
font-size : 12px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-align : center;
|
||||
text-transform : initial;
|
||||
}
|
||||
.save-menu {
|
||||
.dropdown {
|
||||
z-index: 1000;
|
||||
}
|
||||
.navItem i.fa-power-off {
|
||||
color : red;
|
||||
&.active {
|
||||
color : rgb(0, 182, 52);
|
||||
filter : drop-shadow(0 0 2px rgba(0, 182, 52, 0.765))
|
||||
}
|
||||
}
|
||||
}
|
||||
.patreon.navItem{
|
||||
border-left : 1px solid #666;
|
||||
border-right : 1px solid #666;
|
||||
&:hover i {
|
||||
color: red;
|
||||
}
|
||||
i{
|
||||
.animate(color);
|
||||
animation-name: pinkColoring;
|
||||
animation-duration: 2s;
|
||||
color: pink;
|
||||
}
|
||||
}
|
||||
.recent.navItem {
|
||||
position : relative;
|
||||
.dropdown{
|
||||
position : absolute;
|
||||
top : 28px;
|
||||
left : 0px;
|
||||
z-index : 10000;
|
||||
width : 100%;
|
||||
overflow : hidden auto;
|
||||
max-height : ~"calc(100vh - 28px)";
|
||||
scrollbar-color : #666 #333;
|
||||
scrollbar-width : thin;
|
||||
h4{
|
||||
display : block;
|
||||
box-sizing : border-box;
|
||||
padding : 5px 0px;
|
||||
background-color : #333;
|
||||
font-size : 0.8em;
|
||||
color : #bbb;
|
||||
text-align : center;
|
||||
border-top : 1px solid #888;
|
||||
&:nth-of-type(1){ background-color: darken(@teal, 20%); }
|
||||
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
|
||||
}
|
||||
.item{
|
||||
#backgroundColorsHover;
|
||||
.animate(background-color);
|
||||
position : relative;
|
||||
display : block;
|
||||
box-sizing : border-box;
|
||||
padding : 8px 5px 13px;
|
||||
background-color : #333;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
border-top : 1px solid #888;
|
||||
overflow : clip;
|
||||
.clear{
|
||||
display : none;
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
transform : translateY(-50%);
|
||||
right : 0px;
|
||||
width : 20px;
|
||||
height : 100%;
|
||||
background-color : #333;
|
||||
opacity : 70%;
|
||||
border-radius : 3px;
|
||||
&:hover {
|
||||
opacity : 100%;
|
||||
}
|
||||
i {
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
margin : 0;
|
||||
height :100%;
|
||||
width :100%;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
background-color : @blue;
|
||||
|
||||
.clear{
|
||||
display : grid;
|
||||
place-content : center;
|
||||
}
|
||||
}
|
||||
.title{
|
||||
display : inline-block;
|
||||
overflow : hidden;
|
||||
width : 100%;
|
||||
text-overflow : ellipsis;
|
||||
white-space : nowrap;
|
||||
}
|
||||
.time{
|
||||
position : absolute;
|
||||
right : 2px;
|
||||
bottom : 2px;
|
||||
font-size : 0.7em;
|
||||
color : #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.warning.navItem{
|
||||
position : relative;
|
||||
background-color : @orange;
|
||||
color : white;
|
||||
&:hover>.dropdown{
|
||||
visibility : visible;
|
||||
}
|
||||
.dropdown{
|
||||
position : absolute;
|
||||
display : block;
|
||||
top : 28px;
|
||||
left : 0px;
|
||||
visibility : hidden;
|
||||
z-index : 10000;
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
padding : 13px 5px;
|
||||
background-color : #333;
|
||||
text-align : center;
|
||||
}
|
||||
}
|
||||
.account.navItem{
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,11 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const { splitTextStyleAndMetadata } = require('../../../shared/helpers.js'); // Importing the function from helpers.js
|
||||
|
||||
const BREWKEY = 'homebrewery-new';
|
||||
const STYLEKEY = 'homebrewery-new-style';
|
||||
const METAKEY = 'homebrewery-new-meta';
|
||||
|
||||
const NewBrew = ()=>{
|
||||
const handleFileChange = (e)=>{
|
||||
const file = e.target.files[0];
|
||||
if(file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e)=>{
|
||||
const fileContent = e.target.result;
|
||||
const newBrew = {
|
||||
text : fileContent,
|
||||
style : ''
|
||||
};
|
||||
if(fileContent.startsWith('```metadata')) {
|
||||
splitTextStyleAndMetadata(newBrew); // Modify newBrew directly
|
||||
localStorage.setItem(BREWKEY, newBrew.text);
|
||||
localStorage.setItem(STYLEKEY, newBrew.style);
|
||||
localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])));
|
||||
window.location.href = '/new';
|
||||
} else {
|
||||
alert('This file is invalid, please, enter a valid file');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Nav.dropdown>
|
||||
<Nav.item
|
||||
className='new'
|
||||
color='purple'
|
||||
icon='fa-solid fa-plus-square'>
|
||||
new
|
||||
</Nav.item>
|
||||
<Nav.item
|
||||
className='fromBlank'
|
||||
href='/new'
|
||||
newTab={true}
|
||||
color='purple'
|
||||
icon='fa-solid fa-file'>
|
||||
from blank
|
||||
</Nav.item>
|
||||
|
||||
<Nav.item
|
||||
className='fromFile'
|
||||
color='purple'
|
||||
icon='fa-solid fa-upload'
|
||||
onClick={()=>{ document.getElementById('uploadTxt').click(); }}>
|
||||
<input id='uploadTxt' className='newFromLocal' type='file' onChange={handleFileChange} style={{ display: 'none' }} />
|
||||
from file
|
||||
</Nav.item>
|
||||
</Nav.dropdown>
|
||||
);
|
||||
module.exports = function(props){
|
||||
return <Nav.item
|
||||
href='/new'
|
||||
color='purple'
|
||||
icon='fas fa-plus-square'>
|
||||
new
|
||||
</Nav.item>;
|
||||
};
|
||||
|
||||
module.exports = NewBrew;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||
|
||||
module.exports = function(){
|
||||
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||
module.exports = function(props){
|
||||
return <Nav.item newTab={true} href={`/print/${props.shareId}?dialog=true`} color='purple' icon='far fa-file-pdf'>
|
||||
get PDF
|
||||
</Nav.item>;
|
||||
};
|
||||
|
||||
@@ -121,7 +121,6 @@ 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) || '[]');
|
||||
@@ -140,11 +139,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 className='navItem' href={brew.url} key={`${brew.id}-${i}`} target='_blank' rel='noopener noreferrer' title={brew.title || '[ no title ]'}>
|
||||
return <a href={brew.url} className='item' 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>
|
||||
@@ -152,25 +151,25 @@ const RecentItems = createClass({
|
||||
});
|
||||
};
|
||||
|
||||
return <>
|
||||
return <div className='dropdown'>
|
||||
{(this.props.showEdit && this.props.showView) ?
|
||||
<Nav.item className='header'>edited</Nav.item> : null }
|
||||
<h4>edited</h4> : null }
|
||||
{this.props.showEdit ?
|
||||
makeItems(this.state.edit) : null }
|
||||
{(this.props.showEdit && this.props.showView) ?
|
||||
<Nav.item className='header'>viewed</Nav.item> : null }
|
||||
<h4>viewed</h4> : null }
|
||||
{this.props.showView ?
|
||||
makeItems(this.state.view) : null }
|
||||
</>;
|
||||
</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.dropdown className='recent'>
|
||||
<Nav.item icon='fas fa-history' color='grey' >
|
||||
{this.props.text}
|
||||
</Nav.item>
|
||||
return <Nav.item icon='fas fa-history' color='grey' className='recent'
|
||||
onMouseEnter={()=>this.handleDropdown(true)}
|
||||
onMouseLeave={()=>this.handleDropdown(false)}>
|
||||
{this.props.text}
|
||||
{this.renderDropdown()}
|
||||
</Nav.dropdown>;
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const cx = require('classnames');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const MAX_URL_SIZE = 2083;
|
||||
const MAIN_URL = 'https://www.reddit.com/r/UnearthedArcana/submit?selftext=true';
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
const React = require('react');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function (props) {
|
||||
return (
|
||||
<Nav.item
|
||||
color='purple'
|
||||
icon='fas fa-dungeon'
|
||||
href='/vault'
|
||||
newTab={false}
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
Vault
|
||||
</Nav.item>
|
||||
);
|
||||
};
|
||||
@@ -1,82 +1,71 @@
|
||||
const React = require('react');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const moment = require('moment');
|
||||
|
||||
const UIPage = require('../basePages/uiPage/uiPage.jsx');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
|
||||
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||
|
||||
let SAVEKEY = '';
|
||||
const AccountPage = createClass({
|
||||
displayName : 'AccountPage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {},
|
||||
uiItems : {}
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
uiItems : this.props.uiItems
|
||||
};
|
||||
},
|
||||
|
||||
const AccountPage = (props)=>{
|
||||
// destructure props and set state for save location
|
||||
const { accountDetails, brew } = props;
|
||||
const [saveLocation, setSaveLocation] = React.useState('');
|
||||
renderNavItems : function() {
|
||||
return <Navbar>
|
||||
<Nav.section>
|
||||
<NewBrew />
|
||||
<HelpNavItem />
|
||||
<RecentNavItem />
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>;
|
||||
},
|
||||
|
||||
// initialize save location from local storage based on user id
|
||||
React.useEffect(()=>{
|
||||
if(!saveLocation && accountDetails.username) {
|
||||
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${accountDetails.username}`;
|
||||
// if no SAVEKEY in local storage, default save location to Google Drive if user has Google account.
|
||||
let saveLocation = window.localStorage.getItem(SAVEKEY);
|
||||
saveLocation = saveLocation ?? (accountDetails.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY');
|
||||
setActiveSaveLocation(saveLocation);
|
||||
}
|
||||
}, []);
|
||||
renderUiItems : function() {
|
||||
// console.log(this.props.uiItems);
|
||||
return <>
|
||||
<div className='dataGroup'>
|
||||
<h1>Account Information <i className='fas fa-user'></i></h1>
|
||||
<p><strong>Username: </strong> {this.props.uiItems.username || 'No user currently logged in'}</p>
|
||||
<p><strong>Last Login: </strong> {moment(this.props.uiItems.issued).format('dddd, MMMM Do YYYY, h:mm:ss a ZZ') || '-'}</p>
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Homebrewery Information <NaturalCritIcon /></h3>
|
||||
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount || '-'}</p>
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
|
||||
<p><strong>Linked to Google: </strong> {this.props.uiItems.googleId ? 'YES' : 'NO'}</p>
|
||||
{this.props.uiItems.googleId ? <p><strong>Brews on Google Drive: </strong> {this.props.uiItems.fileCount || '-'}</p> : '' }
|
||||
</div>
|
||||
</>;
|
||||
},
|
||||
|
||||
const setActiveSaveLocation = (newSelection)=>{
|
||||
if(saveLocation === newSelection) return;
|
||||
window.localStorage.setItem(SAVEKEY, newSelection);
|
||||
setSaveLocation(newSelection);
|
||||
};
|
||||
|
||||
// todo: should this be a set of radio buttons (well styled) since it's either/or choice?
|
||||
const renderSaveLocationButton = (name, key, shouldRender = true)=>{
|
||||
if(!shouldRender) return null;
|
||||
return (
|
||||
<button className={saveLocation === key ? 'active' : ''} onClick={()=>{setActiveSaveLocation(key);}}>
|
||||
{name}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// render the entirety of the account page content
|
||||
const renderAccountPage = ()=>{
|
||||
return (
|
||||
<>
|
||||
<div className='dataGroup'>
|
||||
<h1>Account Information <i className='fas fa-user'></i></h1>
|
||||
<p><strong>Username: </strong>{accountDetails.username || 'No user currently logged in'}</p>
|
||||
<p><strong>Last Login: </strong>{moment(accountDetails.issued).format('dddd, MMMM Do YYYY, h:mm:ss a ZZ') || '-'}</p>
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Homebrewery Information <NaturalCritIcon /></h3>
|
||||
<p><strong>Brews on Homebrewery: </strong>{accountDetails.mongoCount}</p>
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
|
||||
<p><strong>Linked to Google: </strong>{accountDetails.googleId ? 'YES' : 'NO'}</p>
|
||||
{accountDetails.googleId && (
|
||||
<p>
|
||||
<strong>Brews on Google Drive: </strong>{accountDetails.googleCount ?? (
|
||||
<>
|
||||
Unable to retrieve files - <a href='https://github.com/naturalcrit/homebrewery/discussions/1580'>follow these steps to renew your Google credentials.</a>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h4>Default Save Location</h4>
|
||||
{renderSaveLocationButton('Homebrewery', 'HOMEBREWERY')}
|
||||
{renderSaveLocationButton('Google Drive', 'GOOGLE-DRIVE', accountDetails.googleId)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// return the account page inside the base layout wrapper (with navbar etc).
|
||||
return (
|
||||
<UIPage brew={brew}>
|
||||
{renderAccountPage()}
|
||||
</UIPage>);
|
||||
};
|
||||
render : function(){
|
||||
return <UIPage brew={this.props.brew}>
|
||||
{this.renderUiItems()}
|
||||
</UIPage>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = AccountPage;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
require('./brewItem.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const moment = require('moment');
|
||||
const request = require('../../../../utils/request-middleware.js');
|
||||
const request = require('superagent');
|
||||
|
||||
const googleDriveIcon = require('../../../../googleDrive.svg');
|
||||
const homebreweryIcon = require('../../../../thumbnail.png');
|
||||
const googleDriveIcon = require('../../../../googleDrive.png');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
const BrewItem = createClass({
|
||||
@@ -17,10 +18,7 @@ const BrewItem = createClass({
|
||||
description : '',
|
||||
authors : [],
|
||||
stubbed : true
|
||||
},
|
||||
updateListFilter : ()=>{},
|
||||
reportError : ()=>{},
|
||||
renderStorage : true
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -35,19 +33,11 @@ const BrewItem = createClass({
|
||||
|
||||
request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`)
|
||||
.send()
|
||||
.end((err, res)=>{
|
||||
if(err) {
|
||||
this.props.reportError(err);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
.end(function(err, res){
|
||||
location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
updateFilter : function(type, term){
|
||||
this.props.updateListFilter(type, term);
|
||||
},
|
||||
|
||||
renderDeleteBrewLink : function(){
|
||||
if(!this.props.brew.editId) return;
|
||||
|
||||
@@ -95,18 +85,11 @@ const BrewItem = createClass({
|
||||
</a>;
|
||||
},
|
||||
|
||||
renderStorageIcon : function(){
|
||||
if(!this.props.renderStorage) return;
|
||||
if(this.props.brew.googleId) {
|
||||
return <span title={this.props.brew.webViewLink ? 'Your Google Drive Storage': 'Another User\'s Google Drive Storage'}>
|
||||
<a href={this.props.brew.webViewLink} target='_blank'>
|
||||
<img className='googleDriveIcon' src={googleDriveIcon} alt='googleDriveIcon' />
|
||||
</a>
|
||||
</span>;
|
||||
}
|
||||
renderGoogleDriveIcon : function(){
|
||||
if(!this.props.brew.googleId) return;
|
||||
|
||||
return <span title='Homebrewery Storage'>
|
||||
<img className='homebreweryIcon' src={homebreweryIcon} alt='homebreweryIcon' />
|
||||
return <span>
|
||||
<img className='googleDriveIcon' src={googleDriveIcon} alt='googleDriveIcon' />
|
||||
</span>;
|
||||
},
|
||||
|
||||
@@ -114,9 +97,6 @@ const BrewItem = createClass({
|
||||
const brew = this.props.brew;
|
||||
if(Array.isArray(brew.tags)) { // temporary fix until dud tags are cleaned
|
||||
brew.tags = brew.tags?.filter((tag)=>tag); //remove tags that are empty strings
|
||||
brew.tags.sort((a, b)=>{
|
||||
return a.indexOf(':') - b.indexOf(':') != 0 ? a.indexOf(':') - b.indexOf(':') : a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
});
|
||||
}
|
||||
const dateFormatString = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
@@ -133,25 +113,17 @@ const BrewItem = createClass({
|
||||
<div className='info'>
|
||||
|
||||
{brew.tags?.length ? <>
|
||||
<div className='brewTags' title={`${brew.tags.length} tags:\n${brew.tags.join('\n')}`}>
|
||||
<div className='brewTags' title={`Tags:\n${brew.tags.join('\n')}`}>
|
||||
<i className='fas fa-tags'/>
|
||||
{brew.tags.map((tag, idx)=>{
|
||||
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
|
||||
return <span key={idx} className={matches[1]} onClick={()=>{this.updateFilter(tag);}}>{matches[2]}</span>;
|
||||
return <span key={idx} className={matches[1]}>{matches[2]}</span>;
|
||||
})}
|
||||
</div>
|
||||
</> : <></>
|
||||
}
|
||||
<span title={`Authors:\n${brew.authors?.join('\n')}`}>
|
||||
<i className='fas fa-user'/> {brew.authors?.map((author, index)=>(
|
||||
<React.Fragment key={index}>
|
||||
{author === 'hidden'
|
||||
? <span title="Username contained an email address; hidden to protect user's privacy">{author}</span>
|
||||
: <a href={`/user/${author}`}>{author}</a>
|
||||
}
|
||||
{index < brew.authors.length - 1 && ', '}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<i className='fas fa-user'/> {brew.authors?.join(', ')}
|
||||
</span>
|
||||
<br />
|
||||
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
||||
@@ -167,7 +139,7 @@ const BrewItem = createClass({
|
||||
Last updated: ${moment(brew.updatedAt).local().format(dateFormatString)}`}>
|
||||
<i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()}
|
||||
</span>
|
||||
{this.renderStorageIcon()}
|
||||
{this.renderGoogleDriveIcon()}
|
||||
</div>
|
||||
|
||||
<div className='links'>
|
||||
|
||||
@@ -48,10 +48,6 @@
|
||||
&>span{
|
||||
margin-right : 12px;
|
||||
line-height : 1.5em;
|
||||
|
||||
a {
|
||||
color:inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
.brewTags span {
|
||||
@@ -63,41 +59,6 @@
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
border-color: currentColor;
|
||||
cursor : pointer;
|
||||
&:before {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-size: 12px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
&.type {
|
||||
background-color: #0080003b;
|
||||
color: #008000;
|
||||
&:before{
|
||||
content: '\f0ad';
|
||||
}
|
||||
}
|
||||
&.group {
|
||||
background-color: #5050503b;
|
||||
color: #000000;
|
||||
&:before{
|
||||
content: '\f500';
|
||||
}
|
||||
}
|
||||
&.meta {
|
||||
background-color: #0000803b;
|
||||
color: #000080;
|
||||
&:before{
|
||||
content: '\f05a';
|
||||
}
|
||||
}
|
||||
&.system {
|
||||
background-color: #8000003b;
|
||||
color: #800000;
|
||||
&:before{
|
||||
content: '\f518';
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
.links{
|
||||
@@ -119,12 +80,11 @@
|
||||
text-align : center;
|
||||
a{
|
||||
.animate(opacity);
|
||||
display : block;
|
||||
margin : 8px 0px;
|
||||
opacity : 0.6;
|
||||
font-size : 1.3em;
|
||||
color : white;
|
||||
text-decoration : unset;
|
||||
display : block;
|
||||
margin : 8px 0px;
|
||||
opacity : 0.6;
|
||||
font-size : 1.3em;
|
||||
color : white;
|
||||
&:hover{
|
||||
opacity : 1;
|
||||
}
|
||||
@@ -134,15 +94,8 @@
|
||||
}
|
||||
}
|
||||
.googleDriveIcon {
|
||||
height : 18px;
|
||||
height : 20px;
|
||||
padding : 0px;
|
||||
margin : -5px;
|
||||
}
|
||||
.homebreweryIcon {
|
||||
mix-blend-mode : darken;
|
||||
height : 24px;
|
||||
position : relative;
|
||||
top : 5px;
|
||||
left : -5px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ const ListPage = createClass({
|
||||
brews : []
|
||||
}
|
||||
],
|
||||
navItems : <></>,
|
||||
reportError : null
|
||||
navItems : <></>
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
@@ -36,7 +35,6 @@ const ListPage = createClass({
|
||||
|
||||
return {
|
||||
filterString : this.props.query?.filter || '',
|
||||
filterTags : [],
|
||||
sortType : this.props.query?.sort || null,
|
||||
sortDir : this.props.query?.dir || null,
|
||||
query : this.props.query,
|
||||
@@ -83,14 +81,14 @@ const ListPage = createClass({
|
||||
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
|
||||
|
||||
return _.map(brews, (brew, idx)=>{
|
||||
return <BrewItem brew={brew} key={idx} reportError={this.props.reportError} updateListFilter={ (tag)=>{ this.updateUrl(this.state.filterString, this.state.sortType, this.state.sortDir, tag); }}/>;
|
||||
return <BrewItem brew={brew} key={idx}/>;
|
||||
});
|
||||
},
|
||||
|
||||
sortBrewOrder : function(brew){
|
||||
if(!brew.title){brew.title = 'No Title';}
|
||||
const mapping = {
|
||||
'alpha' : _.deburr(brew.title.trim().toLowerCase()),
|
||||
'alpha' : _.deburr(brew.title.toLowerCase()),
|
||||
'created' : moment(brew.createdAt).format(),
|
||||
'updated' : moment(brew.updatedAt).format(),
|
||||
'views' : brew.views,
|
||||
@@ -137,33 +135,13 @@ const ListPage = createClass({
|
||||
return;
|
||||
},
|
||||
|
||||
updateUrl : function(filterTerm, sortType, sortDir, filterTag=''){
|
||||
updateUrl : function(filterTerm, sortType, sortDir){
|
||||
const url = new URL(window.location.href);
|
||||
const urlParams = new URLSearchParams(url.search);
|
||||
|
||||
urlParams.set('sort', sortType);
|
||||
urlParams.set('dir', sortDir);
|
||||
|
||||
let filterTags = urlParams.getAll('tag');
|
||||
if(filterTag != '') {
|
||||
if(filterTags.findIndex((tag)=>{return tag.toLowerCase()==filterTag.toLowerCase();}) == -1){
|
||||
filterTags.push(filterTag);
|
||||
} else {
|
||||
filterTags = filterTags.filter((tag)=>{ return tag.toLowerCase() != filterTag.toLowerCase(); });
|
||||
}
|
||||
}
|
||||
urlParams.delete('tag');
|
||||
// Add tags to URL in the order they were clicked
|
||||
filterTags.forEach((tag)=>{ urlParams.append('tag', tag); });
|
||||
// Sort tags before updating state
|
||||
filterTags.sort((a, b)=>{
|
||||
return a.indexOf(':') - b.indexOf(':') != 0 ? a.indexOf(':') - b.indexOf(':') : a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
});
|
||||
|
||||
this.setState({
|
||||
filterTags
|
||||
});
|
||||
|
||||
if(!filterTerm)
|
||||
urlParams.delete('filter');
|
||||
else
|
||||
@@ -187,16 +165,6 @@ const ListPage = createClass({
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderTagsOptions : function(){
|
||||
if(this.state.filterTags?.length == 0) return;
|
||||
return <div className='tags-container'>
|
||||
{_.map(this.state.filterTags, (tag, idx)=>{
|
||||
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
|
||||
return <span key={idx} className={matches[1]} onClick={()=>{ this.updateUrl(this.state.filterString, this.state.sortType, this.state.sortDir, tag); }}>{matches[2]}</span>;
|
||||
})}
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderSortOptions : function(){
|
||||
return <div className='sort-container'>
|
||||
<h6>Sort by :</h6>
|
||||
@@ -207,6 +175,9 @@ const ListPage = createClass({
|
||||
{/* {this.renderSortOption('Latest', 'latest')} */}
|
||||
|
||||
{this.renderFilterOption()}
|
||||
|
||||
|
||||
|
||||
</div>;
|
||||
},
|
||||
|
||||
@@ -214,28 +185,14 @@ const ListPage = createClass({
|
||||
const testString = _.deburr(this.state.filterString).toLowerCase();
|
||||
|
||||
brews = _.filter(brews, (brew)=>{
|
||||
// Filter by user entered text
|
||||
const brewStrings = _.deburr([
|
||||
brew.title,
|
||||
brew.description,
|
||||
brew.tags].join('\n')
|
||||
.toLowerCase());
|
||||
|
||||
const filterTextTest = brewStrings.includes(testString);
|
||||
|
||||
// Filter by user selected tags
|
||||
let filterTagTest = true;
|
||||
if(this.state.filterTags.length > 0){
|
||||
filterTagTest = Array.isArray(brew.tags) && this.state.filterTags?.every((tag)=>{
|
||||
return brew.tags.findIndex((brewTag)=>{
|
||||
return brewTag.toLowerCase() == tag.toLowerCase();
|
||||
}) >= 0;
|
||||
});
|
||||
}
|
||||
|
||||
return filterTextTest && filterTagTest;
|
||||
return brewStrings.includes(testString);
|
||||
});
|
||||
|
||||
return _.orderBy(brews, (brew)=>{ return this.sortBrewOrder(brew); }, this.state.sortDir);
|
||||
},
|
||||
|
||||
@@ -261,15 +218,12 @@ const ListPage = createClass({
|
||||
|
||||
render : function(){
|
||||
return <div className='listPage sitePage'>
|
||||
{/*<style>@layer V3_5ePHB, bundle;</style>*/}
|
||||
<link href='/themes/V3/Blank/style.css' type='text/css' rel='stylesheet'/>
|
||||
<link href='/themes/V3/5ePHB/style.css' type='text/css' rel='stylesheet'/>
|
||||
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/>
|
||||
{this.props.navItems}
|
||||
{this.renderSortOptions()}
|
||||
{this.renderTagsOptions()}
|
||||
|
||||
<div className='content V3'>
|
||||
<div className='page'>
|
||||
<div className='phb page'>
|
||||
{this.renderBrewCollection(this.state.brewCollection)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,24 +2,22 @@
|
||||
.noColumns(){
|
||||
column-count : auto;
|
||||
column-fill : auto;
|
||||
column-gap : normal;
|
||||
column-gap : auto;
|
||||
column-width : auto;
|
||||
-webkit-column-count : auto;
|
||||
-moz-column-count : auto;
|
||||
-webkit-column-width : auto;
|
||||
-moz-column-width : auto;
|
||||
-webkit-column-gap : normal;
|
||||
-moz-column-gap : normal;
|
||||
height : auto;
|
||||
min-height : 279.4mm;
|
||||
margin : 20px auto;
|
||||
contain : unset;
|
||||
-webkit-column-gap : auto;
|
||||
-moz-column-gap : auto;
|
||||
}
|
||||
.listPage{
|
||||
.content{
|
||||
z-index : 1;
|
||||
.page{
|
||||
.noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer
|
||||
.phb{
|
||||
.noColumns();
|
||||
height : auto;
|
||||
min-height : 279.4mm;
|
||||
margin : 20px auto;
|
||||
&::after{
|
||||
display : none;
|
||||
}
|
||||
@@ -53,7 +51,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.sort-container {
|
||||
.sort-container{
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
position : sticky;
|
||||
top : 0;
|
||||
@@ -65,7 +63,7 @@
|
||||
border-bottom : 1px solid #666;
|
||||
color : white;
|
||||
text-align : center;
|
||||
z-index : 1;
|
||||
z-index : 500;
|
||||
display : flex;
|
||||
justify-content : center;
|
||||
align-items : baseline;
|
||||
@@ -125,66 +123,4 @@
|
||||
|
||||
|
||||
}
|
||||
.tags-container {
|
||||
height : 30px;
|
||||
background-color : #555;
|
||||
border-top : 1px solid #666;
|
||||
border-bottom : 1px solid #666;
|
||||
color : white;
|
||||
display : flex;
|
||||
justify-content : center;
|
||||
align-items : center;
|
||||
column-gap : 15px;
|
||||
row-gap : 5px;
|
||||
flex-wrap : wrap;
|
||||
span {
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-size : 11px;
|
||||
font-weight : bold;
|
||||
border : 1px solid;
|
||||
border-radius : 3px;
|
||||
padding : 3px;
|
||||
cursor : pointer;
|
||||
color: #dfdfdf;
|
||||
&:before {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-size: 12px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
&:after {
|
||||
content: '\f00d';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-size: 12px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
&.type {
|
||||
background-color: #008000;
|
||||
border-color: #00a000;
|
||||
&:before{
|
||||
content: '\f0ad';
|
||||
}
|
||||
}
|
||||
&.group {
|
||||
background-color: #505050;
|
||||
border-color: #000000;
|
||||
&:before{
|
||||
content: '\f500';
|
||||
}
|
||||
}
|
||||
&.meta {
|
||||
background-color: #000080;
|
||||
border-color: #0000a0;
|
||||
&:before{
|
||||
content: '\f05a';
|
||||
}
|
||||
}
|
||||
&.system {
|
||||
background-color: #800000;
|
||||
border-color: #a00000;
|
||||
&:before{
|
||||
content: '\f518';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,47 @@
|
||||
.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;
|
||||
button {
|
||||
background-color : transparent;
|
||||
border : 1px solid black;
|
||||
border-radius : 5px;
|
||||
width : 125px;
|
||||
color : black;
|
||||
margin-right : 5px;
|
||||
&.active {
|
||||
background-color: #0007;
|
||||
color: white;
|
||||
&:before {
|
||||
content: '\f00c';
|
||||
font-family: 'FONT AWESOME 5 FREE';
|
||||
margin-right: 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; }
|
||||
.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;
|
||||
}
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ require('./editPage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const request = require('superagent');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
@@ -11,8 +11,7 @@ const Navbar = require('../../navbar/navbar.jsx');
|
||||
|
||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
const PrintNavItem = require('../../navbar/print.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
|
||||
@@ -20,14 +19,10 @@ const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const LockNotification = require('./lockNotification/lockNotification.jsx');
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
|
||||
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
|
||||
|
||||
const googleDriveIcon = require('../../googleDrive.svg');
|
||||
const googleDriveActive = require('../../googleDrive.png');
|
||||
const googleDriveInactive = require('../../googleDriveMono.png');
|
||||
|
||||
const SAVE_TIMEOUT = 3000;
|
||||
|
||||
@@ -35,7 +30,24 @@ const EditPage = createClass({
|
||||
displayName : 'EditPage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : DEFAULT_BREW_LOAD
|
||||
brew : {
|
||||
text : '',
|
||||
style : '',
|
||||
shareId : null,
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
gDrive : false,
|
||||
trashed : false,
|
||||
|
||||
title : '',
|
||||
description : '',
|
||||
tags : '',
|
||||
published : false,
|
||||
authors : [],
|
||||
systems : [],
|
||||
renderer : 'legacy'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -48,19 +60,14 @@ const EditPage = createClass({
|
||||
alertLoginToTransfer : false,
|
||||
saveGoogle : this.props.brew.googleId ? true : false,
|
||||
confirmGoogleTransfer : false,
|
||||
error : null,
|
||||
errors : null,
|
||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
||||
url : '',
|
||||
autoSave : true,
|
||||
autoSaveWarning : false,
|
||||
unsavedTime : new Date(),
|
||||
currentEditorPage : 0,
|
||||
displayLockMessage : this.props.brew.lock || false,
|
||||
themeBundle : {}
|
||||
unsavedTime : new Date()
|
||||
};
|
||||
},
|
||||
|
||||
editor : React.createRef(null),
|
||||
savedBrew : null,
|
||||
|
||||
componentDidMount : function(){
|
||||
@@ -68,6 +75,7 @@ const EditPage = createClass({
|
||||
url : window.location.href
|
||||
});
|
||||
|
||||
|
||||
this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy
|
||||
|
||||
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
|
||||
@@ -88,8 +96,6 @@ const EditPage = createClass({
|
||||
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||
}));
|
||||
|
||||
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
|
||||
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
@@ -101,8 +107,8 @@ const EditPage = createClass({
|
||||
if(!(e.ctrlKey || e.metaKey)) return;
|
||||
const S_KEY = 83;
|
||||
const P_KEY = 80;
|
||||
if(e.keyCode == S_KEY) this.trySave(true);
|
||||
if(e.keyCode == P_KEY) printCurrentBrew();
|
||||
if(e.keyCode == S_KEY) this.save();
|
||||
if(e.keyCode == P_KEY) window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus();
|
||||
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@@ -110,7 +116,7 @@ const EditPage = createClass({
|
||||
},
|
||||
|
||||
handleSplitMove : function(){
|
||||
this.editor.current.update();
|
||||
this.refs.editor.update();
|
||||
},
|
||||
|
||||
handleTextChange : function(text){
|
||||
@@ -119,10 +125,9 @@ const EditPage = createClass({
|
||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, text: text },
|
||||
isPending : true,
|
||||
htmlErrors : htmlErrors,
|
||||
currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
|
||||
brew : { ...prevState.brew, text: text },
|
||||
isPending : true,
|
||||
htmlErrors : htmlErrors
|
||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||
},
|
||||
|
||||
@@ -133,10 +138,7 @@ const EditPage = createClass({
|
||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||
},
|
||||
|
||||
handleMetaChange : function(metadata, field=undefined){
|
||||
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
|
||||
fetchThemeBundle(this, metadata.renderer, metadata.theme);
|
||||
|
||||
handleMetaChange : function(metadata){
|
||||
this.setState((prevState)=>({
|
||||
brew : {
|
||||
...prevState.brew,
|
||||
@@ -144,20 +146,20 @@ const EditPage = createClass({
|
||||
},
|
||||
isPending : true,
|
||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||
|
||||
},
|
||||
|
||||
hasChanges : function(){
|
||||
return !_.isEqual(this.state.brew, this.savedBrew);
|
||||
},
|
||||
|
||||
trySave : function(immediate=false){
|
||||
trySave : function(){
|
||||
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
||||
if(this.hasChanges()){
|
||||
this.debounceSave();
|
||||
} else {
|
||||
this.debounceSave.cancel();
|
||||
}
|
||||
if(immediate) this.debounceSave.flush();
|
||||
},
|
||||
|
||||
handleGoogleClick : function(){
|
||||
@@ -170,10 +172,7 @@ const EditPage = createClass({
|
||||
this.setState((prevState)=>({
|
||||
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
|
||||
}));
|
||||
this.setState({
|
||||
error : null,
|
||||
isSaving : false
|
||||
});
|
||||
this.clearErrors();
|
||||
},
|
||||
|
||||
closeAlerts : function(event){
|
||||
@@ -189,16 +188,24 @@ const EditPage = createClass({
|
||||
this.setState((prevState)=>({
|
||||
saveGoogle : !prevState.saveGoogle,
|
||||
isSaving : false,
|
||||
error : null
|
||||
errors : null
|
||||
}), ()=>this.save());
|
||||
},
|
||||
|
||||
clearErrors : function(){
|
||||
this.setState({
|
||||
errors : null,
|
||||
isSaving : false
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
save : async function(){
|
||||
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
|
||||
|
||||
this.setState((prevState)=>({
|
||||
isSaving : true,
|
||||
error : null,
|
||||
errors : null,
|
||||
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||
}));
|
||||
|
||||
@@ -213,9 +220,8 @@ const EditPage = createClass({
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
console.log('Error Updating Local Brew');
|
||||
this.setState({ error: err });
|
||||
this.setState({ errors: err });
|
||||
});
|
||||
if(!res) return;
|
||||
|
||||
this.savedBrew = res.body;
|
||||
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
|
||||
@@ -224,8 +230,7 @@ const EditPage = createClass({
|
||||
brew : { ...prevState.brew,
|
||||
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
|
||||
editId : this.savedBrew.editId,
|
||||
shareId : this.savedBrew.shareId,
|
||||
version : this.savedBrew.version
|
||||
shareId : this.savedBrew.shareId
|
||||
},
|
||||
isPending : false,
|
||||
isSaving : false,
|
||||
@@ -235,7 +240,10 @@ const EditPage = createClass({
|
||||
|
||||
renderGoogleDriveIcon : function(){
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
|
||||
<img src={googleDriveIcon} className={this.state.saveGoogle ? '' : 'inactive'} alt='Google Drive icon'/>
|
||||
{this.state.saveGoogle
|
||||
? <img src={googleDriveActive} alt='googleDriveActive'/>
|
||||
: <img src={googleDriveInactive} alt='googleDriveInactive'/>
|
||||
}
|
||||
|
||||
{this.state.confirmGoogleTransfer &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
@@ -268,19 +276,71 @@ const EditPage = createClass({
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{this.state.alertTrashedGoogleBrew &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
This brew is currently in your Trash folder on Google Drive!<br />If you want to keep it, make sure to move it before it is deleted permanently!<br />
|
||||
<div className='confirm'>
|
||||
OK
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Nav.item>;
|
||||
},
|
||||
|
||||
renderSaveButton : function(){
|
||||
if(this.state.errors){
|
||||
let errMsg = '';
|
||||
try {
|
||||
errMsg += `${this.state.errors.toString()}\n\n`;
|
||||
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
|
||||
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
|
||||
console.log(errMsg);
|
||||
} catch (e){}
|
||||
|
||||
// if(this.state.errors.status == '401'){
|
||||
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
// Oops!
|
||||
// <div className='errorContainer' onClick={this.clearErrors}>
|
||||
// You must be signed in to a Google account
|
||||
// to save this to<br />Google Drive!<br />
|
||||
// <a target='_blank' rel='noopener noreferrer'
|
||||
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
// <div className='confirm'>
|
||||
// Sign In
|
||||
// </div>
|
||||
// </a>
|
||||
// <div className='deny'>
|
||||
// Not Now
|
||||
// </div>
|
||||
// </div>
|
||||
// </Nav.item>;
|
||||
// }
|
||||
|
||||
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={this.clearErrors}>
|
||||
Looks like your Google credentials have
|
||||
expired! Visit our log in page to sign out
|
||||
and sign back in with Google,
|
||||
then try saving again!
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(this.state.autoSaveWarning && this.hasChanges()){
|
||||
this.setAutosaveWarning();
|
||||
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
|
||||
@@ -324,12 +384,6 @@ const EditPage = createClass({
|
||||
this.warningTimer;
|
||||
},
|
||||
|
||||
errorReported : function(error) {
|
||||
this.setState({
|
||||
error
|
||||
});
|
||||
},
|
||||
|
||||
renderAutoSaveButton : function(){
|
||||
return <Nav.item onClick={this.handleAutoSave}>
|
||||
Autosave <i className={this.state.autoSave ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
|
||||
@@ -358,19 +412,26 @@ const EditPage = createClass({
|
||||
const shareLink = this.processShareId();
|
||||
|
||||
return <Navbar>
|
||||
|
||||
{this.state.alertTrashedGoogleBrew &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
This brew is currently in your Trash folder on Google Drive!<br />If you want to keep it, make sure to move it before it is deleted permanently!<br />
|
||||
<div className='confirm'>
|
||||
OK
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{this.renderGoogleDriveIcon()}
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
<Nav.dropdown className='save-menu'>
|
||||
{this.renderSaveButton()}
|
||||
{this.renderAutoSaveButton()}
|
||||
</Nav.dropdown>
|
||||
}
|
||||
<Nav.dropdown className='save-menu'>
|
||||
{this.renderSaveButton()}
|
||||
{this.renderAutoSaveButton()}
|
||||
</Nav.dropdown>
|
||||
<NewBrew />
|
||||
<HelpNavItem/>
|
||||
<Nav.dropdown>
|
||||
@@ -387,7 +448,7 @@ const EditPage = createClass({
|
||||
post to reddit
|
||||
</Nav.item>
|
||||
</Nav.dropdown>
|
||||
<PrintNavItem />
|
||||
<PrintLink shareId={this.processShareId()} />
|
||||
<RecentNavItem brew={this.state.brew} storageKey='edit' />
|
||||
<Account />
|
||||
</Nav.section>
|
||||
@@ -401,30 +462,16 @@ const EditPage = createClass({
|
||||
{this.renderNavbar()}
|
||||
|
||||
<div className='content'>
|
||||
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
|
||||
<SplitPane onDragFinish={this.handleSplitMove}>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<Editor
|
||||
ref={this.editor}
|
||||
ref='editor'
|
||||
brew={this.state.brew}
|
||||
onTextChange={this.handleTextChange}
|
||||
onStyleChange={this.handleStyleChange}
|
||||
onMetaChange={this.handleMetaChange}
|
||||
reportError={this.errorReported}
|
||||
renderer={this.state.brew.renderer}
|
||||
userThemes={this.props.userThemes}
|
||||
snippetBundle={this.state.themeBundle.snippets}
|
||||
/>
|
||||
<BrewRenderer
|
||||
text={this.state.brew.text}
|
||||
style={this.state.brew.style}
|
||||
renderer={this.state.brew.renderer}
|
||||
theme={this.state.brew.theme}
|
||||
themeBundle={this.state.themeBundle}
|
||||
errors={this.state.htmlErrors}
|
||||
lang={this.state.brew.lang}
|
||||
currentEditorPage={this.state.currentEditorPage}
|
||||
allowPrint={true}
|
||||
/>
|
||||
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} errors={this.state.htmlErrors} />
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
@@ -13,17 +13,87 @@
|
||||
cursor : initial;
|
||||
color : #666;
|
||||
}
|
||||
&.error{
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
}
|
||||
.googleDriveStorage {
|
||||
position : relative;
|
||||
}
|
||||
.googleDriveStorage img{
|
||||
height : 18px;
|
||||
height : 20px;
|
||||
padding : 0px;
|
||||
margin : -5px;
|
||||
|
||||
&.inactive {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 500;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
require('./lockNotification.less');
|
||||
const React = require('react');
|
||||
import Dialog from '../../../../components/dialog.jsx';
|
||||
|
||||
function LockNotification(props) {
|
||||
props = {
|
||||
shareId : 0,
|
||||
disableLock : ()=>{},
|
||||
message : '',
|
||||
...props
|
||||
};
|
||||
|
||||
const removeLock = ()=>{
|
||||
alert(`Not yet implemented - ID ${props.shareId}`);
|
||||
};
|
||||
|
||||
return <Dialog className='lockNotification' blocking closeText='CONTINUE TO EDITOR' >
|
||||
<h1>BREW LOCKED</h1>
|
||||
<p>This brew been locked by the Administrators. It will not be accessible by any method other than the Editor until the lock is removed.</p>
|
||||
<hr />
|
||||
<h3>LOCK REASON</h3>
|
||||
<p>{props.message || 'Unable to retrieve Lock Message'}</p>
|
||||
<hr />
|
||||
<p>Once you have resolved this issue, click REQUEST LOCK REMOVAL to notify the Administrators for review.</p>
|
||||
<p>Click CONTINUE TO EDITOR to temporarily hide this notification; it will reappear the next time the page is reloaded.</p>
|
||||
<button onClick={removeLock}>REQUEST LOCK REMOVAL</button>
|
||||
</Dialog>;
|
||||
};
|
||||
|
||||
module.exports = LockNotification;
|
||||
@@ -1,27 +0,0 @@
|
||||
.lockNotification {
|
||||
z-index : 1;
|
||||
width : 80%;
|
||||
padding : 10px;
|
||||
margin : 5% 10%;
|
||||
line-height : 1.5em;
|
||||
color : black;
|
||||
text-align : center;
|
||||
background-color : #CCCCCC;
|
||||
|
||||
&::backdrop { background-color : #000000AA; }
|
||||
|
||||
button {
|
||||
margin : 10px;
|
||||
color : white;
|
||||
background-color : #333333;
|
||||
|
||||
&:hover { background-color : #777777; }
|
||||
}
|
||||
|
||||
h1, h3 {
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-weight : 800;
|
||||
}
|
||||
h1 { font-size : 24px; }
|
||||
h3 { font-size : 18px; }
|
||||
}
|
||||
@@ -1,25 +1,48 @@
|
||||
require('./errorPage.less');
|
||||
const React = require('react');
|
||||
const UIPage = require('../basePages/uiPage/uiPage.jsx');
|
||||
const Markdown = require('../../../../shared/naturalcrit/markdown.js');
|
||||
const ErrorIndex = require('./errors/errorIndex.js');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const ErrorPage = ({ brew })=>{
|
||||
// Retrieving the error text based on the brew's error code from ErrorIndex
|
||||
const errorText = ErrorIndex({ brew })[brew.HBErrorCode.toString()] || '';
|
||||
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');
|
||||
|
||||
return (
|
||||
<UIPage brew={{ title: 'Crit Fail!' }}>
|
||||
<div className='dataGroup'>
|
||||
<div className='errorTitle'>
|
||||
<h1>{`Error ${brew?.status || '000'}`}</h1>
|
||||
<h4>{brew?.text || 'No error text'}</h4>
|
||||
</div>
|
||||
<hr />
|
||||
<div dangerouslySetInnerHTML={{ __html: Markdown.render(errorText) }} />
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const ErrorPage = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
ver : '0.0.0',
|
||||
errorId : ''
|
||||
};
|
||||
},
|
||||
|
||||
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>
|
||||
|
||||
<Nav.section>
|
||||
<PatreonNavItem />
|
||||
<HelpNavItem />
|
||||
<RecentNavItem />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
|
||||
<div className='content'>
|
||||
<BrewRenderer text={this.text} />
|
||||
</div>
|
||||
</UIPage>
|
||||
);
|
||||
};
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorPage;
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
.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; }
|
||||
}
|
||||
.errorPage{
|
||||
.errorTitle{
|
||||
background-color: @orange;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
const loginUrl = 'https://www.naturalcrit.com/login';
|
||||
|
||||
//001-050 : Brew errors
|
||||
//050-100 : Other pages errors
|
||||
|
||||
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}](/user/${author})`;}).join(', ') || 'Unable to list authors'}
|
||||
|
||||
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
|
||||
|
||||
// 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}](/user/${author})`;}).join(', ') || 'Unable to list authors'}
|
||||
|
||||
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
|
||||
|
||||
|
||||
// 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}`,
|
||||
|
||||
// Theme load error
|
||||
'09' : dedent`
|
||||
## No Homebrewery theme 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}`,
|
||||
|
||||
//account page when account is not defined
|
||||
'50' : dedent`
|
||||
## You are not signed in
|
||||
|
||||
You are trying to access the account page, but are not signed in to an account.
|
||||
|
||||
Please login or signup at our [login page](https://www.naturalcrit.com/login?redirect=https://homebrewery.naturalcrit.com/account).`,
|
||||
|
||||
// Brew locked by Administrators error
|
||||
'51' : dedent`
|
||||
## This brew has been locked.
|
||||
|
||||
Only an author may request that this lock is removed.
|
||||
|
||||
:
|
||||
|
||||
**Brew ID:** ${props.brew.brewId}
|
||||
|
||||
**Brew Title:** ${props.brew.brewTitle}`,
|
||||
|
||||
'90' : dedent` An unexpected error occurred while looking for these brews.
|
||||
Try again in a few minutes.`,
|
||||
|
||||
'91' : dedent` An unexpected error occurred while trying to get the total of brews.`,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = errorIndex;
|
||||
12
client/homebrew/pages/hijackPrint.js
Normal file
@@ -0,0 +1,12 @@
|
||||
//TODO: Depricate
|
||||
|
||||
module.exports = function(shareId){
|
||||
return function(event){
|
||||
event = event || window.event;
|
||||
if((event.ctrlKey || event.metaKey) && event.keyCode == 80){
|
||||
const win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
|
||||
win.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -3,80 +3,61 @@ const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const request = require('superagent');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
const { fetchThemeBundle } = require('../../../../shared/helpers.js');
|
||||
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
|
||||
|
||||
|
||||
const HomePage = createClass({
|
||||
displayName : 'HomePage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : DEFAULT_BREW,
|
||||
ver : '0.0.0'
|
||||
brew : {
|
||||
text : '',
|
||||
},
|
||||
ver : '0.0.0'
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
brew : this.props.brew,
|
||||
welcomeText : this.props.brew.text,
|
||||
error : undefined,
|
||||
currentEditorPage : 0,
|
||||
themeBundle : {}
|
||||
brew : this.props.brew,
|
||||
welcomeText : this.props.brew.text
|
||||
};
|
||||
},
|
||||
|
||||
editor : React.createRef(null),
|
||||
|
||||
componentDidMount : function() {
|
||||
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
|
||||
},
|
||||
|
||||
handleSave : function(){
|
||||
request.post('/api')
|
||||
.send(this.state.brew)
|
||||
.end((err, res)=>{
|
||||
if(err) {
|
||||
this.setState({ error: err });
|
||||
return;
|
||||
}
|
||||
if(err) return;
|
||||
const brew = res.body;
|
||||
window.location = `/edit/${brew.editId}`;
|
||||
});
|
||||
},
|
||||
handleSplitMove : function(){
|
||||
this.editor.current.update();
|
||||
this.refs.editor.update();
|
||||
},
|
||||
handleTextChange : function(text){
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, text: text },
|
||||
currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
|
||||
brew : { ...prevState.brew, text: text }
|
||||
}));
|
||||
},
|
||||
renderNavbar : function(){
|
||||
return <Navbar ver={this.props.ver}>
|
||||
<Nav.section>
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
null
|
||||
}
|
||||
<NewBrewItem />
|
||||
<HelpNavItem />
|
||||
<VaultNavItem />
|
||||
<RecentNavItem />
|
||||
<AccountNavItem />
|
||||
</Nav.section>
|
||||
@@ -89,22 +70,15 @@ const HomePage = createClass({
|
||||
{this.renderNavbar()}
|
||||
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove}>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<Editor
|
||||
ref={this.editor}
|
||||
ref='editor'
|
||||
brew={this.state.brew}
|
||||
onTextChange={this.handleTextChange}
|
||||
renderer={this.state.brew.renderer}
|
||||
showEditButtons={false}
|
||||
snippetBundle={this.state.themeBundle.snippets}
|
||||
/>
|
||||
<BrewRenderer
|
||||
text={this.state.brew.text}
|
||||
style={this.state.brew.style}
|
||||
renderer={this.state.brew.renderer}
|
||||
currentEditorPage={this.state.currentEditorPage}
|
||||
themeBundle={this.state.themeBundle}
|
||||
/>
|
||||
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer}/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -40,11 +40,4 @@
|
||||
right : 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.navItem.save{
|
||||
background-color: @orange;
|
||||
&:hover{
|
||||
background-color: @green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ The Homebrewery makes the creation and sharing of authentic looking Fifth-Editio
|
||||
**Try it!** Simply edit the text on the left and watch it *update live* on the right. Note that not every button is visible on this demo page. Click New {{fas,fa-plus-square}} in the navbar above to start brewing with all the features!
|
||||
|
||||
### Editing and Sharing
|
||||
When you create a new homebrew document ("brew"), your document will be given a *edit link* and a *share link*.
|
||||
When you create your own homebrew, you will be given a *edit url* and a *share url*.
|
||||
|
||||
The *edit link* is where you write your brew. If you edit a brew while logged in, you are added as one of the brew's authors, and no one else can edit that brew until you add them as a new author via the {{fa,fa-info-circle}} **Properties** tab. Brews without any author can still be edited by anyone with the *edit link*, so be careful about who you share it with if you prefer to work without an account.
|
||||
Any changes you make while on the *edit url* will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew, so be careful about who you share it with.
|
||||
|
||||
Anyone with the *share url* will be able to access a read-only version of your homebrew.
|
||||
|
||||
@@ -36,7 +36,7 @@ After clicking the "Print" item in the navbar a new page will open and a print d
|
||||
If you want to save ink or have a monochrome printer, add the **PRINT → {{fas,fa-tint}} Ink Friendly** snippet to your brew!
|
||||
}}
|
||||
|
||||
 {position:absolute,bottom:20px,left:130px,width:220px}
|
||||
 {position:absolute,bottom:20px,left:130px,width:220px}
|
||||
|
||||
{{artist,bottom:160px,left:100px
|
||||
##### Homebrew Mug
|
||||
@@ -48,63 +48,57 @@ If you want to save ink or have a monochrome printer, add the **PRINT → {{fas,
|
||||
|
||||
\column
|
||||
|
||||
## V3 vs Legacy
|
||||
The Homebrewery has two renderers: Legacy and V3. The V3 renderer is recommended for all users because it is more powerful, more customizable, and continues to receive new feature updates while Legacy does not. However Legacy mode will remain available for older brews and veteran users.
|
||||
|
||||
At any time, any individual brew can be changed to your renderer of choice via the {{fa,fa-info-circle}} **Properties** tab on your brew. However, converting between Legacy and V3 may require heavily tweaking the document; while both renderers can use raw HTML, V3 prefers a streamlined curly bracket syntax that avoids the complex HTML structures required by Legacy.
|
||||
## New in V3.0.0
|
||||
We've implemented an extended Markdown-like syntax for block and span elements, plus a few other changes, eliminating the need for HTML tags like `div` and `span` in most cases. No raw HTML tags should be needed in a brew (*but can still be used if you insist*).
|
||||
|
||||
Much of the syntax and styling has changed in V3, so converting a Legacy brew to V3 (or vice-versa) will require tweaking your document. *However*, all brews made prior to the release of v3.0.0 will still render normally, and you may switch between the "Legacy" brew renderer and the newer "V3" renderer via the {{fa,fa-info-circle}} **Properties** button on your brew at any time.
|
||||
|
||||
Scroll down to the next page for a brief summary of the changes and new features available in V3!
|
||||
|
||||
Scroll down to the next page for a brief summary of the changes and features available in V3!
|
||||
#### New Things All The Time!
|
||||
Check out the latest updates in the full changelog [here](/changelog).
|
||||
|
||||
### Helping out
|
||||
Like this tool? Head over to our [Patreon](https://www.patreon.com/Naturalcrit) to help us keep the servers running.
|
||||
Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/Naturalcrit) to help me keep the servers running.
|
||||
|
||||
|
||||
This tool will **always** be free, never have ads, and we will never offer any "premium" features or whatever.
|
||||
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
|
||||
|
||||
### Bugs, Issues, Suggestions?
|
||||
- Check the [Frequently Asked Questions](/faq) page first for quick answers.
|
||||
- Get help or the right look for your brew by posting on [r/Homebrewery](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) or joining the [Discord Of Many Things](https://discord.gg/by3deKx).
|
||||
- Report technical issues or provide feedback on the [GitHub Repo](https://github.com/naturalcrit/homebrewery/).
|
||||
Take a quick look at our [Frequently Asked Questions page](/faq) to see if your question has a handy answer.
|
||||
|
||||
Need help getting started or just the right look for your brew? Head to [r/Homebrewery](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let us know!
|
||||
|
||||
Have an idea to make The Homebrewery better? Or did you find something that wasn't quite right? Check out the [GitHub Repo](https://github.com/naturalcrit/homebrewery/) to report technical issues.
|
||||
|
||||
### Legal Junk
|
||||
The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/homebrewery/blob/master/license). Which means you are free to use The Homebrewery codebase any way that you want, except for claiming that you made it yourself.
|
||||
|
||||
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||
#### Crediting Us
|
||||
If you'd like to credit us in your brew, we'd be flattered! Just reference that you made it with The Homebrewery.
|
||||
|
||||
#### Crediting Me
|
||||
If you'd like to credit me in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
|
||||
|
||||
### More Homebrew Resources
|
||||
[{width:50px,float:right,padding-left:10px}](https://discord.gg/by3deKx)
|
||||
|
||||
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/wiki/resources). The [Discord Of Many Things](https://discord.gg/by3deKx) is another great resource to connect with fellow homebrewers for help and feedback.
|
||||
|
||||
<a href='https://discord.gg/by3deKx' target='_blank'><img src='/assets/discordOfManyThings.svg' alt='Discord of Many Things Logo' title='Discord of Many Things Logo' style='width:50px; float: right; padding-left: 10px;'/></a>
|
||||
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/wiki/resources). The <a href='https://discord.gg/by3deKx' target='_blank' title='Discord of Many Things'>Discord of Many Things</a> is another great resource to connect with fellow homebrewers for help and feedback.
|
||||
|
||||
{{position:absolute;top:20px;right:20px;width:auto
|
||||
[{height:30px}](https://discord.gg/by3deKx)
|
||||
[{height:30px}](https://github.com/naturalcrit/homebrewery)
|
||||
[{height:30px}](https://patreon.com/NaturalCrit)
|
||||
[{height:30px}](https://www.reddit.com/r/homebrewery/)
|
||||
<a href='https://discord.gg/by3deKx' target='_blank' title='Discord of Many Things' style='color: black;'><img src='/assets/discord.png' style='height:30px'/></a>
|
||||
<a href='https://github.com/naturalcrit/homebrewery' target='_blank' title='Github' style='color: black; padding-left: 5px;'><img src='/assets/github.png' style='height:30px'/></a>
|
||||
<a href='https://patreon.com/NaturalCrit' target='_blank' title='Patreon' style='color: black; padding-left: 5px;'><img src='/assets/patreon.png' style='height:30px'/></a>
|
||||
<a href='https://www.reddit.com/r/homebrewery/' target='_blank' title='Reddit' style='color: black; padding-left: 5px;'><img src='/assets/reddit.png' style='height:30px'/></a>
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Markdown+
|
||||
The Homebrewery aims to make homebrewing as simple as possible, providing a live editor with Markdown syntax that is more human-readable and faster to write with than raw HTML.
|
||||
|
||||
From version 3.0.0, with a goal of adding maximum flexibility without users resorting to complex HTML to accomplish simple tasks, Homebrewery provides an extended verision of Markdown with additional syntax.
|
||||
In version 3.0.0, with a goal of adding maximum flexibility without users resorting to complex HTML to accomplish simple tasks, Homebrewery provides an extended verision of Markdown with additional syntax.
|
||||
**You can enable V3 via the {{fa,fa-info-circle}} Properties button!**
|
||||
|
||||
### Curly Brackets
|
||||
Standard Markdown lacks several equivalences to HTML. Hence, we have introduced `{{ }}` as a replacement for `<span></span>` and `<div></div>` for a cleaner custom formatting. Inline spans and block elements can be created and given ID's and Classes, as well as CSS properties, each of which are comma separated with no spaces. Use double quotes if a value requires spaces. Spans and Blocks start the same:
|
||||
The biggest change in V3 is the replacement of `<span></span>` and `<div></div>` with `{{ }}` for a cleaner custom formatting. Inline spans and block elements can be created and given ID's and Classes, as well as css properties, each of which are comma separated with no spaces. Use double quotes if a value requires spaces. Spans and Blocks start the same:
|
||||
|
||||
#### Span
|
||||
My favorite author is {{pen,#author,color:orange,font-family:"trebuchet ms" Brandon Sanderson}}. The orange text has a class of `pen`, an id of `author`, is colored orange, and given a new font. The first space outside of quotes marks the beginning of the content.
|
||||
@@ -132,18 +126,16 @@ A blank line can be achieved with a run of one or more `:` alone on a line. More
|
||||
|
||||
::
|
||||
|
||||
|
||||
Much nicer than `<br><br><br><br><br>`
|
||||
|
||||
### Definition Lists
|
||||
**Example** :: V3 uses HTML *definition lists* to create "lists" with hanging indents.
|
||||
|
||||
|
||||
|
||||
### Column Breaks
|
||||
Column and page breaks with `\column` and `\page`.
|
||||
|
||||
\column
|
||||
|
||||
### Tables
|
||||
Tables now allow column & row spanning between cells. This is included in some updated snippets, but a simplified example is given below.
|
||||
|
||||
@@ -171,13 +163,13 @@ Using *Curly Injection* you can assign an id, classes, or inline CSS properties
|
||||
|
||||
 {width:100px,border:"2px solid",border-radius:10px}
|
||||
|
||||
\* *When using Imgur-hosted images, use the "direct link", which can be found when you click into your image in the Imgur interface.*
|
||||
\* *When using Imgur-hosted images, use the "direct link", which can be found when you click into your image in the Imgur interace.*
|
||||
|
||||
## Snippets
|
||||
Homebrewery comes with a series of *code snippets* found at the top of the editor pane that make it easy to create brews as quickly as possible. Just set your cursor where you want the code to appear in the editor pane, choose a snippet, and make the adjustments you need.
|
||||
|
||||
## Style Editor Panel
|
||||
{{fa,fa-paint-brush}} Usually overlooked or unused by some users, the **Style Editor** tab is located on the right side of the Snippet bar. This editor accepts CSS for styling without requiring `<style>` tags-- anything that would have gone inside style tags before can now be placed here, and snippets that insert CSS styles are now located on that tab.
|
||||
{{fa,fa-paint-brush}} Technically released prior to v3 but still new to many users, check out the new **Style Editor** located on the right side of the Snippet bar. This editor accepts CSS for styling without requiring `<style>` tags-- anything that would have gone inside style tags before can now be placed here, and snippets that insert CSS styles are now located on that tab.
|
||||
|
||||
{{pageNumber 2}}
|
||||
{{footnote PART 2 | BORING STUFF}}
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
require('./newPage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const _ = require('lodash');
|
||||
const request = require('superagent');
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const PrintNavItem = require('../../navbar/print.navitem.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
|
||||
@@ -18,39 +17,49 @@ const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
|
||||
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
|
||||
|
||||
const BREWKEY = 'homebrewery-new';
|
||||
const BREWKEY = 'homebrewery-new';
|
||||
const STYLEKEY = 'homebrewery-new-style';
|
||||
const METAKEY = 'homebrewery-new-meta';
|
||||
let SAVEKEY;
|
||||
const METAKEY = 'homebrewery-new-meta';
|
||||
|
||||
|
||||
const NewPage = createClass({
|
||||
displayName : 'NewPage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : DEFAULT_BREW
|
||||
brew : {
|
||||
text : '',
|
||||
style : undefined,
|
||||
title : '',
|
||||
description : '',
|
||||
renderer : 'V3',
|
||||
theme : '5ePHB'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
const brew = this.props.brew;
|
||||
let brew = this.props.brew;
|
||||
|
||||
if(this.props.brew.shareId) {
|
||||
brew = {
|
||||
text : brew.text ?? '',
|
||||
style : brew.style ?? undefined,
|
||||
title : brew.title ?? '',
|
||||
description : brew.description ?? '',
|
||||
renderer : brew.renderer ?? 'legacy',
|
||||
theme : brew.theme ?? '5ePHB'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
brew : brew,
|
||||
isSaving : false,
|
||||
saveGoogle : (global.account && global.account.googleId ? true : false),
|
||||
error : null,
|
||||
htmlErrors : Markdown.validate(brew.text),
|
||||
currentEditorPage : 0,
|
||||
themeBundle : {}
|
||||
brew : brew,
|
||||
isSaving : false,
|
||||
saveGoogle : (global.account && global.account.googleId ? true : false),
|
||||
errors : null,
|
||||
htmlErrors : Markdown.validate(brew.text)
|
||||
};
|
||||
},
|
||||
|
||||
editor : React.createRef(null),
|
||||
|
||||
componentDidMount : function() {
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
|
||||
@@ -67,26 +76,15 @@ const NewPage = createClass({
|
||||
// brew.description = metaStorage?.description || this.state.brew.description;
|
||||
brew.renderer = metaStorage?.renderer ?? brew.renderer;
|
||||
brew.theme = metaStorage?.theme ?? brew.theme;
|
||||
brew.lang = metaStorage?.lang ?? brew.lang;
|
||||
|
||||
this.setState({
|
||||
brew : brew
|
||||
});
|
||||
}
|
||||
|
||||
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`;
|
||||
const saveStorage = localStorage.getItem(SAVEKEY) || 'HOMEBREWERY';
|
||||
|
||||
this.setState({
|
||||
brew : brew,
|
||||
saveGoogle : (saveStorage == 'GOOGLE-DRIVE' && this.state.saveGoogle)
|
||||
});
|
||||
|
||||
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
|
||||
|
||||
localStorage.setItem(BREWKEY, brew.text);
|
||||
if(brew.style)
|
||||
localStorage.setItem(STYLEKEY, brew.style);
|
||||
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme, 'lang': brew.lang }));
|
||||
if(window.location.pathname != '/new') {
|
||||
window.history.replaceState({}, window.location.title, '/new/');
|
||||
}
|
||||
localStorage.setItem(STYLEKEY, brew.style);
|
||||
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme }));
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
@@ -97,7 +95,7 @@ const NewPage = createClass({
|
||||
const S_KEY = 83;
|
||||
const P_KEY = 80;
|
||||
if(e.keyCode == S_KEY) this.save();
|
||||
if(e.keyCode == P_KEY) printCurrentBrew();
|
||||
if(e.keyCode == P_KEY) this.print();
|
||||
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@@ -105,7 +103,7 @@ const NewPage = createClass({
|
||||
},
|
||||
|
||||
handleSplitMove : function(){
|
||||
this.editor.current.update();
|
||||
this.refs.editor.update();
|
||||
},
|
||||
|
||||
handleTextChange : function(text){
|
||||
@@ -114,9 +112,8 @@ const NewPage = createClass({
|
||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, text: text },
|
||||
htmlErrors : htmlErrors,
|
||||
currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
|
||||
brew : { ...prevState.brew, text: text },
|
||||
htmlErrors : htmlErrors
|
||||
}));
|
||||
localStorage.setItem(BREWKEY, text);
|
||||
},
|
||||
@@ -128,22 +125,24 @@ const NewPage = createClass({
|
||||
localStorage.setItem(STYLEKEY, style);
|
||||
},
|
||||
|
||||
handleMetaChange : function(metadata, field=undefined){
|
||||
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
|
||||
fetchThemeBundle(this, metadata.renderer, metadata.theme);
|
||||
|
||||
handleMetaChange : function(metadata){
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, ...metadata },
|
||||
}), ()=>{
|
||||
localStorage.setItem(METAKEY, JSON.stringify({
|
||||
// 'title' : this.state.brew.title,
|
||||
// 'description' : this.state.brew.description,
|
||||
'renderer' : this.state.brew.renderer,
|
||||
'theme' : this.state.brew.theme,
|
||||
'lang' : this.state.brew.lang
|
||||
}));
|
||||
}));
|
||||
localStorage.setItem(METAKEY, JSON.stringify({
|
||||
// 'title' : this.state.brew.title,
|
||||
// 'description' : this.state.brew.description,
|
||||
'renderer' : this.state.brew.renderer,
|
||||
'theme' : this.state.brew.theme
|
||||
}));
|
||||
},
|
||||
|
||||
clearErrors : function(){
|
||||
this.setState({
|
||||
errors : null,
|
||||
isSaving : false
|
||||
|
||||
});
|
||||
;
|
||||
},
|
||||
|
||||
save : async function(){
|
||||
@@ -151,6 +150,8 @@ const NewPage = createClass({
|
||||
isSaving : true
|
||||
});
|
||||
|
||||
console.log('saving new brew');
|
||||
|
||||
let brew = this.state.brew;
|
||||
// Split out CSS to Style if CSS codefence exists
|
||||
if(brew.text.startsWith('```css') && brew.text.indexOf('```\n\n') > 0) {
|
||||
@@ -160,11 +161,13 @@ const NewPage = createClass({
|
||||
}
|
||||
|
||||
brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
||||
|
||||
const res = await request
|
||||
.post(`/api${this.state.saveGoogle ? '?saveToGoogle=true' : ''}`)
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
this.setState({ isSaving: false, error: err });
|
||||
console.log(err);
|
||||
this.setState({ isSaving: false, errors: err });
|
||||
});
|
||||
if(!res) return;
|
||||
|
||||
@@ -176,6 +179,67 @@ const NewPage = createClass({
|
||||
},
|
||||
|
||||
renderSaveButton : function(){
|
||||
if(this.state.errors){
|
||||
let errMsg = '';
|
||||
try {
|
||||
errMsg += `${this.state.errors.toString()}\n\n`;
|
||||
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
|
||||
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
|
||||
console.log(errMsg);
|
||||
} catch (e){}
|
||||
|
||||
// if(this.state.errors.status == '401'){
|
||||
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
// Oops!
|
||||
// <div className='errorContainer' onClick={this.clearErrors}>
|
||||
// You must be signed in to a Google account
|
||||
// to save this to<br />Google Drive!<br />
|
||||
// <a target='_blank' rel='noopener noreferrer'
|
||||
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
// <div className='confirm'>
|
||||
// Sign In
|
||||
// </div>
|
||||
// </a>
|
||||
// <div className='deny'>
|
||||
// Not Now
|
||||
// </div>
|
||||
// </div>
|
||||
// </Nav.item>;
|
||||
// }
|
||||
|
||||
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={this.clearErrors}>
|
||||
Looks like your Google credentials have
|
||||
expired! Visit our log in page to sign out
|
||||
and sign back in with Google,
|
||||
then try saving again!
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://github.com/naturalcrit/homebrewery/issues/new?body=${encodeURIComponent(errMsg)}`}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(this.state.isSaving){
|
||||
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
|
||||
save...
|
||||
@@ -187,6 +251,16 @@ const NewPage = createClass({
|
||||
}
|
||||
},
|
||||
|
||||
print : function(){
|
||||
window.open('/print?dialog=true&local=print', '_blank');
|
||||
},
|
||||
|
||||
renderLocalPrintButton : function(){
|
||||
return <Nav.item color='purple' icon='far fa-file-pdf' onClick={this.print}>
|
||||
get PDF
|
||||
</Nav.item>;
|
||||
},
|
||||
|
||||
renderNavbar : function(){
|
||||
return <Navbar>
|
||||
|
||||
@@ -195,11 +269,8 @@ const NewPage = createClass({
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
this.renderSaveButton()
|
||||
}
|
||||
<PrintNavItem />
|
||||
{this.renderSaveButton()}
|
||||
{this.renderLocalPrintButton()}
|
||||
<HelpNavItem />
|
||||
<RecentNavItem />
|
||||
<AccountNavItem />
|
||||
@@ -211,28 +282,16 @@ const NewPage = createClass({
|
||||
return <div className='newPage sitePage'>
|
||||
{this.renderNavbar()}
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove}>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<Editor
|
||||
ref={this.editor}
|
||||
ref='editor'
|
||||
brew={this.state.brew}
|
||||
onTextChange={this.handleTextChange}
|
||||
onStyleChange={this.handleStyleChange}
|
||||
onMetaChange={this.handleMetaChange}
|
||||
renderer={this.state.brew.renderer}
|
||||
userThemes={this.props.userThemes}
|
||||
snippetBundle={this.state.themeBundle.snippets}
|
||||
/>
|
||||
<BrewRenderer
|
||||
text={this.state.brew.text}
|
||||
style={this.state.brew.style}
|
||||
renderer={this.state.brew.renderer}
|
||||
theme={this.state.brew.theme}
|
||||
themeBundle={this.state.themeBundle}
|
||||
errors={this.state.htmlErrors}
|
||||
lang={this.state.brew.lang}
|
||||
currentEditorPage={this.state.currentEditorPage}
|
||||
allowPrint={true}
|
||||
/>
|
||||
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} errors={this.state.htmlErrors}/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
@@ -4,5 +4,79 @@
|
||||
&:hover{
|
||||
background-color: @green;
|
||||
}
|
||||
&.error{
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
}
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 100000;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
client/homebrew/pages/printPage/printPage.jsx
Normal file
@@ -0,0 +1,108 @@
|
||||
require('./printPage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const Themes = require('themes/themes.json');
|
||||
|
||||
const BREWKEY = 'homebrewery-new';
|
||||
const STYLEKEY = 'homebrewery-new-style';
|
||||
const METAKEY = 'homebrewery-new-meta';
|
||||
|
||||
const PrintPage = createClass({
|
||||
displayName : 'PrintPage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
query : {},
|
||||
brew : {
|
||||
text : '',
|
||||
style : '',
|
||||
renderer : 'legacy'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
brew : {
|
||||
text : this.props.brew.text || '',
|
||||
style : this.props.brew.style || undefined,
|
||||
renderer : this.props.brew.renderer || 'legacy'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
if(this.props.query.local == 'print'){
|
||||
const brewStorage = localStorage.getItem(BREWKEY);
|
||||
const styleStorage = localStorage.getItem(STYLEKEY);
|
||||
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
|
||||
|
||||
this.setState((prevState, prevProps)=>{
|
||||
return {
|
||||
brew : {
|
||||
text : brewStorage,
|
||||
style : styleStorage,
|
||||
renderer : metaStorage?.renderer || 'legacy',
|
||||
theme : metaStorage?.theme || '5ePHB'
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if(this.props.query.dialog) window.print();
|
||||
},
|
||||
|
||||
renderStyle : function() {
|
||||
if(!this.state.brew.style) return;
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.state.brew.style} </style>` }} />;
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
if(this.state.brew.renderer == 'legacy') {
|
||||
return _.map(this.state.brew.text.split('\\page'), (pageText, index)=>{
|
||||
return <div
|
||||
className='phb page'
|
||||
id={`p${index + 1}`}
|
||||
dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }}
|
||||
key={index} />;
|
||||
});
|
||||
} else {
|
||||
return _.map(this.state.brew.text.split(/^\\page$/gm), (pageText, index)=>{
|
||||
pageText += `\n\n \n\\column\n `; //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>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render : function(){
|
||||
const rendererPath = this.state.brew.renderer == 'V3' ? 'V3' : 'Legacy';
|
||||
const themePath = this.state.brew.theme ?? '5ePHB';
|
||||
const baseThemePath = Themes[rendererPath][themePath].baseTheme;
|
||||
|
||||
return <div>
|
||||
<Meta name='robots' content='noindex, nofollow' />
|
||||
<link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/>
|
||||
{baseThemePath &&
|
||||
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
|
||||
}
|
||||
<link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/>
|
||||
{/* Apply CSS from Style tab */}
|
||||
{this.renderStyle()}
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = PrintPage;
|
||||
3
client/homebrew/pages/printPage/printPage.less
Normal file
@@ -0,0 +1,3 @@
|
||||
.printPage{
|
||||
|
||||
}
|
||||
@@ -5,34 +5,33 @@ const { Meta } = require('vitreum/headtags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const MetadataNav = require('../../navbar/metadata.navitem.jsx');
|
||||
const PrintNavItem = require('../../navbar/print.navitem.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
|
||||
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
|
||||
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
|
||||
|
||||
const SharePage = createClass({
|
||||
displayName : 'SharePage',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : DEFAULT_BREW_LOAD,
|
||||
disableMeta : false
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
themeBundle : {}
|
||||
brew : {
|
||||
title : '',
|
||||
text : '',
|
||||
style : '',
|
||||
shareId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
views : 0,
|
||||
renderer : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
|
||||
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
|
||||
},
|
||||
|
||||
componentWillUnmount : function() {
|
||||
@@ -43,7 +42,7 @@ const SharePage = createClass({
|
||||
if(!(e.ctrlKey || e.metaKey)) return;
|
||||
const P_KEY = 80;
|
||||
if(e.keyCode == P_KEY){
|
||||
if(e.keyCode == P_KEY) printCurrentBrew();
|
||||
window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -55,52 +54,28 @@ const SharePage = createClass({
|
||||
this.props.brew.shareId;
|
||||
},
|
||||
|
||||
renderEditLink : function(){
|
||||
if(!this.props.brew.editId) return;
|
||||
|
||||
let editLink = this.props.brew.editId;
|
||||
if(this.props.brew.googleId && !this.props.brew.stubbed) {
|
||||
editLink = this.props.brew.googleId + editLink;
|
||||
}
|
||||
|
||||
return <Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}>
|
||||
edit
|
||||
</Nav.item>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
const titleStyle = this.props.disableMeta ? { cursor: 'default' } : {};
|
||||
const titleEl = <Nav.item className='brewTitle' style={titleStyle}>{this.props.brew.title}</Nav.item>;
|
||||
|
||||
return <div className='sharePage sitePage'>
|
||||
<Meta name='robots' content='noindex, nofollow' />
|
||||
<Navbar>
|
||||
<Nav.section className='titleSection'>
|
||||
{
|
||||
this.props.disableMeta ?
|
||||
titleEl
|
||||
:
|
||||
<MetadataNav brew={this.props.brew}>
|
||||
{titleEl}
|
||||
</MetadataNav>
|
||||
}
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{this.props.brew.shareId && <>
|
||||
<PrintNavItem/>
|
||||
<PrintLink shareId={this.processShareId()} />
|
||||
<Nav.dropdown>
|
||||
<Nav.item color='red' icon='fas fa-code'>
|
||||
source
|
||||
</Nav.item>
|
||||
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${this.processShareId()}`}>
|
||||
<Nav.item color='blue' href={`/source/${this.processShareId()}`}>
|
||||
view
|
||||
</Nav.item>
|
||||
{this.renderEditLink()}
|
||||
<Nav.item color='blue' icon='fas fa-download' href={`/download/${this.processShareId()}`}>
|
||||
<Nav.item color='blue' href={`/download/${this.processShareId()}`}>
|
||||
download
|
||||
</Nav.item>
|
||||
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${this.processShareId()}`}>
|
||||
<Nav.item color='blue' href={`/new/${this.processShareId()}`}>
|
||||
clone to new
|
||||
</Nav.item>
|
||||
</Nav.dropdown>
|
||||
@@ -111,14 +86,7 @@ const SharePage = createClass({
|
||||
</Navbar>
|
||||
|
||||
<div className='content'>
|
||||
<BrewRenderer
|
||||
text={this.props.brew.text}
|
||||
style={this.props.brew.style}
|
||||
renderer={this.props.brew.renderer}
|
||||
theme={this.props.brew.theme}
|
||||
themeBundle={this.state.themeBundle}
|
||||
allowPrint={true}
|
||||
/>
|
||||
<BrewRenderer text={this.props.brew.text} style={this.props.brew.style} renderer={this.props.brew.renderer} theme={this.props.brew.theme} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
.sharePage{
|
||||
.navContent .navSection.titleSection {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
.content{
|
||||
overflow-y : hidden;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const ListPage = require('../basePages/listPage/listPage.jsx');
|
||||
|
||||
@@ -11,8 +12,6 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||
const VaultNavitem = require('../../navbar/vault.navitem.jsx');
|
||||
|
||||
const UserPage = createClass({
|
||||
displayName : 'UserPage',
|
||||
@@ -20,8 +19,7 @@ const UserPage = createClass({
|
||||
return {
|
||||
username : '',
|
||||
brews : [],
|
||||
query : '',
|
||||
error : null
|
||||
query : ''
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
@@ -52,22 +50,12 @@ const UserPage = createClass({
|
||||
brewCollection : brewCollection
|
||||
};
|
||||
},
|
||||
errorReported : function(error) {
|
||||
this.setState({
|
||||
error
|
||||
});
|
||||
},
|
||||
|
||||
navItems : function() {
|
||||
return <Navbar>
|
||||
<Nav.section>
|
||||
{this.state.error ?
|
||||
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
|
||||
null
|
||||
}
|
||||
<NewBrew />
|
||||
<HelpNavItem />
|
||||
<VaultNavitem/>
|
||||
<RecentNavItem />
|
||||
<Account />
|
||||
</Nav.section>
|
||||
@@ -75,7 +63,7 @@ const UserPage = createClass({
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query} reportError={this.errorReported}></ListPage>;
|
||||
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query}></ListPage>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
require('./vaultPage.less');
|
||||
|
||||
const React = require('react');
|
||||
const { useState, useEffect, useRef } = React;
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||
const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
|
||||
const SplitPane = require('../../../../shared/naturalcrit/splitPane/splitPane.jsx');
|
||||
const ErrorIndex = require('../errorPage/errors/errorIndex.js');
|
||||
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
|
||||
const VaultPage = (props)=>{
|
||||
const [pageState, setPageState] = useState(parseInt(props.query.page) || 1);
|
||||
|
||||
//Response state
|
||||
const [brewCollection, setBrewCollection] = useState(null);
|
||||
const [totalBrews, setTotalBrews] = useState(null);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
|
||||
const titleRef = useRef(null);
|
||||
const authorRef = useRef(null);
|
||||
const countRef = useRef(null);
|
||||
const v3Ref = useRef(null);
|
||||
const legacyRef = useRef(null);
|
||||
const submitButtonRef = useRef(null);
|
||||
|
||||
useEffect(()=>{
|
||||
disableSubmitIfFormInvalid();
|
||||
loadPage(pageState, true);
|
||||
}, []);
|
||||
|
||||
const updateStateWithBrews = (brews, page)=>{
|
||||
setBrewCollection(brews || null);
|
||||
setPageState(parseInt(page) || 1);
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const updateUrl = (titleValue, authorValue, countValue, v3Value, legacyValue, page)=>{
|
||||
const url = new URL(window.location.href);
|
||||
const urlParams = new URLSearchParams(url.search);
|
||||
|
||||
urlParams.set('title', titleValue);
|
||||
urlParams.set('author', authorValue);
|
||||
urlParams.set('count', countValue);
|
||||
urlParams.set('v3', v3Value);
|
||||
urlParams.set('legacy', legacyValue);
|
||||
urlParams.set('page', page);
|
||||
|
||||
url.search = urlParams.toString();
|
||||
window.history.replaceState(null, '', url.toString());
|
||||
};
|
||||
|
||||
const performSearch = async (title, author, count, v3, legacy, page)=>{
|
||||
updateUrl(title, author, count, v3, legacy, page);
|
||||
|
||||
const response = await request.get(
|
||||
`/api/vault?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}&count=${count}&page=${page}`
|
||||
).catch((error)=>{
|
||||
console.log('error at loadPage: ', error);
|
||||
setError(error);
|
||||
updateStateWithBrews([], 1);
|
||||
});
|
||||
|
||||
if(response.ok)
|
||||
updateStateWithBrews(response.body.brews, page);
|
||||
};
|
||||
|
||||
const loadTotal = async (title, author, v3, legacy)=>{
|
||||
setTotalBrews(null);
|
||||
|
||||
const response = await request.get(
|
||||
`/api/vault/total?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}`
|
||||
).catch((error)=>{
|
||||
console.log('error at loadTotal: ', error);
|
||||
setError(error);
|
||||
updateStateWithBrews([], 1);
|
||||
});
|
||||
|
||||
if(response.ok)
|
||||
setTotalBrews(response.body.totalBrews);
|
||||
};
|
||||
|
||||
const loadPage = async (page, updateTotal)=>{
|
||||
if(!validateForm())
|
||||
return;
|
||||
|
||||
setSearching(true);
|
||||
setError(null);
|
||||
|
||||
const title = titleRef.current.value || '';
|
||||
const author = authorRef.current.value || '';
|
||||
const count = countRef.current.value || 10;
|
||||
const v3 = v3Ref.current.checked != false;
|
||||
const legacy = legacyRef.current.checked != false;
|
||||
|
||||
performSearch(title, author, count, v3, legacy, page);
|
||||
|
||||
if(updateTotal)
|
||||
loadTotal(title, author, v3, legacy);
|
||||
};
|
||||
|
||||
const renderNavItems = ()=>(
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>
|
||||
Vault: Search for brews
|
||||
</Nav.item>
|
||||
</Nav.section>
|
||||
<Nav.section>
|
||||
<NewBrew />
|
||||
<HelpNavItem />
|
||||
<RecentNavItem />
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
const validateForm = ()=>{
|
||||
//form validity: title or author must be written, and at least one renderer set
|
||||
const isTitleValid = titleRef.current.validity.valid && titleRef.current.value;
|
||||
const isAuthorValid = authorRef.current.validity.valid && authorRef.current.value;
|
||||
const isCheckboxChecked = legacyRef.current.checked || v3Ref.current.checked;
|
||||
|
||||
const isFormValid = (isTitleValid || isAuthorValid) && isCheckboxChecked;
|
||||
|
||||
return isFormValid;
|
||||
};
|
||||
|
||||
const disableSubmitIfFormInvalid = ()=>{
|
||||
submitButtonRef.current.disabled = !validateForm();
|
||||
};
|
||||
|
||||
const renderForm = ()=>(
|
||||
<div className='brewLookup'>
|
||||
<h2 className='formTitle'>Brew Lookup</h2>
|
||||
<div className='formContents'>
|
||||
<label>
|
||||
Title of the brew
|
||||
<input
|
||||
ref={titleRef}
|
||||
type='text'
|
||||
name='title'
|
||||
defaultValue={props.query.title || ''}
|
||||
onKeyUp={disableSubmitIfFormInvalid}
|
||||
pattern='.{3,}'
|
||||
title='At least 3 characters'
|
||||
onKeyDown={(e)=>{
|
||||
if(e.key === 'Enter' && !submitButtonRef.current.disabled)
|
||||
loadPage(1, true);
|
||||
}}
|
||||
placeholder='v3 Reference Document'
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Author of the brew
|
||||
<input
|
||||
ref={authorRef}
|
||||
type='text'
|
||||
name='author'
|
||||
pattern='.{1,}'
|
||||
defaultValue={props.query.author || ''}
|
||||
onKeyUp={disableSubmitIfFormInvalid}
|
||||
onKeyDown={(e)=>{
|
||||
if(e.key === 'Enter' && !submitButtonRef.current.disabled)
|
||||
loadPage(1, true);
|
||||
}}
|
||||
placeholder='Username'
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Results per page
|
||||
<select ref={countRef} name='count' defaultValue={props.query.count || 20}>
|
||||
<option value='10'>10</option>
|
||||
<option value='20'>20</option>
|
||||
<option value='40'>40</option>
|
||||
<option value='60'>60</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input
|
||||
className='renderer'
|
||||
ref={v3Ref}
|
||||
type='checkbox'
|
||||
defaultChecked={props.query.v3 !== 'false'}
|
||||
onChange={disableSubmitIfFormInvalid}
|
||||
/>
|
||||
Search for v3 brews
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input
|
||||
className='renderer'
|
||||
ref={legacyRef}
|
||||
type='checkbox'
|
||||
defaultChecked={props.query.legacy !== 'false'}
|
||||
onChange={disableSubmitIfFormInvalid}
|
||||
/>
|
||||
Search for legacy brews
|
||||
</label>
|
||||
|
||||
<button
|
||||
id='searchButton'
|
||||
ref={submitButtonRef}
|
||||
onClick={()=>{
|
||||
loadPage(1, true);
|
||||
}}
|
||||
>
|
||||
Search
|
||||
<i
|
||||
className={searching ? 'fas fa-spin fa-spinner': 'fas fa-search'}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<legend>
|
||||
<h3>Tips and tricks</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Only <b>published</b> brews are searchable via this tool
|
||||
</li>
|
||||
<li>
|
||||
Usernames are case-sensitive
|
||||
</li>
|
||||
<li>
|
||||
Use <code>"word"</code> to match an exact string,
|
||||
and <code>-</code> to exclude words (at least one word must not be negated)
|
||||
</li>
|
||||
<li>
|
||||
Some common words like "a", "after", "through", "itself", "here", etc.,
|
||||
are ignored in searches. The full list can be found
|
||||
<a href='https://github.com/mongodb/mongo/blob/0e3b3ca8480ddddf5d0105d11a94bd4698335312/src/mongo/db/fts/stop_words_english.txt'>
|
||||
here
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<small>New features will be coming, such as filters and search by tags.</small>
|
||||
</legend>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderPaginationControls = ()=>{
|
||||
if(!totalBrews) return null;
|
||||
|
||||
const countInt = parseInt(props.query.count || 20);
|
||||
const totalPages = Math.ceil(totalBrews / countInt);
|
||||
|
||||
let startPage, endPage;
|
||||
if(pageState <= 6) {
|
||||
startPage = 1;
|
||||
endPage = Math.min(totalPages, 10);
|
||||
} else if(pageState + 4 >= totalPages) {
|
||||
startPage = Math.max(1, totalPages - 9);
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
startPage = pageState - 5;
|
||||
endPage = pageState + 4;
|
||||
}
|
||||
|
||||
const pagesAroundCurrent = new Array(endPage - startPage + 1)
|
||||
.fill()
|
||||
.map((_, index)=>(
|
||||
<a
|
||||
key={startPage + index}
|
||||
className={`pageNumber ${
|
||||
pageState === startPage + index ? 'currentPage' : ''
|
||||
}`}
|
||||
onClick={()=>loadPage(startPage + index, false)}
|
||||
>
|
||||
{startPage + index}
|
||||
</a>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className='paginationControls'>
|
||||
<button
|
||||
className='previousPage'
|
||||
onClick={()=>loadPage(pageState - 1, false)}
|
||||
disabled={pageState === startPage}
|
||||
>
|
||||
<i className='fa-solid fa-chevron-left'></i>
|
||||
</button>
|
||||
<ol className='pages'>
|
||||
{startPage > 1 && (
|
||||
<a
|
||||
className='pageNumber firstPage'
|
||||
onClick={()=>loadPage(1, false)}
|
||||
>
|
||||
1 ...
|
||||
</a>
|
||||
)}
|
||||
{pagesAroundCurrent}
|
||||
{endPage < totalPages && (
|
||||
<a
|
||||
className='pageNumber lastPage'
|
||||
onClick={()=>loadPage(totalPages, false)}
|
||||
>
|
||||
... {totalPages}
|
||||
</a>
|
||||
)}
|
||||
</ol>
|
||||
<button
|
||||
className='nextPage'
|
||||
onClick={()=>loadPage(pageState + 1, false)}
|
||||
disabled={pageState === totalPages}
|
||||
>
|
||||
<i className='fa-solid fa-chevron-right'></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFoundBrews = ()=>{
|
||||
if(searching) {
|
||||
return (
|
||||
<div className='foundBrews searching'>
|
||||
<h3 className='searchAnim'>Searching</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(error) {
|
||||
const errorText = ErrorIndex()[error.HBErrorCode.toString()] || '';
|
||||
|
||||
return (
|
||||
<div className='foundBrews noBrews'>
|
||||
<h3>Error: {errorText}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(!brewCollection) {
|
||||
return (
|
||||
<div className='foundBrews noBrews'>
|
||||
<h3>No search yet</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(brewCollection.length === 0) {
|
||||
return (
|
||||
<div className='foundBrews noBrews'>
|
||||
<h3>No brews found</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='foundBrews'>
|
||||
<span className='totalBrews'>
|
||||
{`Brews found: `}
|
||||
<span>{totalBrews}</span>
|
||||
</span>
|
||||
{brewCollection.map((brew, index)=>{
|
||||
return (
|
||||
<BrewItem
|
||||
brew={{ ...brew }}
|
||||
key={index}
|
||||
reportError={props.reportError}
|
||||
renderStorage={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{renderPaginationControls()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='vaultPage'>
|
||||
<link href='/themes/V3/Blank/style.css' rel='stylesheet' />
|
||||
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet' />
|
||||
{renderNavItems()}
|
||||
<div className='content'>
|
||||
<SplitPane showDividerButtons={false}>
|
||||
<div className='form dataGroup'>{renderForm()}</div>
|
||||
|
||||
<div className='resultsContainer dataGroup'>
|
||||
{renderFoundBrews()}
|
||||
</div>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = VaultPage;
|
||||
@@ -1,371 +0,0 @@
|
||||
body {
|
||||
height : 100vh;
|
||||
|
||||
.content { height : 100%; }
|
||||
|
||||
small {
|
||||
font-size : 10pt;
|
||||
color : #555555;
|
||||
|
||||
a { color : #333333; }
|
||||
}
|
||||
|
||||
code {
|
||||
padding-inline : 5px;
|
||||
background : lightgrey;
|
||||
border-radius : 5px;
|
||||
}
|
||||
|
||||
*:not(input) { user-select : none; }
|
||||
}
|
||||
|
||||
.vaultPage {
|
||||
height : 100%;
|
||||
overflow-y : hidden;
|
||||
background-color : #2C3E50;
|
||||
|
||||
|
||||
.content {
|
||||
background : #2C3E50;
|
||||
|
||||
.dataGroup {
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
background : white;
|
||||
|
||||
&.form .brewLookup {
|
||||
position : relative;
|
||||
padding : 50px clamp(20px, 4vw, 50px);
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family : 'CodeBold';
|
||||
letter-spacing : 2px;
|
||||
}
|
||||
|
||||
|
||||
legend {
|
||||
h3 {
|
||||
margin-block : 30px 20px;
|
||||
font-size : 20px;
|
||||
text-align : center;
|
||||
border-bottom : 2px solid;
|
||||
}
|
||||
ul {
|
||||
padding-inline : 30px 10px;
|
||||
li {
|
||||
margin-block : 5px;
|
||||
line-height : calc(1em + 5px);
|
||||
list-style : disc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
position : absolute;
|
||||
top : 0;
|
||||
right : 0;
|
||||
left : 0;
|
||||
display : block;
|
||||
padding : 10px;
|
||||
font-weight : 900;
|
||||
color : white;
|
||||
white-space : pre-wrap;
|
||||
content : 'Error:\A At least one renderer should be enabled to make a search';
|
||||
background : rgb(255, 60, 60);
|
||||
opacity : 0;
|
||||
transition : opacity 0.5s;
|
||||
}
|
||||
&:not(:has(input[type='checkbox']:checked))::after { opacity : 1; }
|
||||
|
||||
.formTitle {
|
||||
margin : 20px 0;
|
||||
font-size : 30px;
|
||||
color : black;
|
||||
text-align : center;
|
||||
border-bottom : 2px solid;
|
||||
}
|
||||
|
||||
.formContents {
|
||||
position : relative;
|
||||
display : flex;
|
||||
flex-direction : column;
|
||||
|
||||
label {
|
||||
display : flex;
|
||||
align-items : center;
|
||||
margin : 10px 0;
|
||||
}
|
||||
select { margin : 0 10px; }
|
||||
|
||||
input {
|
||||
margin : 0 10px;
|
||||
|
||||
&:invalid { background : rgb(255, 188, 181); }
|
||||
|
||||
&[type='checkbox'] {
|
||||
position : relative;
|
||||
display : inline-block;
|
||||
width : 50px;
|
||||
height : 30px;
|
||||
font-family : 'WalterTurncoat';
|
||||
font-size : 20px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
letter-spacing : 2px;
|
||||
appearance : none;
|
||||
background : red;
|
||||
isolation : isolate;
|
||||
border-radius : 5px;
|
||||
|
||||
&::before,&::after {
|
||||
position : absolute;
|
||||
inset : 0;
|
||||
z-index : 5;
|
||||
padding-top : 2px;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display : block;
|
||||
content : 'No';
|
||||
}
|
||||
|
||||
&::after {
|
||||
display : none;
|
||||
content : 'Yes';
|
||||
}
|
||||
|
||||
&:checked {
|
||||
background : green;
|
||||
|
||||
&::before { display : none; }
|
||||
&::after { display : block; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#searchButton {
|
||||
position : absolute;
|
||||
right : 20px;
|
||||
bottom : 0;
|
||||
|
||||
i {
|
||||
margin-left : 10px;
|
||||
animation-duration : 1000s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.resultsContainer {
|
||||
display : flex;
|
||||
flex-direction : column;
|
||||
height : 100%;
|
||||
overflow-y : auto;
|
||||
font-family : 'BookInsanityRemake';
|
||||
font-size : 0.34cm;
|
||||
|
||||
|
||||
h3 {
|
||||
font-family : 'Open Sans';
|
||||
font-weight : 900;
|
||||
color : white;
|
||||
}
|
||||
|
||||
.foundBrews {
|
||||
position : relative;
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
max-height : 100%;
|
||||
padding : 50px 50px 70px 50px;
|
||||
overflow-y : scroll;
|
||||
background-color : #2C3E50;
|
||||
|
||||
h3 { font-size : 25px; }
|
||||
|
||||
&.noBrews {
|
||||
display : grid;
|
||||
place-items : center;
|
||||
color : white;
|
||||
}
|
||||
|
||||
&.searching {
|
||||
display : grid;
|
||||
place-items : center;
|
||||
color : white;
|
||||
|
||||
h3 { position : relative; }
|
||||
|
||||
h3.searchAnim::after {
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
right : 0;
|
||||
width : max-content;
|
||||
height : 1em;
|
||||
content : '';
|
||||
translate : calc(100% + 5px) -50%;
|
||||
animation : trailingDots 2s ease infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.totalBrews {
|
||||
position : fixed;
|
||||
right : 0;
|
||||
bottom : 0;
|
||||
z-index : 1000;
|
||||
padding : 8px 10px;
|
||||
font-family : 'Open Sans';
|
||||
font-size : 11px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
background-color : #333333;
|
||||
|
||||
.searchAnim {
|
||||
position : relative;
|
||||
display : inline-block;
|
||||
width : 3ch;
|
||||
height : 1em;
|
||||
}
|
||||
|
||||
.searchAnim::after {
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
right : 0;
|
||||
width : max-content;
|
||||
height : 1em;
|
||||
content : '';
|
||||
translate : -50% -50%;
|
||||
animation : trailingDots 2s ease infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.brewItem {
|
||||
width : 47%;
|
||||
margin-right : 40px;
|
||||
color : black;
|
||||
isolation:isolate;
|
||||
|
||||
&:after {
|
||||
position:absolute;
|
||||
inset:0;
|
||||
display:block;
|
||||
content:'';
|
||||
background-image : url('/assets/parchmentBackground.jpg');
|
||||
z-index:-1;
|
||||
}
|
||||
|
||||
&:nth-child(even of .brewItem) { margin-right : 0; }
|
||||
|
||||
h2 {
|
||||
font-family : 'MrEavesRemake';
|
||||
font-size : 0.75cm;
|
||||
font-weight : 800;
|
||||
line-height : 0.988em;
|
||||
color : var(--HB_Color_HeaderText);
|
||||
}
|
||||
.info {
|
||||
font-family : 'ScalySansRemake';
|
||||
font-size : 1.2em;
|
||||
position:relative;
|
||||
z-index:2;
|
||||
|
||||
>span {
|
||||
margin-right : 12px;
|
||||
line-height : 1.5em;
|
||||
}
|
||||
}
|
||||
.links {
|
||||
z-index:2;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 0px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
z-index:1;
|
||||
}
|
||||
}
|
||||
|
||||
.paginationControls {
|
||||
position : absolute;
|
||||
left : 50%;
|
||||
display : grid;
|
||||
grid-template-areas : 'previousPage currentPage nextPage';
|
||||
grid-template-columns : 50px 1fr 50px;
|
||||
place-items : center;
|
||||
width : auto;
|
||||
translate : -50%;
|
||||
|
||||
.pages {
|
||||
display : flex;
|
||||
grid-area : currentPage;
|
||||
justify-content : space-evenly;
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
padding : 5px 8px;
|
||||
text-align : center;
|
||||
|
||||
.pageNumber {
|
||||
margin-inline : 1vw;
|
||||
font-family : 'Open Sans';
|
||||
font-weight : 900;
|
||||
color : white;
|
||||
text-underline-position : under;
|
||||
text-wrap : nowrap;
|
||||
cursor : pointer;
|
||||
|
||||
&.currentPage {
|
||||
color : gold;
|
||||
text-decoration : underline;
|
||||
pointer-events : none;
|
||||
}
|
||||
|
||||
&.firstPage { margin-right : -5px; }
|
||||
|
||||
&.lastPage { margin-left : -5px; }
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
width : max-content;
|
||||
|
||||
&.previousPage { grid-area : previousPage; }
|
||||
|
||||
&.nextPage { grid-area : nextPage; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes trailingDots {
|
||||
|
||||
0%,
|
||||
32% { content : ' .'; }
|
||||
|
||||
33%,
|
||||
65% { content : ' ..'; }
|
||||
|
||||
66%,
|
||||
100% { content : ' ...'; }
|
||||
}
|
||||
|
||||
// media query for when the page is smaller than 1079 px in width
|
||||
@media screen and (max-width : 1079px) {
|
||||
.vaultPage .content {
|
||||
|
||||
.dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; }
|
||||
|
||||
.dataGroup.resultsContainer .foundBrews .brewItem {
|
||||
width : 100%;
|
||||
margin-inline : auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
const request = require('superagent');
|
||||
|
||||
const addHeader = (request)=>request.set('Homebrewery-Version', global.version);
|
||||
|
||||
const requestMiddleware = {
|
||||
get : (path)=>addHeader(request.get(path)),
|
||||
put : (path)=>addHeader(request.put(path)),
|
||||
post : (path)=>addHeader(request.post(path)),
|
||||
delete : (path)=>addHeader(request.delete(path)),
|
||||
};
|
||||
|
||||
module.exports = requestMiddleware;
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 791.04 953.29"><title>Davek</title><g id="Layer_2" data-name="Layer 2"><g id="Davek"><path d="M178.41,13.46a19.33,19.33,0,0,0-4.71,5.38q8.07,6.07,13.46,6.07a8.27,8.27,0,0,0,4.71-1.35,130.23,130.23,0,0,0,16.83-7.07,74.55,74.55,0,0,1,18.85-6.39h2.7q8.07,0,14.81,8.74a944.19,944.19,0,0,0,95.6,4.72q19.5,0,38.37-.67,69.33-2,139.68-5.72t139.7-5.06q16.82-.64,34.34-.66,50.49,0,98.29,3.36-17.5,12.12-22.55,31.64t-5,33.66q.64,22.89.66,45.1,0,47.13-3.36,97-6.07,74.05-9.78,148.11t-5,146.09v17.51a766.1,766.1,0,0,0,8.75,118.48,38.57,38.57,0,0,0-4,17.51,30.94,30.94,0,0,0,.67,6.06q2,12.12,3.36,23.22c.9,7.42,1.57,14.92,2,22.55v3.37a57.93,57.93,0,0,1-3.36,19.52c.43,4.5.67,8.77.67,12.8a260.65,260.65,0,0,1-2.7,37,344.26,344.26,0,0,0-4,52.52,133.5,133.5,0,0,0,8.09,45.44q8.07,22.57,33,36.68-6.06,8.78-20.19,8.77H762.1c-4.5-.45-8.53-.69-12.12-.69a78.11,78.11,0,0,0-21.54,2.7,579.1,579.1,0,0,0-63.64,3.71q-33.31,3.71-67.65,6.39t-68.66,3.37h-4a188.05,188.05,0,0,1-59.92-9.43q20.19-4,39.06-23.22t20.19-47.46q11.44-22.21,11.45-49.82a320.44,320.44,0,0,1,3.36-49.15q-9.45-4.69-10.09-8.75v-2.7a73,73,0,0,1,.66-8.74,105.81,105.81,0,0,0,3.37-12.8,7.49,7.49,0,0,0,.68-3.37q0-4.7-4.05-10.09c.45-4.93.69-10.1.69-15.48a311.71,311.71,0,0,0-3.37-46.45,207.31,207.31,0,0,1-1.35-24.25,274.58,274.58,0,0,1,4-45.1l15.5,6.73q-3.37-17.49-3.37-41.07,0-24.89,8.75-44.44a27.73,27.73,0,0,0,2-9.43,15.32,15.32,0,0,0-3.36-10.09,60.75,60.75,0,0,1-10.1-15.48l-7.39,6.73q2.67-47.79,8.74-99,3.35-33.63,3.37-65.29,0-14.81-.69-29a205.09,205.09,0,0,1-4-41.74,190.26,190.26,0,0,1,2-26.92q4-37,14.81-67.33a25.14,25.14,0,0,1-2.68-11.43,31.13,31.13,0,0,1,.66-6.07V140q0-6.72-8.74-10.09-3.37-16.83-5.73-31.3T521.07,77.41q-55.2,2.7-115.78,4.71-19.55.7-39.72.69-38.38,0-74.06-2.7c-5.4,4.5-8.08,9.21-8.08,14.14v1.34a41.5,41.5,0,0,0,4.37,15.49q3.7,7.4,7.4,15.16a35,35,0,0,1,3.71,15.13q32.31,34.35,64,68.68a335.89,335.89,0,0,1,51.83,73.38q13.46,7.4,18.51,17.49t10.11,19.87q5.06,9.78,10.1,18.85t16.5,11.78v12.12a194.5,194.5,0,0,1-37.38-4q-20.52-4-40.73-6.73a114.48,114.48,0,0,0-17.49-1.35,97.2,97.2,0,0,0-20.2,2q-17.52,4.05-31,20.19-16.84-1.35-27.27-9.75a76.13,76.13,0,0,1-17.51-20.2q-7.06-11.76-14.47-24.9a79.77,79.77,0,0,0-18.84-22.57A305.87,305.87,0,0,1,177.73,237q-28.29-33.67-54.54-69T68,99.31A381.16,381.16,0,0,0,0,38.37q12.79,0,22.89-9.75A190.69,190.69,0,0,1,44.76,10.44Q56.54,2,68.66,0H72Q82.8,0,97,10.76Z"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 428.05 941.17"><title>Iokharic</title><g id="Layer_2" data-name="Layer 2"><g id="Iokharic"><path d="M334.76,909.61V259.3l2.74-89.18c3.43,0,6.18-8.23,7.55-24.69,3.43,0,7.55-8.92,13.72-27.44,13-11,19.89-21.27,19.89-31.56,0-13-5.48-20.58-17.15-23.32l-30.87,2.74H320.36c-21.27,13-39.79,22.64-56.94,27.44h-37c-11.67,0-26.76,7.55-46,22q-12.34,0-30.86,16.46c-10.29,0-40.48,26.75-91.93,80.95,0,8.23-6.17,21.26-18.52,38.41l-3.43,15.78v41.84L67.23,343c2.74,0,9.6,6.86,19.89,19.9,24,18.52,36.36,30.86,36.36,38.41l-12.35,10.29H105c-24.7-15.78-45.28-32.93-62.43-52.13L15.78,316.92,0,266.85c3.43-17.84,7.55-29.5,13.72-35v-11c0-18.52,7.55-39.79,22-63.8,0-9.6,8.23-21.27,24.7-34.3,0-9.6,15.77-26.07,46.64-50.08,19.9-16.46,46-28.12,76.83-35,5.49-6.86,21.27-14.41,46.65-21.95C238,5.49,251.07,0,270.28,0h137.2c8.91,0,15.77,8.23,20.57,24V40.47l-5.48,8.23V166c0,17.15-7.55,31.55-21.95,43.22v41.15l-2.75,24.7q0,9.26,24.7,30.87v38.41c0,10.29-4.81,19.9-15.09,28.82h-6.86V558.39c0,55.57-4.81,97.41-15.1,124.16-4.8,2.75-7.54,19.21-9.6,48.71l2.74,17.15-2.74,76.14v30.19q0,32.93-32.93,86.43C337.5,937.74,334.76,926.76,334.76,909.61Z"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 527.7 940.25"><title>Rellanic</title><g id="Layer_2" data-name="Layer 2"><g id="Rellanic"><path d="M527.7,5.45q-3.83,19.65-15,30.56a129.61,129.61,0,0,1-26.46,19.64q-9.84,6.56-31.66,15.28-19.63,7.65-31.64,16.38Q380.33,103.69,342.16,108a468.46,468.46,0,0,1-54,3.28q-15.83,0-30.56-1.1a53.19,53.19,0,0,0-20.19-6.55H217.74q-7.12,1.11-21.29,1.1a51.67,51.67,0,0,1-20.18-4.36q8.72,19.65,25.63,29.46,14.19,8.74,28.38,29.47a634.05,634.05,0,0,1,98.78,90.58l91.12,103.69a65.1,65.1,0,0,0-.54,8.19,42.47,42.47,0,0,0,.54,7.09c.73,1.82,1.27,3.29,1.64,4.37q7.08,8.75,10.92,12,1.62,1.1,12.55,14.19a14,14,0,0,1,3.27,6.55,9.75,9.75,0,0,1,1.1,4.37,9.62,9.62,0,0,1-1.1,4.36q35.46,43.66,51.3,89.5,3.25,9.82,5.45,19.64a288.59,288.59,0,0,1,10.37,68.75v8.19a296,296,0,0,1-9.81,76.94q-7.12,27.3-24,77.5L418,831.65Q383,872,344.88,899.31a243.27,243.27,0,0,1-90.59,38.19,179.84,179.84,0,0,1-31.64,2.75q-38.78,0-81.87-15.84A293.78,293.78,0,0,1,78,886.22a312.61,312.61,0,0,1-51.85-48,300.52,300.52,0,0,0-18-46.94,60.18,60.18,0,0,1-4.92-13.64,82.36,82.36,0,0,1-2.19-19.11,104.89,104.89,0,0,1,.56-10.91,176.12,176.12,0,0,1-1.64-24,199.79,199.79,0,0,1,2.72-32.74Q5.45,663,5.45,645a103.71,103.71,0,0,0-.54-10.92,242.44,242.44,0,0,1,50.74-67.66,646.83,646.83,0,0,0,57.86-61.12q11.44-10.89,25.09-13.1A88.3,88.3,0,0,1,163.71,489q14.17-1.11,29.46-1.1a108.11,108.11,0,0,0,28.38-7.63q17.44,8.75,27.29,12a124.47,124.47,0,0,1,28.38,13.1q8.71,4.38,23.46,17.46,9.29,9.86,17.47,28.38,7.07,12,9.27,21.83a35.16,35.16,0,0,1,1.64,9.83V585a80.23,80.23,0,0,1-8.73,27.28q-8.2,14.19-18,22.93a166.18,166.18,0,0,1-19.65,19.64q-13.1,8.74-20.72,13.1l-7.65-4.37v-1.64q0-12,6.55-18-8.17-6.55-10.36-10.92l-2.18-8.73c0-2.18-.74-5.81-2.19-10.91v-3.29a38,38,0,0,0-3.82-7.63,196.53,196.53,0,0,0-33.84-40.39Q185.53,542.43,162.61,537a163.71,163.71,0,0,0-50.75,9.81q-25.08,8.76-32.2,36Q67.12,615.56,67.13,654.3a256,256,0,0,0,3.26,39.83,176.75,176.75,0,0,0,5.47,28.38Q88.37,770,122.78,812a452.22,452.22,0,0,0,103.13,58.94,153.57,153.57,0,0,0,107,5.45q25.63-12,37.66-27.28,13.62-14.21,23.46-34.93,10.36-18.57,20.2-39.29Q426.72,753.05,437.1,740q3.27-44.76,5.47-61.12a228.17,228.17,0,0,0,3.26-38.21,213.15,213.15,0,0,0-1.64-26.19,245.3,245.3,0,0,0-8.17-48q-2.2-8.17-4.93-16.36-9.27-30.55-34.92-61.12a70,70,0,0,0-2.18-18,29.12,29.12,0,0,0-4.37-10.37,175.28,175.28,0,0,0-17.46-29.48l-18.55-27.27q-12-16.38-16.38-28.38a282.35,282.35,0,0,1-27.81-28.37q-20.22-26.2-24-31.66Q269,295.76,260.29,286q-10.92-12-31.1-25.11-36.56-31.65-79.12-70.94-45.31-39.28-88.41-66.58-14.74-8.17-17.46-16.9a16.93,16.93,0,0,0-.54-3.83V99.87q0-8.73,6.54-19.11A102.47,102.47,0,0,1,63.3,61.12q9.27-9.82,12.56-18.56a223.6,223.6,0,0,1,38.73-3.27,271,271,0,0,1,40.93,3.27A367.15,367.15,0,0,0,215,47.48c6.91,0,13.64-.17,20.2-.56a45,45,0,0,0,21.27,5.47q17.44,0,25.65-1.1h22.93a77.75,77.75,0,0,1,24,7.65,114,114,0,0,1,27.82-3.29H364q27.25,2.2,39.29,2.19,16.34,0,36.55-5.45,19.1-6.55,27.83-22.93h2.72A20.48,20.48,0,0,0,484.58,24c2.17-4.71,6.17-7.09,12-7.09a26.6,26.6,0,0,1,4.92.54v-.54c0-1.08.72-3.46,2.19-7.11a36.74,36.74,0,0,1,6-6.54C512.57,1.1,515.12,0,517.32,0,521,0,524.41,1.82,527.7,5.45Z"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 541.53217 512"
|
||||
version="1.1"
|
||||
id="back-cover-icon"
|
||||
sodipodi:docname="book-front-cover.svg"
|
||||
width="541.53217"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.39257813"
|
||||
inkscape:cx="-263.64179"
|
||||
inkscape:cy="444.49751"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<g id="g20308" transform="matrix(3.7795276,0,0,3.7795276,-201.76367,-251.58203)">
|
||||
<path id="rect20232" d="M95.1,66.6h-8.5c-4.7,0-8.5,3.8-8.5,8.5v21.4c3.5-0.4,7.4-0.5,12-0.5c0.7,0,0.6,0,1.2,0
|
||||
c0-2.4,0-4.2,0.3-6.2c0.3-2.2,2.2-5.8,3.5-7c0.9-0.9,3-3.2,7-3.7c1-0.1,2-0.1,2.8,0c2.6,0.3,4.6,1.6,6.1,2.6
|
||||
c3.9,2.7,7.4,6.4,14.8,13.8c6.3,6.3,9.8,9.8,12,12.4c1.1,1.3,2.1,2.4,2.9,4c0.9,1.7,1.4,4.2,1.4,5.6c0,1.4-0.5,4-1.4,5.6
|
||||
c-0.9,1.6-1.8,2.7-2.9,4c-2.2,2.6-5.6,6-11.8,12.2c-3.8,3.8-7.4,7.3-10.2,9.9c-1.4,1.3-2.6,2.4-3.6,3.3c-0.5,0.4-1,0.8-1.5,1.2
|
||||
c-0.3,0.2-0.5,0.4-1,0.7s-0.7,0.7-2.8,1.2c-4.3,1.1-6.3,0.4-9.4-1.3c-0.5-0.3-1.9-0.9-3.3-2.6c-1.4-1.7-2.1-3.7-2.4-5.1
|
||||
c-0.5-2.4-0.5-4.3-0.6-7.2c-3.9,0-6,0.1-6.5,0.1c-0.5,0.1,0.2-0.2-1.2,0.5c-1.7,0.8-3.6,2.8-4.4,4.5c-0.3,0.8-0.5,1-0.6,6.6
|
||||
c-0.1,2.2-0.2,4.3-0.4,6c0,0.3-0.1,0.6-0.1,0.8v1.9c0,4.7,3.8,8.5,8.5,8.5v16.9c-4.7,0-8.5,3.8-8.5,8.5c0,4.7,3.8,8.5,8.5,8.5h8.5
|
||||
h76.2c14,0,25.4-11.4,25.4-25.4V92c0-14-11.4-25.4-25.4-25.4L95.1,66.6z M171.3,168.2c4.7,0,8.5,3.8,8.5,8.5c0,4.7-3.8,8.5-8.5,8.5
|
||||
h-67.7v-16.9L171.3,168.2L171.3,168.2z"/>
|
||||
<path id="path20297" d="M63.4,158c1.8,1.6,4.5,1.9,5.5,0.7c0.3-0.4,0.7-4,0.8-8.1c0.2-5.9,0.5-7.9,1.4-10c1.7-3.7,4.9-7,8.6-8.9
|
||||
c3.1-1.5,3.6-1.6,11.7-1.6h8.5l0.3,7.6c0.3,7.5,0.3,7.7,1.7,8.5c0.8,0.5,2.1,0.7,2.8,0.5c0.8-0.2,7.4-6.4,14.9-13.9
|
||||
c12.4-12.4,13.5-13.7,13.5-15.5c0-1.8-1.1-3.1-13.8-15.7c-14.7-14.7-15.4-15.2-18-12.7c-1,1-1.1,1.9-1.1,7.6c0,3.6-0.2,6.9-0.3,7.4
|
||||
c-0.3,0.8-1.7,0.9-9.8,0.9c-15.6,0-21.1,1.7-27.9,8.5c-6.5,6.5-8.8,12-8.8,21.1c0,4.7,0.3,6.8,1.3,9.8
|
||||
C56.2,148.6,60.7,155.7,63.4,158L63.4,158z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 541.53217 512"
|
||||
version="1.1"
|
||||
id="front-cover-icon"
|
||||
sodipodi:docname="book-front-cover.svg"
|
||||
width="541.53217"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.39257813"
|
||||
inkscape:cx="-263.64179"
|
||||
inkscape:cy="444.49751"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<g
|
||||
id="g20308"
|
||||
transform="matrix(3.7795276,0,0,3.7795276,-201.76367,-251.58203)">
|
||||
<path
|
||||
id="rect20232"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:17.9;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill;stop-color:#000000"
|
||||
d="m 78.783305,66.564412 c -14.022889,0 -25.4,11.377111 -25.4,25.4 v 84.666668 c 0,14.02289 11.377111,25.4 25.4,25.4 h 76.199995 8.46667 c 4.68312,0 8.46667,-3.78355 8.46667,-8.46667 0,-4.68311 -3.78355,-8.46666 -8.46667,-8.46666 v -16.93334 c 4.68312,0 8.46667,-3.78355 8.46667,-8.46666 v -1.9327 c -0.0322,-0.27545 -0.0652,-0.54693 -0.0946,-0.83923 -0.17511,-1.74441 -0.30542,-3.81626 -0.37672,-6.02909 -0.18285,-5.67612 -0.29322,-5.86808 -0.63459,-6.62698 -0.74838,-1.66366 -2.65792,-3.64941 -4.38681,-4.49844 -1.41973,-0.69716 -0.72585,-0.45434 -1.20923,-0.51934 -0.47548,-0.0639 -2.54581,-0.13856 -6.47454,-0.14056 -0.0907,2.9929 -0.0862,4.81682 -0.58601,7.244 -0.28023,1.36071 -0.97957,3.42078 -2.40812,5.10356 -1.42519,1.67884 -2.81498,2.35811 -3.28145,2.61896 -3.14428,1.76375 -5.09549,2.43427 -9.41597,1.33997 -2.05224,-0.5197 -2.32631,-0.92288 -2.76159,-1.19527 -0.43528,-0.27239 -0.71007,-0.47684 -0.97461,-0.67593 -0.52909,-0.39816 -0.97871,-0.77171 -1.48622,-1.20664 -1.015,-0.86987 -2.20927,-1.95397 -3.6096,-3.26182 -2.80065,-2.61568 -6.38094,-6.09226 -10.18335,-9.90844 -6.19117,-6.21357 -9.5466,-9.59164 -11.7874,-12.16412 -1.1204,-1.28623 -2.03413,-2.38181 -2.90576,-4.03127 -0.87162,-1.64948 -1.40664,-4.21493 -1.40664,-5.61103 0,-1.4012 0.54783,-3.99366 1.42989,-5.64668 0.88206,-1.65304 1.8039,-2.74855 2.94142,-4.04679 2.27504,-2.59646 5.70131,-6.03358 12.03699,-12.369267 7.37691,-7.376888 10.87768,-11.090687 14.75208,-13.810527 1.45289,-1.019939 3.46378,-2.249133 6.08386,-2.580204 0.87337,-0.110323 1.8133,-0.120299 2.82412,0.0098 4.0433,0.520471 6.12413,2.832857 7.01973,3.728454 1.29782,1.297845 3.1373,4.826955 3.46852,7.049182 0.29817,2.00025 0.26393,3.770666 0.25993,6.212541 0.57954,0.0034 0.50388,0.0217 1.17564,0.0217 4.54211,0 8.44363,0.111537 11.991,0.50953 v -21.41004 c 0,-4.683115 -3.78355,-8.466667 -8.46667,-8.466667 h -8.46667 z m 0,101.599998 h 67.733335 v 16.93334 H 78.783305 c -4.683115,0 -8.466667,-3.78357 -8.466667,-8.46667 0,-4.68313 3.783552,-8.46667 8.466667,-8.46667 z" />
|
||||
<path
|
||||
style="color:#000000;fill:#000000;stroke-width:17.9;stroke-linejoin:round;-inkscape-stroke:none;paint-order:stroke markers fill"
|
||||
d="m 186.69094,157.95633 c 2.67243,-2.24871 7.17957,-9.39389 8.63888,-13.69528 1.03796,-3.05942 1.31928,-5.13546 1.33362,-9.84167 0.0278,-9.1246 -2.25302,-14.5915 -8.79325,-21.07662 -6.8535,-6.79576 -12.35348,-8.46107 -27.94423,-8.46107 -8.05417,0 -9.45684,-0.12924 -9.75203,-0.89852 -0.18964,-0.49417 -0.34479,-3.81715 -0.34479,-7.384389 0,-5.728497 -0.13266,-6.618534 -1.13607,-7.621956 -2.57777,-2.57775 -3.29907,-2.07141 -18.02212,12.651595 -12.64444,12.64444 -13.78771,13.94921 -13.78771,15.73575 0,1.78396 1.13629,3.08846 13.49078,15.48766 7.47518,7.50224 14.10644,13.69554 14.8715,13.88928 0.78576,0.19902 2.0096,-0.002 2.84016,-0.46789 1.42969,-0.80092 1.46523,-0.97351 1.74346,-8.46583 l 0.28402,-7.64825 h 8.52049 c 8.16738,0 8.65373,0.0655 11.73586,1.579 3.72428,1.82893 6.9202,5.12058 8.60236,8.86006 0.94352,2.09748 1.22898,4.1112 1.41901,10.01012 0.13083,4.06143 0.49647,7.70394 0.81253,8.09446 0.94895,1.17251 3.64241,0.80611 5.48753,-0.74645 z"
|
||||
id="path20297" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 704.00001 512"
|
||||
version="1.1"
|
||||
id="svg22127"
|
||||
sodipodi:docname="book-inside-cover.svg"
|
||||
width="704"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
inkscape:export-filename="InsideCover3.png"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.47274833"
|
||||
inkscape:cx="83.55397"
|
||||
inkscape:cy="178.74204"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
id="path2161-6"
|
||||
style="color:#000000;fill:#000000;stroke-width:1;-inkscape-stroke:none;paint-order:stroke fill markers"
|
||||
d="M 208,0 C 147.0078,0 94.429433,14.25071 60.367188,26.66992 23.520854,39.96036 0,76.16076 0,112.95896 v 317.8321 c 0,59.8499 56.949847,92.6546 107.47266,76.6035 l -0.1543,0.049 c 26.46715,-8.335 74.84649,-18.3965 100.68164,-18.3965 17.25807,0 61.31688,10.6183 85.14453,18.8438 l 0.0508,0.018 0.0527,0.018 c 19.82627,6.5858 40.84117,4.9222 58.99804,-3.0762 18.04267,7.8799 38.84257,9.6126 58.33594,3.1328 l 0.13672,-0.045 0.13672,-0.047 c 23.88445,-8.0588 67.88646,-18.8437 85.14453,-18.8437 25.83515,0 74.22549,10.0266 100.68164,18.3964 l 0.1543,0.049 0.15625,0.049 C 647.13371,523.05316 704,490.64216 704,430.79226 v -317.8321 c 0,-36.8274 -23.49583,-72.8235 -60.00977,-86.25583 l -0.16015,-0.0606 -0.16211,-0.0566 C 609.79193,14.33005 557.11269,0.0012 496,0.0012 434.5671,0.0012 387.12553,14.01354 352,34.94261 316.87446,14.01344 269.4331,0.0012 208,0.0012 Z m 0,32.00977 c 58.3999,0 103.40004,18.89469 123,33.63279 3.3,2.4564 5,6.4246 5,10.3926 v 356.5508 c 0,10.7702 -11.70041,18.2326 -22.40039,14.6426 -26.59996,-8.9751 -71.69966,-22.2012 -105.59961,-22.2012 -38.49993,0 -88.40045,11.4317 -119.900391,21.3516 C 76.799621,449.96896 64,442.03166 64,430.78906 V 80.94726 C 64,69.51586 70.799631,58.93546 82.099609,54.87306 110.29956,44.57516 157.50009,32.00977 208,32.00977 Z m 288,0 c 50.49991,0 97.70044,12.56619 125.90039,22.76949 C 633.20037,58.93616 640,69.51586 640,80.94726 v 349.8418 c 0,11.2426 -12.79963,19.0854 -24.09961,15.5899 -31.49995,-9.9199 -81.40046,-21.3516 -119.90039,-21.3516 -33.89995,0 -78.99966,13.2261 -105.59961,22.2012 C 379.60041,450.81856 368,443.35616 368,432.58596 V 76.03516 c 0,-3.968 1.60001,-7.9362 5,-10.3926 19.59997,-14.7381 64.6001,-33.63279 123,-33.63279 z M 335.52734,45.75386 c -0.1289,0.093 -0.23137,0.2032 -0.35937,0.2969 -0.198,0.1477 -0.428,0.2796 -0.625,0.4278 z m 33.67969,0.5372 0.24805,0.1875 c -0.0427,-0.033 -0.0937,-0.061 -0.13672,-0.094 -0.0393,-0.03 -0.0713,-0.064 -0.11133,-0.094 z" />
|
||||
<path
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;stroke-width:1;-inkscape-stroke:none"
|
||||
d="m 206.76992,184 c -36.98368,0 -73.07301,9.2343 -94.76923,16.9066 v 185.1887 c 27.62799,-7.7405 62.70503,-15.0804 94.76923,-15.0804 28.33376,0 58.16312,7.6425 81.23077,14.806 V 203.0154 C 273.60322,195.1776 243.44241,184 206.76992,184 Z"
|
||||
id="path4372-8"
|
||||
sodipodi:nodetypes="sccsccs" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:63.9999;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 255.99995,122.53007 c -31.8285,-15.342 -80.43462,-15.4137 -112,0"
|
||||
id="path2371-6"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 704.00001 512"
|
||||
version="1.1"
|
||||
id="svg22127"
|
||||
sodipodi:docname="book-part-cover.svg"
|
||||
width="704"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
inkscape:export-filename="InsideCover3.png"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.6685671"
|
||||
inkscape:cx="299.8951"
|
||||
inkscape:cy="80.021886"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
id="path2161-6"
|
||||
style="color:#000000;fill:#000000;stroke-width:1;-inkscape-stroke:none;paint-order:stroke fill markers"
|
||||
d="M 208,0 C 147.0078,0 94.429433,14.25071 60.367188,26.66992 23.520854,39.96036 0,76.16076 0,112.95896 v 317.8321 c 0,59.8499 56.949847,92.6546 107.47266,76.6035 l -0.1543,0.049 c 26.46715,-8.335 74.84649,-18.3965 100.68164,-18.3965 17.25807,0 61.31688,10.6183 85.14453,18.8438 l 0.0508,0.018 0.0527,0.018 c 19.82627,6.5858 40.84117,4.9222 58.99804,-3.0762 18.04267,7.8799 38.84257,9.6126 58.33594,3.1328 l 0.13672,-0.045 0.13672,-0.047 c 23.88445,-8.0588 67.88646,-18.8437 85.14453,-18.8437 25.83515,0 74.22549,10.0266 100.68164,18.3964 l 0.1543,0.049 0.15625,0.049 C 647.13371,523.05316 704,490.64216 704,430.79226 v -317.8321 c 0,-36.8274 -23.49583,-72.8235 -60.00977,-86.25583 l -0.16015,-0.0606 -0.16211,-0.0566 C 609.79193,14.33005 557.11269,0.0012 496,0.0012 434.5671,0.0012 387.12553,14.01354 352,34.94261 316.87446,14.01344 269.4331,0.0012 208,0.0012 Z m 0,32.00977 c 58.3999,0 103.40004,18.89469 123,33.63279 3.3,2.4564 5,6.4246 5,10.3926 v 356.5508 c 0,10.7702 -11.70041,18.2326 -22.40039,14.6426 -26.59996,-8.9751 -71.69966,-22.2012 -105.59961,-22.2012 -38.49993,0 -88.40045,11.4317 -119.900391,21.3516 C 76.799621,449.96896 64,442.03166 64,430.78906 V 80.94726 C 64,69.51586 70.799631,58.93546 82.099609,54.87306 110.29956,44.57516 157.50009,32.00977 208,32.00977 Z m 288,0 c 50.49991,0 97.70044,12.56619 125.90039,22.76949 C 633.20037,58.93616 640,69.51586 640,80.94726 v 349.8418 c 0,11.2426 -12.79963,19.0854 -24.09961,15.5899 -31.49995,-9.9199 -81.40046,-21.3516 -119.90039,-21.3516 -33.89995,0 -78.99966,13.2261 -105.59961,22.2012 C 379.60041,450.81856 368,443.35616 368,432.58596 V 76.03516 c 0,-3.968 1.60001,-7.9362 5,-10.3926 19.59997,-14.7381 64.6001,-33.63279 123,-33.63279 z M 335.52734,45.75386 c -0.1289,0.093 -0.23137,0.2032 -0.35937,0.2969 -0.198,0.1477 -0.428,0.2796 -0.625,0.4278 z m 33.67969,0.5372 0.24805,0.1875 c -0.0427,-0.033 -0.0937,-0.061 -0.13672,-0.094 -0.0393,-0.03 -0.0713,-0.064 -0.11133,-0.094 z" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 417.64553,213.53304 c 88.71546,-18.9285 95.50522,-18.6158 172.79707,0.054"
|
||||
id="path2371-8"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
id="path2315"
|
||||
style="stroke-width:67.6532;stroke-linejoin:bevel;paint-order:stroke markers fill;stop-color:#000000"
|
||||
inkscape:transform-center-x="-3.4164388e-06"
|
||||
inkscape:transform-center-y="-8.443352"
|
||||
d="m 505.27489,52.89544 25.98603,52.6535 58.10652,8.4434 -42.04628,40.985 9.92578,57.8717 -51.97205,-27.3234 -51.97204,27.3234 9.92578,-57.8717 -42.04627,-40.985 58.10651,-8.4434 z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
@@ -1,75 +0,0 @@
|
||||
.fac {
|
||||
display : inline-block;
|
||||
background-color : currentColor;
|
||||
mask-size : contain;
|
||||
mask-repeat : no-repeat;
|
||||
mask-position : center;
|
||||
width : 1em;
|
||||
aspect-ratio : 1;
|
||||
}
|
||||
.position-top-left {
|
||||
mask-image: url('../icons/position-top-left.svg');
|
||||
}
|
||||
.position-top-right {
|
||||
mask-image: url('../icons/position-top-right.svg');
|
||||
}
|
||||
.position-bottom-left {
|
||||
mask-image: url('../icons/position-bottom-left.svg');
|
||||
}
|
||||
.position-bottom-right {
|
||||
mask-image: url('../icons/position-bottom-right.svg');
|
||||
}
|
||||
.position-top {
|
||||
mask-image: url('../icons/position-top.svg');
|
||||
}
|
||||
.position-right {
|
||||
mask-image: url('../icons/position-right.svg');
|
||||
}
|
||||
.position-bottom {
|
||||
mask-image: url('../icons/position-bottom.svg');
|
||||
}
|
||||
.position-left {
|
||||
mask-image: url('../icons/position-left.svg');
|
||||
}
|
||||
.mask-edge {
|
||||
mask-image: url('../icons/mask-edge.svg');
|
||||
}
|
||||
.mask-corner {
|
||||
mask-image: url('../icons/mask-corner.svg');
|
||||
}
|
||||
.mask-center {
|
||||
mask-image: url('../icons/mask-center.svg');
|
||||
}
|
||||
.book-front-cover {
|
||||
mask-image: url('../icons/book-front-cover.svg');
|
||||
}
|
||||
.book-back-cover {
|
||||
mask-image: url('../icons/book-back-cover.svg');
|
||||
}
|
||||
.book-inside-cover {
|
||||
mask-image: url('../icons/book-inside-cover.svg');
|
||||
}
|
||||
.book-part-cover {
|
||||
mask-image: url('../icons/book-part-cover.svg');
|
||||
}
|
||||
.image-wrap-left {
|
||||
mask-image: url('../icons/image-wrap-left.svg');
|
||||
}
|
||||
.image-wrap-right {
|
||||
mask-image: url('../icons/image-wrap-right.svg');
|
||||
}
|
||||
.davek {
|
||||
mask-image: url('../icons/Davek.svg');
|
||||
}
|
||||
.rellanic {
|
||||
mask-image: url('../icons/Rellanic.svg');
|
||||
}
|
||||
.iokharic {
|
||||
mask-image: url('../icons/Iokharic.svg');
|
||||
}
|
||||
.zoom-to-fit {
|
||||
mask-image: url('../icons/zoom-to-fit.svg');
|
||||
}
|
||||
.fit-width {
|
||||
mask-image: url('../icons/fit-width.svg');
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.07509,0,0,1.07509,-3.75511,-3.75468)">
|
||||
<g transform="matrix(0.843549,0,0,0.950644,8.38004,4.39672)">
|
||||
<path d="M28.455,52.413L28.455,58.581C28.455,59.719 27.684,60.745 26.501,61.181C25.318,61.616 23.956,61.375 23.051,60.571L11.114,49.96C9.878,48.862 9.878,47.08 11.114,45.981L23.051,35.371C23.956,34.566 25.318,34.326 26.501,34.761C27.684,35.197 28.455,36.223 28.455,37.361L28.455,43.528L70.223,43.528L70.223,37.361C70.223,36.223 70.995,35.197 72.177,34.761C73.36,34.326 74.722,34.566 75.627,35.371L87.564,45.981C88.8,47.08 88.8,48.862 87.564,49.96L75.627,60.571C74.722,61.375 73.36,61.616 72.177,61.181C70.995,60.745 70.223,59.719 70.223,58.581L70.223,52.413L28.455,52.413Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1.46702,0,0,0.986488,-23.0335,3.50686)">
|
||||
<path d="M23.967,5.877L23.967,88.383C23.967,90.556 22.781,92.321 21.319,92.321L21.157,92.321C19.695,92.321 18.509,90.556 18.509,88.383L18.509,5.877C18.509,3.703 19.695,1.939 21.157,1.939L21.319,1.939C22.781,1.939 23.967,3.703 23.967,5.877Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1.46702,0,0,0.986488,60.7211,3.50686)">
|
||||
<path d="M23.967,5.877L23.967,88.383C23.967,90.556 22.781,92.321 21.319,92.321L21.157,92.321C19.695,92.321 18.509,90.556 18.509,88.383L18.509,5.877C18.509,3.703 19.695,1.939 21.157,1.939L21.319,1.939C22.781,1.939 23.967,3.703 23.967,5.877Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 512.00006 512"
|
||||
xml:space="preserve"
|
||||
id="svg10"
|
||||
sodipodi:docname="noun-wrap-image-left-212078.svg"
|
||||
width="512.00006"
|
||||
height="512"
|
||||
inkscape:export-filename="image-wrap-right.svg"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs10" /><sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 185.80018,144 H 32"
|
||||
id="path11"
|
||||
sodipodi:nodetypes="cc"
|
||||
clip-path="none"
|
||||
inkscape:export-filename="image-wrap-right.svg"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 185.80018,368 H 32"
|
||||
id="path11-8"
|
||||
sodipodi:nodetypes="cc"
|
||||
clip-path="none" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 480.00007,32 H 32"
|
||||
id="path11-8-2-67"
|
||||
clip-path="none"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 480.00008,480 H 32"
|
||||
id="path11-8-2-67-2"
|
||||
clip-path="none"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 160.0001,255.98832 32,256.01162"
|
||||
id="path11-0"
|
||||
sodipodi:nodetypes="cc"
|
||||
clip-path="none" /><path
|
||||
id="path23"
|
||||
style="opacity:0.922046;fill:#000000;fill-opacity:1;stroke-width:64;stroke-linecap:round;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||
d="m 416.00008,96 a 160,160 0 0 1 96,32.50977 v 254.98046 a 160,160 0 0 1 -96,32.50977 160,160 0 0 1 -160,-160 160,160 0 0 1 160,-160 z" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 512.00006 512"
|
||||
xml:space="preserve"
|
||||
id="svg10"
|
||||
sodipodi:docname="noun-wrap-image-left-212078.svg"
|
||||
width="512.00006"
|
||||
height="512"
|
||||
inkscape:export-filename="image-wrap-right.svg"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs10" /><sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 326.1999,144 H 480.00008"
|
||||
id="path11"
|
||||
sodipodi:nodetypes="cc"
|
||||
clip-path="none"
|
||||
inkscape:export-filename="image-wrap-right.svg"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 326.1999,368 H 480.00008"
|
||||
id="path11-8"
|
||||
sodipodi:nodetypes="cc"
|
||||
clip-path="none" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 32.00001,32 H 480.00008"
|
||||
id="path11-8-2-67"
|
||||
clip-path="none"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 32,480 H 480.00008"
|
||||
id="path11-8-2-67-2"
|
||||
clip-path="none"
|
||||
sodipodi:nodetypes="cc" /><path
|
||||
style="fill:none;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 351.99998,255.98832 128.0001,0.0233"
|
||||
id="path11-0"
|
||||
sodipodi:nodetypes="cc"
|
||||
clip-path="none" /><path
|
||||
id="path23"
|
||||
style="opacity:0.922046;fill:#000000;fill-opacity:1;stroke-width:64;stroke-linecap:round;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||
d="M 96,96 A 160,160 0 0 0 0,128.50977 V 383.49023 A 160,160 0 0 0 96,416 160,160 0 0 0 256,256 160,160 0 0 0 96,96 Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="mask-center.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#Strips1_1"
|
||||
id="pattern3077"
|
||||
patternTransform="matrix(23.13193,-23.131931,19.25517,19.25517,18.091544,-20.306833)" /><pattern
|
||||
inkscape:collect="always"
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="2"
|
||||
height="1"
|
||||
patternTransform="translate(0,0) scale(10,10)"
|
||||
id="Strips1_1"
|
||||
inkscape:stockid="Stripes 1:1"><rect
|
||||
style="fill:black;stroke:none"
|
||||
x="0"
|
||||
y="-0.5"
|
||||
width="1"
|
||||
height="2"
|
||||
id="rect2097" /></pattern></defs><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.67711183"
|
||||
inkscape:cx="31.75251"
|
||||
inkscape:cy="260.66595"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,-5.2e-6 C 21.40803,-5.2e-6 1.98e-5,21.408025 1.98e-5,47.999995 V 464 C 1.98e-5,490.59197 21.40803,512 48,512 h 352 c 26.59198,0 48,-21.40803 48,-48 V 47.999995 C 448,21.408025 426.59198,-5.2e-6 400,-5.2e-6 Z M 64,63.999995 H 384 V 448 H 64 Z" /><rect
|
||||
style="fill:url(#pattern3077);fill-opacity:1;stroke:#000000;stroke-width:48;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="176"
|
||||
height="240"
|
||||
x="136.00002"
|
||||
y="136"
|
||||
rx="48"
|
||||
ry="48" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="mask-corner.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#Strips1_1"
|
||||
id="pattern3077"
|
||||
patternTransform="matrix(23.131931,-23.131931,19.25517,19.25517,26.214281,-26.952711)" /><pattern
|
||||
inkscape:collect="always"
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="2"
|
||||
height="1"
|
||||
patternTransform="translate(0,0) scale(10,10)"
|
||||
id="Strips1_1"
|
||||
inkscape:stockid="Stripes 1:1"><rect
|
||||
style="fill:black;stroke:none"
|
||||
x="0"
|
||||
y="-0.5"
|
||||
width="1"
|
||||
height="2"
|
||||
id="rect2097" /></pattern></defs><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.95758074"
|
||||
inkscape:cx="275.17262"
|
||||
inkscape:cy="306.50157"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,-5.2e-6 C 21.40803,-5.2e-6 1.98e-5,21.408025 1.98e-5,47.999995 V 464 C 1.98e-5,490.59197 21.40803,512 48,512 h 352 c 26.59198,0 48,-21.40803 48,-48 V 47.999995 C 448,21.408025 426.59198,-5.2e-6 400,-5.2e-6 Z M 64,63.999995 H 384 V 448 H 64 Z" /><rect
|
||||
style="fill:url(#pattern3077);fill-opacity:1;stroke:#000000;stroke-width:48;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="32.000011"
|
||||
y="32.000011"
|
||||
rx="48"
|
||||
ry="48" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,69 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="mask-edge.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#Strips1_1"
|
||||
id="pattern3077"
|
||||
patternTransform="matrix(23.131931,-23.13193,19.25517,19.25517,26.214281,-26.952711)" /><pattern
|
||||
inkscape:collect="always"
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="2"
|
||||
height="1"
|
||||
patternTransform="translate(0,0) scale(10,10)"
|
||||
id="Strips1_1"
|
||||
inkscape:stockid="Stripes 1:1"><rect
|
||||
style="fill:black;stroke:none"
|
||||
x="0"
|
||||
y="-0.5"
|
||||
width="1"
|
||||
height="2"
|
||||
id="rect2097" /></pattern></defs><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.95758074"
|
||||
inkscape:cx="231.31209"
|
||||
inkscape:cy="171.78708"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,-5.2e-6 C 21.40803,-5.2e-6 1.98e-5,21.408025 1.98e-5,47.999995 V 464 C 1.98e-5,490.59197 21.40803,512 48,512 h 352 c 26.59198,0 48,-21.40803 48,-48 V 47.999995 C 448,21.408025 426.59198,-5.2e-6 400,-5.2e-6 Z M 64,63.999995 H 384 V 448 H 64 Z" /><rect
|
||||
style="fill:url(#pattern3077);fill-opacity:1;stroke:#000000;stroke-width:48;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="447.99997"
|
||||
x="32.000011"
|
||||
y="32.000011"
|
||||
rx="48"
|
||||
ry="48" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:47.9999;stroke-linejoin:round;stroke-dasharray:none;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect4640"
|
||||
width="48"
|
||||
height="512"
|
||||
x="216"
|
||||
y="0" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-bottom-left.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 48,511.99998 c -26.59197,0 -48.00000035682677,-21.40803 -48.00000035682677,-48 v -416 C -3.5682677e-7,21.40801 21.40803,-1.9692461e-5 48,-1.9692461e-5 h 352 c 26.59198,0 48,21.408029692461 48,47.999999692461 v 416 c 0,26.59197 -21.40802,48 -48,48 z m 16,-64 h 320 v -384 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-3.5682677e-07"
|
||||
y="-512"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(1,-1)" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-bottom-right.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 400,511.99998 c 26.59197,0 48,-21.40803 48,-48 v -416 C 448,21.40801 426.59197,-1.9692461e-5 400,-1.9692461e-5 H 48 C 21.40802,-1.9692461e-5 -3.5682677e-7,21.40801 -3.5682677e-7,47.99998 v 416 c 0,26.59197 21.40802035682677,48 48.00000035682677,48 z m -16,-64 H 64 v -384 h 320 z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-448"
|
||||
y="-512"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(-1)" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-bottom.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="1.0011513"
|
||||
inkscape:cx="273.18549"
|
||||
inkscape:cy="216.25103"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 48,512.00004 c -26.5919,0 -48,-21.4081 -48,-48 V 47.999996 C 0,21.408026 21.4081,-3.8146973e-6 48,-3.8146973e-6 h 352 c 26.592,0 48,21.4080298146973 48,47.9999998146973 V 464.00004 c 0,26.5919 -21.408,48 -48,48 z m 16,-64 H 384 V 63.999996 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30.0001;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-8"
|
||||
width="447.99997"
|
||||
height="240"
|
||||
x="1.40625e-05"
|
||||
y="-512.00006"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(1,-1)" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-left.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="164.56642"
|
||||
inkscape:cy="243.6713"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,0 C 21.4081,0 0,21.40803 0,48 v 416 c 0,26.59197 21.4081,48 48,48 h 352.0001 c 26.5919,0 48,-21.40803 48,-48 V 48 c 0,-26.59197 -21.4081,-48 -48,-48 z M 64,64 H 384.0001 V 448 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-2"
|
||||
width="208"
|
||||
height="512.00006"
|
||||
x="7.0762391e-05"
|
||||
y="-8.8710935e-05"
|
||||
rx="48"
|
||||
ry="48.000004" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-right.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="164.56642"
|
||||
inkscape:cy="243.6713"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-0"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 400.0001,0 c 26.5919,0 48,21.40803 48,48 v 416 c 0,26.59197 -21.4081,48 -48,48 H 48 C 21.4081,512 0,490.59197 0,464 V 48 C 0,21.40803 21.4081,0 48,0 Z m -16,64 H 64 v 384 h 320.0001 z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-2"
|
||||
width="208"
|
||||
height="512.00006"
|
||||
x="-448.00003"
|
||||
y="-8.8710935e-05"
|
||||
rx="48"
|
||||
ry="48.000004"
|
||||
transform="scale(-1,1)" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-top-left.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,0 C 21.40803,0 0,21.40803 0,48 v 416 c 0,26.59197 21.40803,48 48,48 h 352 c 26.59198,0 48,-21.40803 48,-48 V 48 C 448,21.40803 426.59198,0 400,0 Z M 64,64 H 384 V 448 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-3.5682677e-07"
|
||||
y="-1.9692461e-05"
|
||||
rx="48"
|
||||
ry="48" /></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-top-right.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="0.70792086"
|
||||
inkscape:cx="174.45453"
|
||||
inkscape:cy="325.60137"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="m 400,0 c 26.59197,0 48,21.40803 48,48 v 416 c 0,26.59197 -21.40803,48 -48,48 H 48 C 21.40802,512 -3.5682677e-7,490.59197 -3.5682677e-7,464 V 48 C -3.5682677e-7,21.40803 21.40802,0 48,0 Z M 384,64 H 64 v 384 h 320 z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206"
|
||||
width="208"
|
||||
height="240"
|
||||
x="-448"
|
||||
y="-1.9692461e-05"
|
||||
rx="48"
|
||||
ry="48"
|
||||
transform="scale(-1,1)" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg135"
|
||||
sodipodi:docname="position-top.svg"
|
||||
width="448"
|
||||
height="512"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs139" /><sodipodi:namedview
|
||||
id="namedview137"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="false"
|
||||
inkscape:zoom="1.0011513"
|
||||
inkscape:cx="273.18549"
|
||||
inkscape:cy="216.25103"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg135" /><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path
|
||||
id="rect12201-2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
d="M 48,0 C 21.4081,0 0,21.4081 0,48 v 416.00004 c 0,26.59197 21.4081,48 48,48 h 352 c 26.592,0 48,-21.40803 48,-48 V 48 C 448,21.4081 426.592,0 400,0 Z M 64,64 H 384 V 448.00004 H 64 Z" /><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:30.0001;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke;stop-color:#000000"
|
||||
id="rect12206-8"
|
||||
width="447.99997"
|
||||
height="240"
|
||||
x="1.40625e-05"
|
||||
y="-3.8146973e-06"
|
||||
rx="48"
|
||||
ry="48" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.0781,0,0,1.0781,-3.90545,-3.90502)">
|
||||
<g transform="matrix(0.841196,0,0,0.947993,8.49652,4.52391)">
|
||||
<path d="M44.333,52.413L28.455,52.413L28.455,58.581C28.455,59.719 27.684,60.745 26.501,61.181C25.318,61.616 23.956,61.375 23.051,60.571L11.114,49.96C9.878,48.862 9.878,47.08 11.114,45.981L23.051,35.371C23.956,34.566 25.318,34.326 26.501,34.761C27.684,35.197 28.455,36.223 28.455,37.361L28.455,43.528L44.333,43.528L44.333,29.439L37.382,29.439C36.099,29.439 34.943,28.755 34.452,27.705C33.961,26.656 34.233,25.448 35.14,24.644L47.097,14.052C48.335,12.956 50.343,12.956 51.581,14.052L63.539,24.644C64.446,25.448 64.717,26.656 64.226,27.705C63.735,28.755 62.579,29.439 61.296,29.439L54.346,29.439L54.346,43.528L70.223,43.528L70.223,37.361C70.223,36.223 70.995,35.197 72.177,34.761C73.36,34.326 74.722,34.566 75.627,35.371L87.564,45.981C88.8,47.08 88.8,48.862 87.564,49.96L75.627,60.571C74.722,61.375 73.36,61.616 72.177,61.181C70.995,60.745 70.223,59.719 70.223,58.581L70.223,52.413L54.346,52.413L54.346,66.502L61.296,66.502C62.579,66.502 63.735,67.187 64.226,68.236C64.717,69.286 64.446,70.494 63.539,71.297L51.581,81.889C50.343,82.986 48.335,82.986 47.097,81.889L35.14,71.297C34.233,70.494 33.961,69.286 34.452,68.236C34.943,67.187 36.099,66.502 37.382,66.502L44.333,66.503L44.333,52.413Z"/>
|
||||
</g>
|
||||
<g transform="matrix(1.0247,0,0,1.0247,-5.47698,-3.53855)">
|
||||
<path d="M99.4,14.269L99.4,90.227C99.4,94.245 96.137,97.508 92.119,97.508L16.161,97.508C12.142,97.508 8.88,94.245 8.88,90.227L8.88,14.269C8.88,10.25 12.142,6.988 16.161,6.988L92.119,6.988C96.137,6.988 99.4,10.25 99.4,14.269ZM93.633,14.269C93.633,13.433 92.955,12.755 92.119,12.755L16.161,12.755C15.325,12.755 14.647,13.433 14.647,14.269L14.647,90.227C14.647,91.062 15.325,91.741 16.161,91.741L92.119,91.741C92.955,91.741 93.633,91.062 93.633,90.227L93.633,14.269Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -11,10 +11,9 @@ const template = async function(name, title='', props = {}){
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" />
|
||||
<link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
|
||||
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link href=${`/${name}/bundle.css`} type="text/css" rel='stylesheet' />
|
||||
<link href=${`/${name}/bundle.css`} rel='stylesheet' />
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />
|
||||
${ogMetaTags}
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"secret" : "secret",
|
||||
"web_port" : 8000,
|
||||
"enable_v3" : true,
|
||||
"enable_themes" : true,
|
||||
"local_environments" : ["docker", "local"],
|
||||
"publicUrl" : "https://homebrewery.naturalcrit.com"
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import react from "eslint-plugin-react";
|
||||
import jest from "eslint-plugin-jest";
|
||||
import globals from "globals";
|
||||
|
||||
export default [{
|
||||
ignores: ["build/"]
|
||||
},
|
||||
{
|
||||
files : ['**/*.js', '**/*.jsx'],
|
||||
plugins : { react, jest },
|
||||
languageOptions : {
|
||||
ecmaVersion : "latest",
|
||||
sourceType : "module",
|
||||
parserOptions : { ecmaFeatures: { jsx: true } },
|
||||
globals : { ...globals.browser, ...globals.node }
|
||||
},
|
||||
rules: {
|
||||
/** Errors **/
|
||||
"camelcase" : ["error", { properties: "never" }],
|
||||
"no-array-constructor" : "error",
|
||||
"no-iterator" : "error",
|
||||
"no-nested-ternary" : "error",
|
||||
"no-new-object" : "error",
|
||||
"no-proto" : "error",
|
||||
"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", { max: 200, skipComments: true, skipBlankLines: true }],
|
||||
"max-depth" : ["warn", { max: 4 }],
|
||||
"max-params" : ["warn", { max: 5 }],
|
||||
"no-restricted-syntax" : ["warn", "ClassDeclaration", "SwitchStatement"],
|
||||
"no-unused-vars" : ["warn", { vars: "all", args: "none", varsIgnorePattern: "config|_|cx|createClass" }],
|
||||
"react/jsx-uses-vars" : "warn",
|
||||
|
||||
/** Fixable **/
|
||||
"arrow-parens" : ["warn", "always"],
|
||||
"brace-style" : ["warn", "1tbs", { allowSingleLine: true }],
|
||||
"jsx-quotes" : ["warn", "prefer-single"],
|
||||
"no-var" : "warn",
|
||||
"prefer-const" : "warn",
|
||||
"prefer-template" : "warn",
|
||||
"quotes" : ["warn", "single", { allowTemplateLiterals: true }],
|
||||
"semi" : ["warn", "always"],
|
||||
|
||||
/** Whitespace **/
|
||||
"array-bracket-spacing" : ["warn", "never"],
|
||||
"arrow-spacing" : ["warn", { before: false, after: false }],
|
||||
"comma-spacing" : ["warn", { before: false, after: true }],
|
||||
"indent" : ["warn", "tab", { MemberExpression: "off" }],
|
||||
"linebreak-style" : "off",
|
||||
"no-trailing-spaces" : "warn",
|
||||
"no-whitespace-before-property" : "warn",
|
||||
"object-curly-spacing" : ["warn", "always"],
|
||||
"react/jsx-indent-props" : ["warn", "tab"],
|
||||
"space-in-parens" : ["warn", "never"],
|
||||
"template-curly-spacing" : ["warn", "never"],
|
||||
"keyword-spacing" : ["warn", {
|
||||
before : true,
|
||||
after : true,
|
||||
overrides : { if: { before: false, after: false } }
|
||||
}],
|
||||
"key-spacing" : ["warn", {
|
||||
multiLine : { beforeColon: true, afterColon: true, align: "colon" },
|
||||
singleLine : { beforeColon: false, afterColon: true }
|
||||
}]
|
||||
}
|
||||
}
|
||||
];
|
||||
29
faq.md
@@ -62,13 +62,16 @@ pre {
|
||||
```
|
||||
|
||||
# FAQ
|
||||
{{wide Updated Apr. 15, 2023}}
|
||||
{{wide Updated Oct. 11, 2021}}
|
||||
|
||||
|
||||
### The site is down for me! Anyone else?
|
||||
|
||||
You can check the site status here: [Everyone or Just Me](https://downforeveryoneorjustme.com/homebrewery.naturalcrit.com)
|
||||
|
||||
### How do I log out?
|
||||
|
||||
Go to https://homebrewery.naturalcrit.com/login, and hit the "*logout*" link.
|
||||
|
||||
### Why am I getting an error when trying to save, and my account is linked to Google?
|
||||
|
||||
@@ -102,7 +105,7 @@ The best way to avoid this is to leave space at the end of a column equal to one
|
||||
|
||||
### Why do I need to manually create a new page? Why doesn't text flow between pages?
|
||||
|
||||
A Homebrewery document is at its core an HTML & CSS document, and currently limited by the specs of those technologies. It is currently not possible to flow content from inside one box ("page") to the inside of another box. It seems likely that someday CSS will add this capability, and if/when that happens, Homebrewery will adopt it as soon as possible.
|
||||
A Homebrewery document is at it's core an HTML & CSS document, and currently limited by the specs of those technologies. It is currently not possible to flow content from inside one box ("page") to the inside of another box. It seems likely that someday CSS will add this capability, and if/when that happens, Homebrewery will adopt it as soon as possible.
|
||||
|
||||
### Where do I get images?
|
||||
The Homebrewery does not provide images for use besides some page elements and example images for snippets. You will need to find your own images for use and be sure you are following the appropriate license requirements.
|
||||
@@ -117,6 +120,26 @@ The fonts used were originally created for use with the English language, though
|
||||
### Whenever I click on the "Get PDF" button, instead of getting a download, it opens Print Preview in another tab.
|
||||
Yes, this is by design. In the print preview, select "Save as PDF" as the Destination, and then click "Save". There will be a normal download dialog where you can save your brew as a PDF.
|
||||
|
||||
### The preview window is suddenly gone, I can only see the editor side of the Homebrewery (or the other way around).
|
||||
|
||||
1. Press `CTRL`+`SHIFT`+`i` (or right-click and select "Inspect") while in the Homebrewery.
|
||||
|
||||
2. Expand...
|
||||
```
|
||||
- `body`
|
||||
- `main`
|
||||
- `div class="homebrew"`
|
||||
- `div class="editPage page"`
|
||||
- `div class="content"`
|
||||
- `div class="splitPane"`
|
||||
```
|
||||
|
||||
There you will find 3 divs: `div class="pane" [...]`, `div class="divider" [...]`, and `div class="pane" [...]`.
|
||||
|
||||
The `class="pane"` looks similar to this: `div class="pane" data-reactid="36" style="flex: 0 0 auto; width: 925px;"`.
|
||||
|
||||
Change whatever stands behind width: to something smaller than your display width.
|
||||
|
||||
### I have white borders on the bottom/sides of the print preview.
|
||||
|
||||
The Homebrewery paper size and your print paper size do not match.
|
||||
@@ -126,4 +149,4 @@ The Homebrewery defaults to creating US Letter page sizes. If you are printing
|
||||
|
||||
### Typing `#### Adhesion` in the text editor doesn't show the header at all in the completed page?
|
||||
|
||||
Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.
|
||||
Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.
|
||||
@@ -1,43 +0,0 @@
|
||||
# Windows Installation Instructions
|
||||
|
||||
## Before Installing
|
||||
|
||||
These instructions assume that you are installing to a completely new, fresh Windows 10 installation. As such, some steps may not be necessary if you are installing to an existing Windows 10 instance.
|
||||
|
||||
## Installation instructions
|
||||
|
||||
1. Download the installation script from https://raw.githubusercontent.com/naturalcrit/homebrewery/master/install/windows/install.ps1.
|
||||
|
||||
2. Run Powershell as an Administrator.
|
||||
a. Click the Start menu or press the Windows key.
|
||||
b. Type `powershell` into the Search box.
|
||||
c. Right click on the Powershell app and select "Run As Administrator".
|
||||
d. Click YES in the prompt that appears.
|
||||
|
||||
3. Change the script execution policy.
|
||||
a. Run the Powershell command `Set-ExecutionPolicy Bypass -Scope Process`.
|
||||
b. Allow the change to be made - press Y at the prompt that appears.
|
||||
|
||||
4. Run the installation script.
|
||||
a. Navigate to the location of the script, e.g. `cd C:\Users\ExampleUser\Downloads`.
|
||||
b. Start the script - `.\install.ps1`
|
||||
|
||||
5. Once the script has completed, it will start the Homebrewery server. This will normally cause a Network Access prompt for NodeJS - if this appears, click "Allow".
|
||||
|
||||
**NOTE:** At this time, the script **ONLY** installs HomeBrewery. It does **NOT** install the NaturalCrit login system, as that is currently a completely separate project.
|
||||
|
||||
---
|
||||
|
||||
### Testing
|
||||
|
||||
These installation instructions have been tested on the following Ubuntu releases:
|
||||
|
||||
- *Windows 10 Home - OS Build 19045.2546*
|
||||
|
||||
## Final Notes
|
||||
|
||||
While this installation process works successfully at the time of writing (January 23, 2023), it relies on all of the Node.JS packages used in the HomeBrewery project retaining their cross-platform capabilities to continue to function. This is one of the inherent advantages of Node.JS, but it is by no means guaranteed and as such, functionality or even installation may fail without warning at some point in the future.
|
||||
|
||||
Regards,
|
||||
G
|
||||
January 23, 2023
|
||||