mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-27 03:03:09 +00:00
Compare commits
140 Commits
v3.12.0
...
calculusch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f23959bb05 | ||
|
|
228041913e | ||
|
|
fc53989946 | ||
|
|
9f72dc08c6 | ||
|
|
1018ba554f | ||
|
|
709c9ece74 | ||
|
|
19e51102d2 | ||
|
|
68a68bde82 | ||
|
|
e1186b4a1e | ||
|
|
9e71945a76 | ||
|
|
9f6fc3d1ac | ||
|
|
e22830eb4a | ||
|
|
f02a3d815a | ||
|
|
7097897df8 | ||
|
|
9247967f93 | ||
|
|
1b3d82fc04 | ||
|
|
810934f2c1 | ||
|
|
3b507a1fb9 | ||
|
|
4f1056a320 | ||
|
|
963aa8f003 | ||
|
|
431dfd7780 | ||
|
|
4f0cbd82d4 | ||
|
|
53e437c6bc | ||
|
|
0e5c91733d | ||
|
|
f5c729c328 | ||
|
|
4f9e93fac7 | ||
|
|
7cc83eaf95 | ||
|
|
22b01b131f | ||
|
|
2045bf8060 | ||
|
|
df73b37180 | ||
|
|
99f9b10348 | ||
|
|
ed85f77c48 | ||
|
|
777438fd94 | ||
|
|
08406de5cc | ||
|
|
e1599909bc | ||
|
|
e7eda1f5ec | ||
|
|
9f2aaf01c7 | ||
|
|
882c78fbb5 | ||
|
|
a79b2fb755 | ||
|
|
97fba241a1 | ||
|
|
d0fbca7af5 | ||
|
|
36726c747c | ||
|
|
74580e63d6 | ||
|
|
1d8eb35c64 | ||
|
|
659472578b | ||
|
|
9ec549b496 | ||
|
|
e37c190600 | ||
|
|
d51d7efdcf | ||
|
|
b0a16c8daf | ||
|
|
beb86c1820 | ||
|
|
48f8026c35 | ||
|
|
1be0a2dac3 | ||
|
|
d31e495d07 | ||
|
|
3b2a48eabf | ||
|
|
4bc76a0766 | ||
|
|
3faa23c6eb | ||
|
|
e324de8f4f | ||
|
|
7954ae8692 | ||
|
|
85a00b508b | ||
|
|
1e91d7256c | ||
|
|
8a15172db1 | ||
|
|
aae574e4e5 | ||
|
|
45106b47d4 | ||
|
|
59b3038b9b | ||
|
|
31cf8b7d28 | ||
|
|
9f6bc10369 | ||
|
|
f7c7d40195 | ||
|
|
380e3fead3 | ||
|
|
ffa01c7f1d | ||
|
|
15367f9444 | ||
|
|
90a710907e | ||
|
|
da6d06728c | ||
|
|
451ff3ffec | ||
|
|
61038f876d | ||
|
|
20e1c71eff | ||
|
|
d2d7f5b71e | ||
|
|
0ad4cb7cfd | ||
|
|
161efbb3c8 | ||
|
|
b35739c5c1 | ||
|
|
831c635149 | ||
|
|
d961e7695d | ||
|
|
4b842ef37f | ||
|
|
0396bedcd0 | ||
|
|
772b478682 | ||
|
|
dab8dd278d | ||
|
|
72d26c6c7e | ||
|
|
a7f07ab9f5 | ||
|
|
75080135af | ||
|
|
7a9483c0d0 | ||
|
|
484c6bb8a7 | ||
|
|
125adfb198 | ||
|
|
a0c6e92016 | ||
|
|
7de58740a2 | ||
|
|
bae56b8b9d | ||
|
|
e02d925a49 | ||
|
|
03f868d084 | ||
|
|
920c4cd7cb | ||
|
|
bf2c638cad | ||
|
|
eddb513e72 | ||
|
|
fea68ac71a | ||
|
|
97b10c685c | ||
|
|
40fc422ab5 | ||
|
|
647afba2b0 | ||
|
|
bd324a7e74 | ||
|
|
ac080c8323 | ||
|
|
30ebf90371 | ||
|
|
f9a7adbd72 | ||
|
|
b74fb22182 | ||
|
|
b6ea89356b | ||
|
|
37488ded4d | ||
|
|
c47dd828ed | ||
|
|
a34ed8ccb4 | ||
|
|
d4d0546d61 | ||
|
|
e94c1d33d2 | ||
|
|
dd6388bf9f | ||
|
|
8b8071a903 | ||
|
|
19746a78f4 | ||
|
|
13d679c4bf | ||
|
|
a851378a2f | ||
|
|
7f168f35b8 | ||
|
|
21c0916693 | ||
|
|
8f0fb6e458 | ||
|
|
4606ad4c6e | ||
|
|
5a410029f6 | ||
|
|
b4fec32320 | ||
|
|
d0000cee11 | ||
|
|
846b3b9d02 | ||
|
|
54d881642d | ||
|
|
beaf67c975 | ||
|
|
5b35d1169d | ||
|
|
0d1d3a180d | ||
|
|
df3db14e8b | ||
|
|
1c7d2740f3 | ||
|
|
802da2920b | ||
|
|
453656fbeb | ||
|
|
70657c16d1 | ||
|
|
3d5f99adae | ||
|
|
a889fa657e | ||
|
|
4347debf45 | ||
|
|
dc3243ae59 |
@@ -64,6 +64,12 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Test - Mustache Spans
|
name: Test - Mustache Spans
|
||||||
command: npm run test:mustache-syntax
|
command: npm run test:mustache-syntax
|
||||||
|
- run:
|
||||||
|
name: Test - Definition Lists
|
||||||
|
command: npm run test:definition-lists
|
||||||
|
- run:
|
||||||
|
name: Test - Variables
|
||||||
|
command: npm run test:variables
|
||||||
- run:
|
- run:
|
||||||
name: Test - Routes
|
name: Test - Routes
|
||||||
command: npm run test:route
|
command: npm run test:route
|
||||||
|
|||||||
103
.github/workflows/limit-pull-requests.yml
vendored
Normal file
103
.github/workflows/limit-pull-requests.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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
|
||||||
25
.github/workflows/pr-check.yml
vendored
Normal file
25
.github/workflows/pr-check.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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 == 'Homebrew'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: Homebrew/actions/limit-pull-requests@master
|
||||||
|
with:
|
||||||
|
except-users: |
|
||||||
|
dependabot
|
||||||
|
comment-limit: 1
|
||||||
|
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
|
||||||
@@ -14,6 +14,28 @@
|
|||||||
box-shadow : 1px 4px 14px #000000;
|
box-shadow : 1px 4px 14px #000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.pane { position : relative; }
|
.pane { position : relative; }
|
||||||
.pageInfo {
|
.pageInfo {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const Editor = createClass({
|
|||||||
updateEditorSize : function() {
|
updateEditorSize : function() {
|
||||||
if(this.refs.codeEditor) {
|
if(this.refs.codeEditor) {
|
||||||
let paneHeight = this.refs.main.parentNode.clientHeight;
|
let paneHeight = this.refs.main.parentNode.clientHeight;
|
||||||
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
paneHeight -= SNIPPETBAR_HEIGHT;
|
||||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -130,6 +130,8 @@
|
|||||||
height : 1.2em;
|
height : 1.2em;
|
||||||
margin-right : 8px;
|
margin-right : 8px;
|
||||||
font-size : 1.2em;
|
font-size : 1.2em;
|
||||||
|
min-width: 25px;
|
||||||
|
text-align: center;
|
||||||
& ~ i {
|
& ~ i {
|
||||||
margin-right : 0;
|
margin-right : 0;
|
||||||
margin-left : 5px;
|
margin-left : 5px;
|
||||||
@@ -138,7 +140,7 @@
|
|||||||
&.font {
|
&.font {
|
||||||
height : auto;
|
height : auto;
|
||||||
&::before {
|
&::before {
|
||||||
font-size : 1.4em;
|
font-size : 1em;
|
||||||
content : 'ABC';
|
content : 'ABC';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const Homebrew = createClass({
|
|||||||
<Route path='/print' element={<WithRoute el={PrintPage} />} />
|
<Route path='/print' element={<WithRoute el={PrintPage} />} />
|
||||||
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||||
<Route path='/faq' 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='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} accountDetails={this.props.brew.accountDetails} />} />
|
||||||
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||||
<Route path='/error' element={<WithRoute el={ErrorPage} 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} />} />
|
||||||
|
|||||||
@@ -15,6 +15,23 @@
|
|||||||
}
|
}
|
||||||
&.listPage .content {
|
&.listPage .content {
|
||||||
overflow-y : scroll;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,102 +1,82 @@
|
|||||||
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 moment = require('moment');
|
||||||
|
|
||||||
const UIPage = require('../basePages/uiPage/uiPage.jsx');
|
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');
|
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||||
|
|
||||||
let SAVEKEY = '';
|
let SAVEKEY = '';
|
||||||
|
|
||||||
const AccountPage = createClass({
|
const AccountPage = (props)=>{
|
||||||
displayName : 'AccountPage',
|
// destructure props and set state for save location
|
||||||
getDefaultProps : function() {
|
const { accountDetails, brew } = props;
|
||||||
return {
|
const [saveLocation, setSaveLocation] = React.useState('');
|
||||||
brew : {},
|
|
||||||
uiItems : {}
|
// initialize save location from local storage based on user id
|
||||||
};
|
React.useEffect(()=>{
|
||||||
},
|
if(!saveLocation && accountDetails.username) {
|
||||||
getInitialState : function() {
|
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${accountDetails.username}`;
|
||||||
return {
|
// if no SAVEKEY in local storage, default save location to Google Drive if user has Google account.
|
||||||
uiItems : this.props.uiItems
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount : function(){
|
|
||||||
if(!this.state.saveLocation && this.props.uiItems.username) {
|
|
||||||
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${this.props.uiItems.username}`;
|
|
||||||
let saveLocation = window.localStorage.getItem(SAVEKEY);
|
let saveLocation = window.localStorage.getItem(SAVEKEY);
|
||||||
saveLocation = saveLocation ?? (this.state.uiItems.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY');
|
saveLocation = saveLocation ?? (accountDetails.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY');
|
||||||
this.makeActive(saveLocation);
|
setActiveSaveLocation(saveLocation);
|
||||||
}
|
}
|
||||||
},
|
}, []);
|
||||||
|
|
||||||
makeActive : function(newSelection){
|
const setActiveSaveLocation = (newSelection)=>{
|
||||||
if(this.state.saveLocation == newSelection) return;
|
if(saveLocation === newSelection) return;
|
||||||
window.localStorage.setItem(SAVEKEY, newSelection);
|
window.localStorage.setItem(SAVEKEY, newSelection);
|
||||||
this.setState({
|
setSaveLocation(newSelection);
|
||||||
saveLocation : newSelection
|
};
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderButton : function(name, key, shouldRender=true){
|
// todo: should this be a set of radio buttons (well styled) since it's either/or choice?
|
||||||
if(!shouldRender) return;
|
const renderSaveLocationButton = (name, key, shouldRender = true)=>{
|
||||||
return <button className={this.state.saveLocation==key ? 'active' : ''} onClick={()=>{this.makeActive(key);}}>{name}</button>;
|
if(!shouldRender) return null;
|
||||||
},
|
return (
|
||||||
|
<button className={saveLocation === key ? 'active' : ''} onClick={()=>{setActiveSaveLocation(key);}}>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
renderNavItems : function() {
|
// render the entirety of the account page content
|
||||||
return <Navbar>
|
const renderAccountPage = ()=>{
|
||||||
<Nav.section>
|
return (
|
||||||
<NewBrew />
|
<>
|
||||||
<HelpNavItem />
|
|
||||||
<RecentNavItem />
|
|
||||||
<Account />
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderUiItems : function() {
|
|
||||||
return <>
|
|
||||||
<div className='dataGroup'>
|
<div className='dataGroup'>
|
||||||
<h1>Account Information <i className='fas fa-user'></i></h1>
|
<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>Username: </strong>{accountDetails.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>
|
<p><strong>Last Login: </strong>{moment(accountDetails.issued).format('dddd, MMMM Do YYYY, h:mm:ss a ZZ') || '-'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='dataGroup'>
|
<div className='dataGroup'>
|
||||||
<h3>Homebrewery Information <NaturalCritIcon /></h3>
|
<h3>Homebrewery Information <NaturalCritIcon /></h3>
|
||||||
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount}</p>
|
<p><strong>Brews on Homebrewery: </strong>{accountDetails.mongoCount}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='dataGroup'>
|
<div className='dataGroup'>
|
||||||
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
|
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
|
||||||
<p><strong>Linked to Google: </strong> {this.props.uiItems.googleId ? 'YES' : 'NO'}</p>
|
<p><strong>Linked to Google: </strong>{accountDetails.googleId ? 'YES' : 'NO'}</p>
|
||||||
{this.props.uiItems.googleId &&
|
{accountDetails.googleId && (
|
||||||
<p>
|
<p>
|
||||||
<strong>Brews on Google Drive: </strong> {this.props.uiItems.googleCount ?? <>Unable to retrieve files - <a href='https://github.com/naturalcrit/homebrewery/discussions/1580'>follow these steps to renew your Google credentials.</a></>}
|
<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>
|
</p>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='dataGroup'>
|
<div className='dataGroup'>
|
||||||
<h4>Default Save Location</h4>
|
<h4>Default Save Location</h4>
|
||||||
{this.renderButton('Homebrewery', 'HOMEBREWERY')}
|
{renderSaveLocationButton('Homebrewery', 'HOMEBREWERY')}
|
||||||
{this.renderButton('Google Drive', 'GOOGLE-DRIVE', this.state.uiItems.googleId)}
|
{renderSaveLocationButton('Google Drive', 'GOOGLE-DRIVE', accountDetails.googleId)}
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>
|
||||||
},
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render : function(){
|
// return the account page inside the base layout wrapper (with navbar etc).
|
||||||
return <UIPage brew={this.props.brew}>
|
return (
|
||||||
{this.renderUiItems()}
|
<UIPage brew={brew}>
|
||||||
</UIPage>;
|
{renderAccountPage()}
|
||||||
}
|
</UIPage>);
|
||||||
});
|
};
|
||||||
|
|
||||||
module.exports = AccountPage;
|
module.exports = AccountPage;
|
||||||
|
|||||||
@@ -73,9 +73,11 @@ const errorIndex = (props)=>{
|
|||||||
**Properties** tab, and adding your username to the "invited authors" list. You can
|
**Properties** tab, and adding your username to the "invited authors" list. You can
|
||||||
then try to access this document again.
|
then try to access this document again.
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
|
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
|
||||||
|
|
||||||
**Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}
|
**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})`,
|
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
|
||||||
|
|
||||||
@@ -86,9 +88,14 @@ const errorIndex = (props)=>{
|
|||||||
You must be logged in to one of the accounts listed as an author of 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}).
|
User is not logged in. Please log in [here](${loginUrl}).
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
|
**Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
|
||||||
|
|
||||||
**Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}`,
|
**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
|
// Brew load error
|
||||||
'05' : dedent`
|
'05' : dedent`
|
||||||
@@ -97,6 +104,8 @@ const errorIndex = (props)=>{
|
|||||||
The server could not locate the Homebrewery document. It was likely deleted by
|
The server could not locate the Homebrewery document. It was likely deleted by
|
||||||
its owner.
|
its owner.
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
**Requested access:** ${props.brew.accessType}
|
**Requested access:** ${props.brew.accessType}
|
||||||
|
|
||||||
**Brew ID:** ${props.brew.brewId}`,
|
**Brew ID:** ${props.brew.brewId}`,
|
||||||
@@ -113,6 +122,8 @@ const errorIndex = (props)=>{
|
|||||||
|
|
||||||
An error occurred while attempting to remove the Homebrewery document.
|
An error occurred while attempting to remove the Homebrewery document.
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
**Brew ID:** ${props.brew.brewId}`,
|
**Brew ID:** ${props.brew.brewId}`,
|
||||||
|
|
||||||
// Author delete error
|
// Author delete error
|
||||||
@@ -121,7 +132,21 @@ const errorIndex = (props)=>{
|
|||||||
|
|
||||||
An error occurred while attempting to remove the user from the Homebrewery document author list!
|
An error occurred while attempting to remove the user from the Homebrewery document author list!
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
**Brew ID:** ${props.brew.brewId}`,
|
**Brew ID:** ${props.brew.brewId}`,
|
||||||
|
|
||||||
|
// Brew locked by Administrators error
|
||||||
|
'100' : dedent`
|
||||||
|
## This brew has been locked.
|
||||||
|
|
||||||
|
Please contact the Administrators to unlock this document.
|
||||||
|
|
||||||
|
:
|
||||||
|
|
||||||
|
**Brew ID:** ${props.brew.brewId}
|
||||||
|
|
||||||
|
**Brew Title:** ${props.brew.brewTitle}`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
1129
package-lock.json
generated
1129
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -31,7 +31,7 @@
|
|||||||
"test:mustache-syntax:inline": "jest '.*(mustache-syntax).*' -t '^Inline:.*' --verbose --noStackTrace",
|
"test:mustache-syntax:inline": "jest '.*(mustache-syntax).*' -t '^Inline:.*' --verbose --noStackTrace",
|
||||||
"test:mustache-syntax:block": "jest '.*(mustache-syntax).*' -t '^Block:.*' --verbose --noStackTrace",
|
"test:mustache-syntax:block": "jest '.*(mustache-syntax).*' -t '^Block:.*' --verbose --noStackTrace",
|
||||||
"test:mustache-syntax:injection": "jest '.*(mustache-syntax).*' -t '^Injection:.*' --verbose --noStackTrace",
|
"test:mustache-syntax:injection": "jest '.*(mustache-syntax).*' -t '^Injection:.*' --verbose --noStackTrace",
|
||||||
"test:marked-extensions": "jest tests/markdown/marked-extensions.test.js --verbose --noStackTrace",
|
"test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace",
|
||||||
"test:route": "jest tests/routes/static-pages.test.js --verbose",
|
"test:route": "jest tests/routes/static-pages.test.js --verbose",
|
||||||
"phb": "node scripts/phb.js",
|
"phb": "node scripts/phb.js",
|
||||||
"prod": "set NODE_ENV=production && npm run build",
|
"prod": "set NODE_ENV=production && npm run build",
|
||||||
@@ -81,19 +81,19 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.24.0",
|
"@babel/core": "^7.24.4",
|
||||||
"@babel/plugin-transform-runtime": "^7.24.0",
|
"@babel/plugin-transform-runtime": "^7.24.3",
|
||||||
"@babel/preset-env": "^7.24.0",
|
"@babel/preset-env": "^7.24.4",
|
||||||
"@babel/preset-react": "^7.23.3",
|
"@babel/preset-react": "^7.24.1",
|
||||||
"@googleapis/drive": "^8.7.0",
|
"@googleapis/drive": "^8.7.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.5.1",
|
||||||
"codemirror": "^5.65.6",
|
"codemirror": "^5.65.6",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.10.3",
|
"dedent-tabs": "^0.10.3",
|
||||||
"expr-eval": "^2.0.2",
|
"expr-eval": "^2.0.2",
|
||||||
"express": "^4.18.3",
|
"express": "^4.19.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.7",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
"marked-smartypants-lite": "^1.0.2",
|
"marked-smartypants-lite": "^1.0.2",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^8.2.1",
|
"mongoose": "^8.3.1",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -120,8 +120,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-jest": "^27.9.0",
|
"eslint-plugin-jest": "^28.2.0",
|
||||||
"eslint-plugin-react": "^7.34.0",
|
"eslint-plugin-react": "^7.34.1",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
|
|||||||
10
server.js
10
server.js
@@ -7,6 +7,14 @@ DB.connect(config).then(()=>{
|
|||||||
// before launching server
|
// before launching server
|
||||||
const PORT = process.env.PORT || config.get('web_port') || 8000;
|
const PORT = process.env.PORT || config.get('web_port') || 8000;
|
||||||
server.app.listen(PORT, ()=>{
|
server.app.listen(PORT, ()=>{
|
||||||
console.log(`server on port: ${PORT}`);
|
const reset = '\x1b[0m'; // Reset to default style
|
||||||
|
const bright = '\x1b[1m'; // Bright (bold) style
|
||||||
|
const cyan = '\x1b[36m'; // Cyan color
|
||||||
|
const underline = '\x1b[4m'; // Underlined style
|
||||||
|
|
||||||
|
console.log(`\n\tserver started at: ${new Date().toLocaleString()}`);
|
||||||
|
console.log(`\tserver on port: ${PORT}`);
|
||||||
|
console.log(`\t${bright + cyan}Open in browser: ${reset}${underline + bright + cyan}http://localhost:${PORT}${reset}\n\n`)
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
data.uiItems = {
|
data.accountDetails = {
|
||||||
username : req.account.username,
|
username : req.account.username,
|
||||||
issued : req.account.issued,
|
issued : req.account.issued,
|
||||||
googleId : Boolean(req.account.googleId),
|
googleId : Boolean(req.account.googleId),
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ const config = require('./config.js');
|
|||||||
|
|
||||||
let serviceAuth;
|
let serviceAuth;
|
||||||
if(!config.get('service_account')){
|
if(!config.get('service_account')){
|
||||||
console.log('No Google Service Account in config files - Google Drive integration will not be available.');
|
const reset = '\x1b[0m'; // Reset to default style
|
||||||
|
const yellow = '\x1b[33m'; // yellow color
|
||||||
|
console.warn(`\n${yellow}No Google Service Account in config files - Google Drive integration will not be available.${reset}`);
|
||||||
} else {
|
} else {
|
||||||
const keys = typeof(config.get('service_account')) == 'string' ?
|
const keys = typeof(config.get('service_account')) == 'string' ?
|
||||||
JSON.parse(config.get('service_account')) :
|
JSON.parse(config.get('service_account')) :
|
||||||
@@ -18,7 +20,7 @@ if(!config.get('service_account')){
|
|||||||
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];
|
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
console.log('Please make sure the Google Service Account is set up properly in your config files.');
|
console.warn('Please make sure the Google Service Account is set up properly in your config files.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ const api = {
|
|||||||
});
|
});
|
||||||
stub = stub?.toObject();
|
stub = stub?.toObject();
|
||||||
|
|
||||||
|
if(stub?.lock?.locked && accessType != 'edit') {
|
||||||
|
throw { HBErrorCode: '100', code: stub.lock.code, message: stub.lock.message, brewId: stub.shareId, brewTitle: stub.title };
|
||||||
|
}
|
||||||
|
|
||||||
// If there is a google id, try to find the google brew
|
// If there is a google id, try to find the google brew
|
||||||
if(!stubOnly && (googleId || stub?.googleId)) {
|
if(!stubOnly && (googleId || stub?.googleId)) {
|
||||||
let googleError;
|
let googleError;
|
||||||
@@ -81,7 +85,7 @@ const api = {
|
|||||||
if(req.account){
|
if(req.account){
|
||||||
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId};
|
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId};
|
||||||
}
|
}
|
||||||
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title };
|
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If after all of that we still don't have a brew, throw an exception
|
// If after all of that we still don't have a brew, throw an exception
|
||||||
|
|||||||
@@ -298,6 +298,18 @@ describe('Tests for api', ()=>{
|
|||||||
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
|
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
|
||||||
expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share');
|
expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('access is denied to a locked brew', async()=>{
|
||||||
|
const lockBrew = { title: 'test brew', shareId: '1', lock: { locked: true, code: 404, message: 'brew locked' } };
|
||||||
|
model.get = jest.fn(()=>toBrewPromise(lockBrew));
|
||||||
|
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
|
||||||
|
|
||||||
|
const fn = api.getBrew('share', false);
|
||||||
|
const req = { brew: {} };
|
||||||
|
const next = jest.fn();
|
||||||
|
|
||||||
|
await expect(fn(req, null, next)).rejects.toEqual({ 'HBErrorCode': '100', 'brewId': '1', 'brewTitle': 'test brew', 'code': 404, 'message': 'brew locked' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mergeBrewText', ()=>{
|
describe('mergeBrewText', ()=>{
|
||||||
|
|||||||
@@ -24,6 +24,16 @@
|
|||||||
animation-duration: 0.4s;
|
animation-duration: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
width: 20px;
|
||||||
|
background: linear-gradient(90deg, #858585 15px, #808080 15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//.cm-tab {
|
//.cm-tab {
|
||||||
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
|
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -294,10 +294,10 @@ const superSubScripts = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitionListsInline = {
|
const definitionListsSingleLine = {
|
||||||
name : 'definitionListsInline',
|
name : 'definitionListsSingleLine',
|
||||||
level : 'block',
|
level : 'block',
|
||||||
start(src) { return src.match(/^[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/\n[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
|
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
|
||||||
let match;
|
let match;
|
||||||
@@ -312,7 +312,7 @@ const definitionListsInline = {
|
|||||||
}
|
}
|
||||||
if(definitions.length) {
|
if(definitions.length) {
|
||||||
return {
|
return {
|
||||||
type : 'definitionListsInline',
|
type : 'definitionListsSingleLine',
|
||||||
raw : src.slice(0, endIndex),
|
raw : src.slice(0, endIndex),
|
||||||
definitions
|
definitions
|
||||||
};
|
};
|
||||||
@@ -321,15 +321,15 @@ const definitionListsInline = {
|
|||||||
renderer(token) {
|
renderer(token) {
|
||||||
return `<dl>${token.definitions.reduce((html, def)=>{
|
return `<dl>${token.definitions.reduce((html, def)=>{
|
||||||
return `${html}<dt>${this.parser.parseInline(def.dt)}</dt>`
|
return `${html}<dt>${this.parser.parseInline(def.dt)}</dt>`
|
||||||
+ `<dd>${this.parser.parseInline(def.dd)}</dd>`;
|
+ `<dd>${this.parser.parseInline(def.dd)}</dd>\n`;
|
||||||
}, '')}</dl>`;
|
}, '')}</dl>`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitionListsMultiline = {
|
const definitionListsMultiLine = {
|
||||||
name : 'definitionListsMultiline',
|
name : 'definitionListsMultiLine',
|
||||||
level : 'block',
|
level : 'block',
|
||||||
start(src) { return src.match(/^[^\n]*\n::/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/\n[^\n]*\n::/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const regex = /(\n?\n?(?!::)[^\n]+?(?=\n::))|\n::(.(?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y;
|
const regex = /(\n?\n?(?!::)[^\n]+?(?=\n::))|\n::(.(?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y;
|
||||||
let match;
|
let match;
|
||||||
@@ -337,12 +337,14 @@ const definitionListsMultiline = {
|
|||||||
const definitions = [];
|
const definitions = [];
|
||||||
while (match = regex.exec(src)) {
|
while (match = regex.exec(src)) {
|
||||||
if(match[1]) {
|
if(match[1]) {
|
||||||
|
if(this.lexer.blockTokens(match[1].trim())[0]?.type !== 'paragraph') // DT must not be another block-level token besides <p>
|
||||||
|
break;
|
||||||
definitions.push({
|
definitions.push({
|
||||||
dt : this.lexer.inlineTokens(match[1].trim()),
|
dt : this.lexer.inlineTokens(match[1].trim()),
|
||||||
dds : []
|
dds : []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(match[2]) {
|
if(match[2] && definitions.length) {
|
||||||
definitions[definitions.length - 1].dds.push(
|
definitions[definitions.length - 1].dds.push(
|
||||||
this.lexer.inlineTokens(match[2].trim().replace(/\s/g, ' '))
|
this.lexer.inlineTokens(match[2].trim().replace(/\s/g, ' '))
|
||||||
);
|
);
|
||||||
@@ -351,7 +353,7 @@ const definitionListsMultiline = {
|
|||||||
}
|
}
|
||||||
if(definitions.length) {
|
if(definitions.length) {
|
||||||
return {
|
return {
|
||||||
type : 'definitionListsMultiline',
|
type : 'definitionListsMultiLine',
|
||||||
raw : src.slice(0, endIndex),
|
raw : src.slice(0, endIndex),
|
||||||
definitions
|
definitions
|
||||||
};
|
};
|
||||||
@@ -615,7 +617,7 @@ function MarkedVariables() {
|
|||||||
//^=====--------------------< Variable Handling >-------------------=====^//
|
//^=====--------------------< Variable Handling >-------------------=====^//
|
||||||
|
|
||||||
Marked.use(MarkedVariables());
|
Marked.use(MarkedVariables());
|
||||||
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionListsInline, definitionListsMultiline, superSubScripts] });
|
Marked.use({ extensions: [definitionListsMultiLine, definitionListsSingleLine, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] });
|
||||||
Marked.use(mustacheInjectBlock);
|
Marked.use(mustacheInjectBlock);
|
||||||
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
|
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
|
||||||
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
|
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ describe('Inline Definition Lists', ()=>{
|
|||||||
test('No Term 1 Definition', function() {
|
test('No Term 1 Definition', function() {
|
||||||
const source = ':: My First Definition\n\n';
|
const source = ':: My First Definition\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = Markdown.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt></dt><dd>My First Definition</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt></dt><dd>My First Definition</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Single Definition Term', function() {
|
test('Single Definition Term', function() {
|
||||||
const source = 'My term :: My First Definition\n\n';
|
const source = 'My term :: My First Definition\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = Markdown.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>My term</dt><dd>My First Definition</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>My term</dt><dd>My First Definition</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Definition Terms', function() {
|
test('Multiple Definition Terms', function() {
|
||||||
const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n';
|
const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = Markdown.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Definition of Term 1</dd><dt>Term 2</dt><dd>Definition of Term 2</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Definition of Term 1</dd>\n<dt>Term 2</dt><dd>Definition of Term 2</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ describe('Multiline Definition Lists', ()=>{
|
|||||||
test('Multiple Term, Single multi-line definition, followed by an inline dl', function() {
|
test('Multiple Term, Single multi-line definition, followed by an inline dl', function() {
|
||||||
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)';
|
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)';
|
||||||
const rendered = Markdown.render(source).trim();
|
const rendered = Markdown.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><dl><dt></dt><dd>Inline Definition (no term)</dd></dl>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><dl><dt></dt><dd>Inline Definition (no term)</dd>\n</dl>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Multiple Term, Single multi-line definition, followed by paragraph', function() {
|
test('Multiple Term, Single multi-line definition, followed by paragraph', function() {
|
||||||
@@ -76,4 +76,16 @@ describe('Multiline Definition Lists', ()=>{
|
|||||||
const rendered = Markdown.render(source).trim();
|
const rendered = Markdown.render(source).trim();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><p>Paragraph</p>');
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><p>Paragraph</p>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Block Token cannot be the Term of a multi-line definition', function() {
|
||||||
|
const source = '## Header\n::Definition 1 of a single-line DL\n::Definition 1 of another single-line DL';
|
||||||
|
const rendered = Markdown.render(source).trim();
|
||||||
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 id="header">Header</h2>\n<dl><dt></dt><dd>Definition 1 of a single-line DL</dd>\n<dt></dt><dd>Definition 1 of another single-line DL</dd>\n</dl>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Inline DL has priority over Multiline', function() {
|
||||||
|
const source = 'Term 1 :: Inline definition 1\n:: Inline definition 2 (no DT)';
|
||||||
|
const rendered = Markdown.render(source).trim();
|
||||||
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Inline definition 1</dd>\n<dt></dt><dd>Inline definition 2 (no DT)</dd>\n</dl>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -149,8 +149,6 @@ module.exports = {
|
|||||||

|

|
||||||
|
|
||||||
Homebrewery.Naturalcrit.com
|
Homebrewery.Naturalcrit.com
|
||||||
}}
|
}}`;
|
||||||
|
|
||||||
\page`;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import (less) './themes/assets/assets.less';
|
@import (less) './themes/assets/assets.less';
|
||||||
@import (less) './themes/fonts/icon fonts/font-icons.less';
|
@import (less) './themes/fonts/icon fonts/font-icons.less';
|
||||||
@import (less) './themes/fonts/icon fonts/dicefont.less';
|
@import (less) './themes/fonts/icon fonts/diceFont.less';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
//Colors
|
//Colors
|
||||||
@@ -532,14 +532,14 @@
|
|||||||
.page:has(.frontCover) {
|
.page:has(.frontCover) {
|
||||||
columns : 1;
|
columns : 1;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
&::after { all : unset; }
|
&::after { display : none; }
|
||||||
h1 {
|
h1 {
|
||||||
margin-top : 1.2cm;
|
margin-top : 1.2cm;
|
||||||
margin-bottom : 0;
|
margin-bottom : 0;
|
||||||
font-family : 'NodestoCapsCondensed';
|
font-family : 'NodestoCapsCondensed';
|
||||||
font-size : 2.245cm;
|
font-size : 2.245cm;
|
||||||
font-weight : normal;
|
font-weight : normal;
|
||||||
line-height : 0.85em;
|
line-height : 1.9cm;
|
||||||
color : white;
|
color : white;
|
||||||
text-shadow : unset;
|
text-shadow : unset;
|
||||||
text-transform : uppercase;
|
text-transform : uppercase;
|
||||||
@@ -626,14 +626,14 @@
|
|||||||
.page:has(.insideCover) {
|
.page:has(.insideCover) {
|
||||||
columns : 1;
|
columns : 1;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
&::after { all : unset; }
|
&::after { display : none; }
|
||||||
h1 {
|
h1 {
|
||||||
margin-top : 1.2cm;
|
margin-top : 1.2cm;
|
||||||
margin-bottom : 0;
|
margin-bottom : 0;
|
||||||
font-family : 'NodestoCapsCondensed';
|
font-family : 'NodestoCapsCondensed';
|
||||||
font-size : 2.1cm;
|
font-size : 2.1cm;
|
||||||
font-weight : normal;
|
font-weight : normal;
|
||||||
line-height : 0.85em;
|
line-height : 1.785cm;
|
||||||
text-transform : uppercase;
|
text-transform : uppercase;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
@@ -672,7 +672,7 @@
|
|||||||
padding : 2.25cm 1.3cm 2cm 1.3cm;
|
padding : 2.25cm 1.3cm 2cm 1.3cm;
|
||||||
color : #FFFFFF;
|
color : #FFFFFF;
|
||||||
columns : 1;
|
columns : 1;
|
||||||
&::after { all : unset; }
|
&::after { display : none; }
|
||||||
.columnWrapper { width : 7.6cm; }
|
.columnWrapper { width : 7.6cm; }
|
||||||
.backCover {
|
.backCover {
|
||||||
position : absolute;
|
position : absolute;
|
||||||
@@ -688,7 +688,7 @@
|
|||||||
margin-bottom : 0.3cm;
|
margin-bottom : 0.3cm;
|
||||||
font-family : 'NodestoCapsCondensed';
|
font-family : 'NodestoCapsCondensed';
|
||||||
font-size : 1.35cm;
|
font-size : 1.35cm;
|
||||||
line-height : 0.95em;
|
line-height : 1.28cm;
|
||||||
color : #ED1C24;
|
color : #ED1C24;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
}
|
}
|
||||||
@@ -714,7 +714,7 @@
|
|||||||
p {
|
p {
|
||||||
font-family : 'Overpass';
|
font-family : 'Overpass';
|
||||||
font-size : 0.332cm;
|
font-size : 0.332cm;
|
||||||
line-height : 1.5em;
|
line-height : 0.35cm;
|
||||||
}
|
}
|
||||||
hr + p {
|
hr + p {
|
||||||
margin-top : 0.6cm;
|
margin-top : 0.6cm;
|
||||||
@@ -739,10 +739,10 @@
|
|||||||
font-family : 'NodestoCapsWide';
|
font-family : 'NodestoCapsWide';
|
||||||
font-size : 0.4cm;
|
font-size : 0.4cm;
|
||||||
line-height : 1em;
|
line-height : 1em;
|
||||||
|
line-height : 1.28cm;
|
||||||
color : #FFFFFF;
|
color : #FFFFFF;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
text-indent : 0;
|
text-indent : 0;
|
||||||
letter-spacing : 0.08em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -782,7 +782,7 @@
|
|||||||
margin-left : auto;
|
margin-left : auto;
|
||||||
font-family : 'Overpass';
|
font-family : 'Overpass';
|
||||||
font-size : 0.45cm;
|
font-size : 0.45cm;
|
||||||
line-height : 1.1em;
|
line-height : 0.495cm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -326,27 +326,27 @@ module.exports = [
|
|||||||
gen : dedent`{{font-family:CodeLight Dummy Text}}`
|
gen : dedent`{{font-family:CodeLight Dummy Text}}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Scaly Sans Remake',
|
name : 'Scaly Sans',
|
||||||
icon : 'font ScalySansRemake',
|
icon : 'font ScalySansRemake',
|
||||||
gen : dedent`{{font-family:ScalySansRemake Dummy Text}}`
|
gen : dedent`{{font-family:ScalySansRemake Dummy Text}}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Book Insanity Remake',
|
name : 'Book Insanity',
|
||||||
icon : 'font BookInsanityRemake',
|
icon : 'font BookInsanityRemake',
|
||||||
gen : dedent`{{font-family:BookInsanityRemake Dummy Text}}`
|
gen : dedent`{{font-family:BookInsanityRemake Dummy Text}}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Mr Eaves Remake',
|
name : 'Mr Eaves',
|
||||||
icon : 'font MrEavesRemake',
|
icon : 'font MrEavesRemake',
|
||||||
gen : dedent`{{font-family:MrEavesRemake Dummy Text}}`
|
gen : dedent`{{font-family:MrEavesRemake Dummy Text}}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Solbera Imitation Remake',
|
name: 'Solbera Imitation',
|
||||||
icon: 'font SolberaImitationRemake',
|
icon: 'font SolberaImitationRemake',
|
||||||
gen: dedent`{{font-family:SolberaImitationRemake Dummy Text}}`
|
gen: dedent`{{font-family:SolberaImitationRemake Dummy Text}}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Scaly Sans Small Caps Remake',
|
name: 'Scaly Sans Small Caps',
|
||||||
icon: 'font ScalySansSmallCapsRemake',
|
icon: 'font ScalySansSmallCapsRemake',
|
||||||
gen: dedent`{{font-family:ScalySansSmallCapsRemake Dummy Text}}`
|
gen: dedent`{{font-family:ScalySansSmallCapsRemake Dummy Text}}`
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import (less) './themes/fonts/5e/fonts.less';
|
@import (less) './themes/fonts/5e/fonts.less';
|
||||||
@import (less) './themes/assets/assets.less';
|
@import (less) './themes/assets/assets.less';
|
||||||
@import (less) './themes/fonts/icon fonts/dicefont.less';
|
@import (less) './themes/fonts/icon fonts/diceFont.less';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
//Colors
|
//Colors
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/* Icon Font: dicefont */
|
/* Icon Font: diceFont */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family : 'DiceFont';
|
font-family : 'DiceFont';
|
||||||
font-style : normal;
|
font-style : normal;
|
||||||
font-weight : normal;
|
font-weight : normal;
|
||||||
src : url('../../../fonts/icon fonts/dicefont.woff2');
|
src : url('../../../fonts/icon fonts/diceFont.woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
.df {
|
.df {
|
||||||
Reference in New Issue
Block a user