mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-27 16:03:07 +00:00
Compare commits
287 Commits
v3.14.3
...
v3.15.2ABC
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d4cd4ab3 | ||
|
|
2a523c4955 | ||
|
|
487a574f50 | ||
|
|
1a325fb3c5 | ||
|
|
0110c6afed | ||
|
|
0e29620710 | ||
|
|
c33b44855a | ||
|
|
e7e35294c6 | ||
|
|
e9c45b216c | ||
|
|
40925253bd | ||
|
|
66433d9e77 | ||
|
|
3a6750613b | ||
|
|
a7361f8450 | ||
|
|
32fa272947 | ||
|
|
68895bdca2 | ||
|
|
8ab6a8599d | ||
|
|
ea656e5119 | ||
|
|
05ad8e17a7 | ||
|
|
bf87225415 | ||
|
|
8b61e69b77 | ||
|
|
e260eb0911 | ||
|
|
c8424e0b10 | ||
|
|
ff9a75f6b6 | ||
|
|
84736980c9 | ||
|
|
e0be7a5db3 | ||
|
|
2e332d7699 | ||
|
|
53979f2266 | ||
|
|
780c92cb9b | ||
|
|
20c54ef79e | ||
|
|
5ce69041fc | ||
|
|
20db8c6720 | ||
|
|
ed35eb2680 | ||
|
|
2a7b7cd50c | ||
|
|
d37fa03ec7 | ||
|
|
2c7d39147d | ||
|
|
6535e94ccd | ||
|
|
2ede3d7cf3 | ||
|
|
e56b2a7ce5 | ||
|
|
ad8e004fa9 | ||
|
|
e05d2e805c | ||
|
|
a083999943 | ||
|
|
247bc719b8 | ||
|
|
cd01014d79 | ||
|
|
7ab1efb0c9 | ||
|
|
eb05ac00a6 | ||
|
|
2bc39a468f | ||
|
|
c735ab7c35 | ||
|
|
d5e367649e | ||
|
|
f10444f14a | ||
|
|
29d4003bd2 | ||
|
|
1225e5cb6a | ||
|
|
aab24c732e | ||
|
|
dd5d551c73 | ||
|
|
d7d690a9d1 | ||
|
|
4f39222724 | ||
|
|
a1e585ccaa | ||
|
|
373a627c14 | ||
|
|
a7cb73b02e | ||
|
|
cd3e517b03 | ||
|
|
ef201409d9 | ||
|
|
e3ef93f03a | ||
|
|
aa90513825 | ||
|
|
1990064be4 | ||
|
|
44099c813c | ||
|
|
8f18601c2e | ||
|
|
db02f88287 | ||
|
|
56185e2a1c | ||
|
|
590318ff1d | ||
|
|
48a5a70d2e | ||
|
|
1c1901c90a | ||
|
|
ebae351033 | ||
|
|
e54d81ceef | ||
|
|
849da23829 | ||
|
|
1e36e63ed6 | ||
|
|
abb2d57879 | ||
|
|
52779eec35 | ||
|
|
9e694e5e46 | ||
|
|
321b8a0696 | ||
|
|
f2563e436f | ||
|
|
3c6f49aa0a | ||
|
|
9bc5701006 | ||
|
|
a3a5f749ab | ||
|
|
36b026d89e | ||
|
|
7474605b93 | ||
|
|
00a83ec16e | ||
|
|
afa1e7974a | ||
|
|
1517c00132 | ||
|
|
6d24908465 | ||
|
|
fd2d1f1ce2 | ||
|
|
226ad49663 | ||
|
|
e830c51a16 | ||
|
|
b6e904c9c8 | ||
|
|
3fc2e5202e | ||
|
|
f0b447866c | ||
|
|
c528c8639a | ||
|
|
47d8bb20d2 | ||
|
|
bb08aed1a8 | ||
|
|
9a2f18fc0d | ||
|
|
df21f978df | ||
|
|
fb8c4c5c44 | ||
|
|
19102db23a | ||
|
|
8d2a9ed9cb | ||
|
|
a0010c9c84 | ||
|
|
f7c3e81b7b | ||
|
|
c7f695e86a | ||
|
|
e6e220fbec | ||
|
|
8658a6a97a | ||
|
|
cd94857b13 | ||
|
|
3825bcbbfb | ||
|
|
981e7986ce | ||
|
|
c6f87eded0 | ||
|
|
62b96f4e79 | ||
|
|
8faae1e645 | ||
|
|
e4852b7077 | ||
|
|
2dc4ebb39f | ||
|
|
59672b79d8 | ||
|
|
59717fe630 | ||
|
|
3f9c7a1794 | ||
|
|
ca1f2dd1c3 | ||
|
|
a8e6f5cf26 | ||
|
|
f3a774d55c | ||
|
|
834980890a | ||
|
|
969eb354ce | ||
|
|
9288ead130 | ||
|
|
a8a4930225 | ||
|
|
2bc7cda8c9 | ||
|
|
f1ab332ce0 | ||
|
|
d50f23354a | ||
|
|
662a2d776a | ||
|
|
80f07bf0b0 | ||
|
|
6cf2dd8186 | ||
|
|
5de20cb451 | ||
|
|
aa0d0bed48 | ||
|
|
c92f30cfe0 | ||
|
|
2a8e4b2a63 | ||
|
|
51c7549b45 | ||
|
|
ea5170e6a6 | ||
|
|
cef90f5ff9 | ||
|
|
5a9b77190a | ||
|
|
59a5f641af | ||
|
|
0bda666127 | ||
|
|
5b96ef4406 | ||
|
|
af5bbdc677 | ||
|
|
c1dc712542 | ||
|
|
3e2c2de269 | ||
|
|
cc08579583 | ||
|
|
e562ebef48 | ||
|
|
e206b501a6 | ||
|
|
016a9fa1e8 | ||
|
|
01ee184044 | ||
|
|
ddf2006285 | ||
|
|
5232c16eb2 | ||
|
|
bdfd194672 | ||
|
|
cb0cb32860 | ||
|
|
1e080b30fd | ||
|
|
90431efbc9 | ||
|
|
99b0c2b54e | ||
|
|
042e217872 | ||
|
|
609f5a3330 | ||
|
|
058d70ed82 | ||
|
|
cbdf06f39d | ||
|
|
6f6c142aa2 | ||
|
|
72cb8e7db9 | ||
|
|
133295c225 | ||
|
|
54efc18ad4 | ||
|
|
43f77dc525 | ||
|
|
34d37b24f1 | ||
|
|
1e6427ca56 | ||
|
|
b9152867b8 | ||
|
|
879a1f5a57 | ||
|
|
0f9ba1a5ae | ||
|
|
d96d3c501e | ||
|
|
215888baf4 | ||
|
|
d56ea62b5e | ||
|
|
888cf55b3d | ||
|
|
4d7b1864d9 | ||
|
|
319746f6cd | ||
|
|
8fd165a79b | ||
|
|
ecfdada810 | ||
|
|
68c3e1ba84 | ||
|
|
efda06ebe5 | ||
|
|
e9b85e1a9a | ||
|
|
f9d19c75b2 | ||
|
|
21244fba58 | ||
|
|
55dd4efe41 | ||
|
|
6e08fcca80 | ||
|
|
3d0a9ea805 | ||
|
|
a711f8eb89 | ||
|
|
97ef56f905 | ||
|
|
124af97cc8 | ||
|
|
021ac68400 | ||
|
|
4b10686336 | ||
|
|
153812c6e5 | ||
|
|
9aa3ebe872 | ||
|
|
e23166e1d5 | ||
|
|
a89b575b26 | ||
|
|
5eeac603db | ||
|
|
cca0f3b4dc | ||
|
|
22f4dade4f | ||
|
|
e30a0c3dce | ||
|
|
9966e6322d | ||
|
|
02450982c1 | ||
|
|
f9f2e604c0 | ||
|
|
40ac8b1909 | ||
|
|
f1a2037bca | ||
|
|
ded78c6639 | ||
|
|
51fcb59f09 | ||
|
|
e75c556443 | ||
|
|
752b2caaf4 | ||
|
|
3c84143511 | ||
|
|
094ad3bd59 | ||
|
|
01e3cd0296 | ||
|
|
fd6109099a | ||
|
|
8c07b12bb0 | ||
|
|
eb404b8e5b | ||
|
|
0fdb5e83cf | ||
|
|
2a780bc482 | ||
|
|
f61b664687 | ||
|
|
04844a4422 | ||
|
|
3afc9f83d9 | ||
|
|
e08435568c | ||
|
|
1a80a74d4f | ||
|
|
9c53541cbd | ||
|
|
62db393969 | ||
|
|
d7c9ab43bc | ||
|
|
fcb4c722c6 | ||
|
|
7626f63beb | ||
|
|
a6e45c7fd1 | ||
|
|
2658831e83 | ||
|
|
ffdbe46a23 | ||
|
|
6a11cd0e28 | ||
|
|
153ab63393 | ||
|
|
ea1855485c | ||
|
|
3427fa1e94 | ||
|
|
46262c56db | ||
|
|
3482d92ab6 | ||
|
|
74ac8f9ffa | ||
|
|
7e289950fa | ||
|
|
9fc8af6553 | ||
|
|
7ffc02c3e5 | ||
|
|
2f323cde8a | ||
|
|
534131d994 | ||
|
|
d233e2b4a5 | ||
|
|
05a7defcb8 | ||
|
|
eeec24ae78 | ||
|
|
1d778e3249 | ||
|
|
3bb44d8a17 | ||
|
|
71c52b4587 | ||
|
|
fe449abb47 | ||
|
|
46d1f89b77 | ||
|
|
bf1f2054de | ||
|
|
399caaaeff | ||
|
|
27b4176e23 | ||
|
|
a8bc6b4e1d | ||
|
|
3ca8f72762 | ||
|
|
8aec5dbba6 | ||
|
|
cccebd8494 | ||
|
|
5fbbd92ea7 | ||
|
|
81f26e0892 | ||
|
|
9366284e1d | ||
|
|
9aa5eea8c9 | ||
|
|
20f61bff07 | ||
|
|
625819da91 | ||
|
|
2c691d84f2 | ||
|
|
4630d2640b | ||
|
|
043f24d5ca | ||
|
|
87e18c0521 | ||
|
|
7e30f860b2 | ||
|
|
0ac0ffe53d | ||
|
|
ae4e1b55e6 | ||
|
|
756ced088c | ||
|
|
8ce6b22be7 | ||
|
|
6184d64f89 | ||
|
|
c00c2626b4 | ||
|
|
89fddd0210 | ||
|
|
0c167d803c | ||
|
|
c50042c1e7 | ||
|
|
0dc1b46466 | ||
|
|
4ed9fc7d0e | ||
|
|
162929bdca | ||
|
|
54a2f6940c | ||
|
|
0dff59d793 | ||
|
|
7951c4a03a | ||
|
|
66fd56fccb | ||
|
|
da699e999f | ||
|
|
c6a5f50c76 | ||
|
|
74c7395ab9 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1 +1,3 @@
|
|||||||
package-lock.json binary
|
package-lock.json binary
|
||||||
|
|
||||||
|
*.json text eol=lf
|
||||||
20
changelog.md
20
changelog.md
@@ -84,6 +84,24 @@ pre {
|
|||||||
## changelog
|
## changelog
|
||||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
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
|
### Wednesday 8/28/2024 - v3.14.3
|
||||||
|
|
||||||
{{taskList
|
{{taskList
|
||||||
@@ -101,7 +119,7 @@ Fixes issue [#380](https://github.com/naturalcrit/homebrewery/issues/380)
|
|||||||
|
|
||||||
Fixes issue [#3605](https://github.com/naturalcrit/homebrewery/issues/3605)
|
Fixes issue [#3605](https://github.com/naturalcrit/homebrewery/issues/3605)
|
||||||
|
|
||||||
##### dbolack-ab
|
##### abquintic
|
||||||
|
|
||||||
* [x] Fix jump hotkeys conflicting with `CTRL + SHIFT`. Preview and Source movement shortcuts now use `CTRL + SHIFT + META + LEFT\RIGHTARROW`
|
* [x] Fix jump hotkeys conflicting with `CTRL + SHIFT`. Preview and Source movement shortcuts now use `CTRL + SHIFT + META + LEFT\RIGHTARROW`
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const _ = require('lodash');
|
|||||||
|
|
||||||
import Dialog from '../../../components/dialog.jsx';
|
import Dialog from '../../../components/dialog.jsx';
|
||||||
|
|
||||||
const DISMISS_KEY = 'dismiss_notification12-04-23';
|
const DISMISS_KEY = 'dismiss_notification01-10-24';
|
||||||
const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
|
const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
|
||||||
|
|
||||||
const NotificationPopup = ()=>{
|
const NotificationPopup = ()=>{
|
||||||
@@ -15,11 +15,40 @@ const NotificationPopup = ()=>{
|
|||||||
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li key='psa'>
|
<li key='ThrottlingError' style={{
|
||||||
<em>Don't store IMAGES in Google Drive</em><br />
|
backgroundColor: '#910000',
|
||||||
Google Drive is not an image service, and will block images from being used
|
margin: '-10px -10px -10px -20px',
|
||||||
in brews if they get more views than expected. Google has confirmed they won't fix
|
padding: '10px 10px 10px 20px',
|
||||||
this, so we recommend you look for another image hosting service such as imgur, ImgBB or Google Photos.
|
fontSize: '1.0em'
|
||||||
|
}}>
|
||||||
|
<em>Known issue with saving/creating Google Drive files</em><br />
|
||||||
|
Dear users. The <a href="https://github.com/naturalcrit/homebrewery/issues/3770">
|
||||||
|
issue with saving to Google Drive</a> has resurfaced as of Oct 1, 2024 22:00 UTC.
|
||||||
|
<br></br><br></br>
|
||||||
|
Earlier we submitted a bug report to Google and have all but confirmed the issue
|
||||||
|
lies on Google's end and the disruption has been affecting multiple other
|
||||||
|
organizations besides us. Unfortunately, it means reliable interaction with
|
||||||
|
Google remains out of our control until they can resolve their issue.
|
||||||
|
<br></br><br></br>
|
||||||
|
Brews saved to Google Drive are <em>not lost</em> and can still be viewed, just not updated.
|
||||||
|
You can also access them via your Google Drive interface in the <code>/Hombrewery</code> folder.
|
||||||
|
<br></br><br></br>
|
||||||
|
If you need to urgently edit documents, you can detatch them from your Google Drive
|
||||||
|
by transferring them to our Homebrewery storage. To do this, click the colored Google Drive
|
||||||
|
icon next to the save button when on an edit page; you can transfer them back later,
|
||||||
|
but this should allow you to edit while this issue is ongoing.
|
||||||
|
<br></br><br></br>
|
||||||
|
If you are experiencing errors creating new documents, you can similarly change your
|
||||||
|
account settings to create new brews by default in the Homebrewery storage. Click
|
||||||
|
your username and then "account", then change the "default save location".
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li key='Vault'>
|
||||||
|
<em>Search brews with our new page!</em><br />
|
||||||
|
We have been working very hard in making this possible, now you can share your work and look at it in the new <a href="/vault">Vault</a> page!
|
||||||
|
All PUBLISHED brews will be available to anyone searching there, by title or author, and filtering by renderer.
|
||||||
|
|
||||||
|
More features will be coming.
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li key='googleDriveFolder'>
|
<li key='googleDriveFolder'>
|
||||||
|
|||||||
@@ -113,7 +113,10 @@ const Editor = createClass({
|
|||||||
this.props.setMoveArrows(newView === 'text');
|
this.props.setMoveArrows(newView === 'text');
|
||||||
this.setState({
|
this.setState({
|
||||||
view : newView
|
view : newView
|
||||||
}, this.updateEditorSize); //TODO: not sure if updateeditorsize needed
|
}, ()=>{
|
||||||
|
this.codeEditor.current?.codeMirror.focus();
|
||||||
|
this.updateEditorSize();
|
||||||
|
}); //TODO: not sure if updateeditorsize needed
|
||||||
},
|
},
|
||||||
|
|
||||||
getCurrentPage : function(){
|
getCurrentPage : function(){
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const UserPage = require('./pages/userPage/userPage.jsx');
|
|||||||
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||||
const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||||
|
const VaultPage = require('./pages/vaultPage/vaultPage.jsx');
|
||||||
const AccountPage = require('./pages/accountPage/accountPage.jsx');
|
const AccountPage = require('./pages/accountPage/accountPage.jsx');
|
||||||
|
|
||||||
const WithRoute = (props)=>{
|
const WithRoute = (props)=>{
|
||||||
@@ -71,6 +72,7 @@ const Homebrew = createClass({
|
|||||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} userThemes={this.props.userThemes}/>} />
|
<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' element={<WithRoute el={NewPage} userThemes={this.props.userThemes}/> } />
|
||||||
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
|
<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='/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='/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='/migrate' element={<WithRoute el={SharePage} brew={this.props.brew} disableMeta={true} />} />
|
||||||
|
|||||||
@@ -116,6 +116,17 @@ const ErrorNavItem = createClass({
|
|||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(HBErrorCode === '55') {
|
||||||
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
|
Oops!
|
||||||
|
<div className='errorContainer' onClick={clearError}>
|
||||||
|
Looks like there are too many requests
|
||||||
|
from this IP address in a short time.
|
||||||
|
Please try again after a few minutes.
|
||||||
|
</div>
|
||||||
|
</Nav.item>;
|
||||||
|
}
|
||||||
|
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
Oops!
|
||||||
<div className='errorContainer'>
|
<div className='errorContainer'>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const RecentItems = createClass({
|
|||||||
//== Add current brew to appropriate recent items list (depending on storageKey) ==//
|
//== Add current brew to appropriate recent items list (depending on storageKey) ==//
|
||||||
if(this.props.storageKey == 'edit'){
|
if(this.props.storageKey == 'edit'){
|
||||||
let editId = this.props.brew.editId;
|
let editId = this.props.brew.editId;
|
||||||
if(this.props.brew.googleId){
|
if(this.props.brew.googleId && !this.props.brew.stubbed){
|
||||||
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
||||||
}
|
}
|
||||||
edited = _.filter(edited, (brew)=>{
|
edited = _.filter(edited, (brew)=>{
|
||||||
@@ -51,7 +51,7 @@ const RecentItems = createClass({
|
|||||||
}
|
}
|
||||||
if(this.props.storageKey == 'view'){
|
if(this.props.storageKey == 'view'){
|
||||||
let shareId = this.props.brew.shareId;
|
let shareId = this.props.brew.shareId;
|
||||||
if(this.props.brew.googleId){
|
if(this.props.brew.googleId && !this.props.brew.stubbed){
|
||||||
shareId = `${this.props.brew.googleId}${this.props.brew.shareId}`;
|
shareId = `${this.props.brew.googleId}${this.props.brew.shareId}`;
|
||||||
}
|
}
|
||||||
viewed = _.filter(viewed, (brew)=>{
|
viewed = _.filter(viewed, (brew)=>{
|
||||||
@@ -83,7 +83,7 @@ const RecentItems = createClass({
|
|||||||
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||||
if(this.props.storageKey == 'edit') {
|
if(this.props.storageKey == 'edit') {
|
||||||
let prevEditId = prevProps.brew.editId;
|
let prevEditId = prevProps.brew.editId;
|
||||||
if(prevProps.brew.googleId){
|
if(prevProps.brew.googleId && !this.props.brew.stubbed){
|
||||||
prevEditId = `${prevProps.brew.googleId}${prevProps.brew.editId}`;
|
prevEditId = `${prevProps.brew.googleId}${prevProps.brew.editId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ const RecentItems = createClass({
|
|||||||
return brew.id !== prevEditId;
|
return brew.id !== prevEditId;
|
||||||
});
|
});
|
||||||
let editId = this.props.brew.editId;
|
let editId = this.props.brew.editId;
|
||||||
if(this.props.brew.googleId){
|
if(this.props.brew.googleId && !this.props.brew.stubbed){
|
||||||
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
||||||
}
|
}
|
||||||
edited.unshift({
|
edited.unshift({
|
||||||
|
|||||||
17
client/homebrew/navbar/vault.navitem.jsx
Normal file
17
client/homebrew/navbar/vault.navitem.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -19,7 +19,8 @@ const BrewItem = createClass({
|
|||||||
stubbed : true
|
stubbed : true
|
||||||
},
|
},
|
||||||
updateListFilter : ()=>{},
|
updateListFilter : ()=>{},
|
||||||
reportError : ()=>{}
|
reportError : ()=>{},
|
||||||
|
renderStorage : true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,6 +96,7 @@ const BrewItem = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderStorageIcon : function(){
|
renderStorageIcon : function(){
|
||||||
|
if(!this.props.renderStorage) return;
|
||||||
if(this.props.brew.googleId) {
|
if(this.props.brew.googleId) {
|
||||||
return <span title={this.props.brew.webViewLink ? 'Your Google Drive Storage': 'Another User\'s Google Drive Storage'}>
|
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'>
|
<a href={this.props.brew.webViewLink} target='_blank'>
|
||||||
@@ -142,10 +144,14 @@ const BrewItem = createClass({
|
|||||||
}
|
}
|
||||||
<span title={`Authors:\n${brew.authors?.join('\n')}`}>
|
<span title={`Authors:\n${brew.authors?.join('\n')}`}>
|
||||||
<i className='fas fa-user'/> {brew.authors?.map((author, index)=>(
|
<i className='fas fa-user'/> {brew.authors?.map((author, index)=>(
|
||||||
<>
|
<React.Fragment key={index}>
|
||||||
<a key={index} href={`/user/${author}`}>{author}</a>
|
{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 && ', '}
|
{index < brew.authors.length - 1 && ', '}
|
||||||
</>))}
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpe
|
|||||||
|
|
||||||
const googleDriveIcon = require('../../googleDrive.svg');
|
const googleDriveIcon = require('../../googleDrive.svg');
|
||||||
|
|
||||||
const SAVE_TIMEOUT = 3000;
|
const SAVE_TIMEOUT = 16000;
|
||||||
|
|
||||||
const EditPage = createClass({
|
const EditPage = createClass({
|
||||||
displayName : 'EditPage',
|
displayName : 'EditPage',
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const errorIndex = (props)=>{
|
|||||||
Please login or signup at our [login page](https://www.naturalcrit.com/login?redirect=https://homebrewery.naturalcrit.com/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
|
// Brew locked by Administrators error
|
||||||
'100' : dedent`
|
'51' : dedent`
|
||||||
## This brew has been locked.
|
## This brew has been locked.
|
||||||
|
|
||||||
Only an author may request that this lock is removed.
|
Only an author may request that this lock is removed.
|
||||||
@@ -171,6 +171,11 @@ const errorIndex = (props)=>{
|
|||||||
**Brew ID:** ${props.brew.brewId}
|
**Brew ID:** ${props.brew.brewId}
|
||||||
|
|
||||||
**Brew Title:** ${props.brew.brewTitle}`,
|
**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.`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ const Nav = require('naturalcrit/nav/nav.jsx');
|
|||||||
const Navbar = require('../../navbar/navbar.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
|
const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
|
||||||
const HelpNavItem = require('../../navbar/help.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 RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||||
const { fetchThemeBundle } = require('../../../../shared/helpers.js');
|
const { fetchThemeBundle } = require('../../../../shared/helpers.js');
|
||||||
|
|
||||||
|
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const Editor = require('../../editor/editor.jsx');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
@@ -76,6 +76,7 @@ const HomePage = createClass({
|
|||||||
}
|
}
|
||||||
<NewBrewItem />
|
<NewBrewItem />
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
|
<VaultNavItem />
|
||||||
<RecentNavItem />
|
<RecentNavItem />
|
||||||
<AccountNavItem />
|
<AccountNavItem />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const Account = require('../../navbar/account.navitem.jsx');
|
|||||||
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||||
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||||
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
|
||||||
|
const VaultNavitem = require('../../navbar/vault.navitem.jsx');
|
||||||
|
|
||||||
const UserPage = createClass({
|
const UserPage = createClass({
|
||||||
displayName : 'UserPage',
|
displayName : 'UserPage',
|
||||||
@@ -66,6 +67,7 @@ const UserPage = createClass({
|
|||||||
}
|
}
|
||||||
<NewBrew />
|
<NewBrew />
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
|
<VaultNavitem/>
|
||||||
<RecentNavItem />
|
<RecentNavItem />
|
||||||
<Account />
|
<Account />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
|
|||||||
396
client/homebrew/pages/vaultPage/vaultPage.jsx
Normal file
396
client/homebrew/pages/vaultPage/vaultPage.jsx
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
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;
|
||||||
362
client/homebrew/pages/vaultPage/vaultPage.less
Normal file
362
client/homebrew/pages/vaultPage/vaultPage.less
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
.vaultPage {
|
||||||
|
height : 100%;
|
||||||
|
overflow-y : hidden;
|
||||||
|
background-color : #2C3E50;
|
||||||
|
|
||||||
|
*:not(input) { user-select : none; }
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background : #2C3E50;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.dataGroup {
|
||||||
|
width : 100%;
|
||||||
|
height : 100%;
|
||||||
|
background : white;
|
||||||
|
|
||||||
|
&.form .brewLookup {
|
||||||
|
position : relative;
|
||||||
|
padding : 50px clamp(20px, 4vw, 50px);
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size : 10pt;
|
||||||
|
color : #555555;
|
||||||
|
|
||||||
|
a { color : #333333; }
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding-inline : 5px;
|
||||||
|
background : lightgrey;
|
||||||
|
border-radius : 5px;
|
||||||
|
font-family : monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
package-lock.json
generated
45
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"version": "3.14.3",
|
"version": "3.15.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"version": "3.14.2",
|
"version": "3.15.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"expr-eval": "^2.0.2",
|
"expr-eval": "^2.0.2",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
|
"express-rate-limit": "^7.4.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.7",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
@@ -38,7 +39,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.6.0",
|
"mongoose": "^8.6.1",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -52,8 +53,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^3.0.1",
|
"@stylistic/stylelint-plugin": "^3.0.1",
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^9.9.1",
|
||||||
"eslint-plugin-jest": "^28.8.2",
|
"eslint-plugin-jest": "^28.8.3",
|
||||||
"eslint-plugin-react": "^7.35.0",
|
"eslint-plugin-react": "^7.35.2",
|
||||||
"globals": "^15.9.0",
|
"globals": "^15.9.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
@@ -5878,9 +5879,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-jest": {
|
"node_modules/eslint-plugin-jest": {
|
||||||
"version": "28.8.2",
|
"version": "28.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.3.tgz",
|
||||||
"integrity": "sha512-mC3OyklHmS5i7wYU1rGId9EnxRI8TVlnFG56AE+8U9iRy6zwaNygZR+DsdZuCL0gRG0wVeyzq+uWcPt6yJrrMA==",
|
"integrity": "sha512-HIQ3t9hASLKm2IhIOqnu+ifw7uLZkIlR7RYNv7fMcEi/p0CIiJmfriStQS2LDkgtY4nyLbIZAD+JL347Yc2ETQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
@@ -5903,11 +5904,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-react": {
|
"node_modules/eslint-plugin-react": {
|
||||||
"version": "7.35.0",
|
"version": "7.35.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz",
|
||||||
"integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==",
|
"integrity": "sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-includes": "^3.1.8",
|
"array-includes": "^3.1.8",
|
||||||
"array.prototype.findlast": "^1.2.5",
|
"array.prototype.findlast": "^1.2.5",
|
||||||
@@ -6353,6 +6353,21 @@
|
|||||||
"integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==",
|
"integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/express-rate-limit": {
|
||||||
|
"version": "7.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.0.tgz",
|
||||||
|
"integrity": "sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/express-rate-limit"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "4 || 5 || ^5.0.0-beta.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/express-static-gzip": {
|
"node_modules/express-static-gzip": {
|
||||||
"version": "2.1.7",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.7.tgz",
|
||||||
@@ -10795,9 +10810,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose": {
|
"node_modules/mongoose": {
|
||||||
"version": "8.6.0",
|
"version": "8.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.1.tgz",
|
||||||
"integrity": "sha512-p6VSbYKvD4ZIabqo8C0kS5eKX1Xpji+opTAIJ9wyuPJ8Y/FblgXSMnFRXnB40bYZLKPQT089K5KU8+bqIXtFdw==",
|
"integrity": "sha512-dppGcYqvsdg+VcnqXR5b467V4a+iNhmvkfYNpEPi6AjaUxnz6ioEDmrMLOi+sOWjvoHapuwPOigV4f2l7HC6ag==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^6.7.0",
|
"bson": "^6.7.0",
|
||||||
"kareem": "2.6.3",
|
"kareem": "2.6.3",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "3.14.3",
|
"version": "3.15.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": "^10.2.x",
|
"npm": "^10.2.x",
|
||||||
"node": "^20.8.x"
|
"node": "^20.8.x"
|
||||||
@@ -100,6 +100,7 @@
|
|||||||
"expr-eval": "^2.0.2",
|
"expr-eval": "^2.0.2",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
|
"express-rate-limit": "^7.4.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.7",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
@@ -113,7 +114,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.6.0",
|
"mongoose": "^8.6.1",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -127,8 +128,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^3.0.1",
|
"@stylistic/stylelint-plugin": "^3.0.1",
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^9.9.1",
|
||||||
"eslint-plugin-jest": "^28.8.2",
|
"eslint-plugin-jest": "^28.8.3",
|
||||||
"eslint-plugin-react": "^7.35.0",
|
"eslint-plugin-react": "^7.35.2",
|
||||||
"globals": "^15.9.0",
|
"globals": "^15.9.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ const sanitizeBrew = (brew, accessType)=>{
|
|||||||
return brew;
|
return brew;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
app.set('trust proxy', 1 /* number of proxies between user and server */)
|
||||||
|
app.get('/ip', (request, response) => response.send(request.ip))
|
||||||
|
|
||||||
|
|
||||||
app.use('/', serveCompressedStaticAssets(`build`));
|
app.use('/', serveCompressedStaticAssets(`build`));
|
||||||
app.use(require('./middleware/content-negotiation.js'));
|
app.use(require('./middleware/content-negotiation.js'));
|
||||||
app.use(require('body-parser').json({ limit: '25mb' }));
|
app.use(require('body-parser').json({ limit: '25mb' }));
|
||||||
@@ -55,6 +59,7 @@ app.use((req, res, next)=>{
|
|||||||
|
|
||||||
app.use(homebrewApi);
|
app.use(homebrewApi);
|
||||||
app.use(require('./admin.api.js'));
|
app.use(require('./admin.api.js'));
|
||||||
|
app.use(require('./vault.api.js'));
|
||||||
|
|
||||||
const HomebrewModel = require('./homebrew.model.js').model;
|
const HomebrewModel = require('./homebrew.model.js').model;
|
||||||
const welcomeText = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
|
const welcomeText = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
|
||||||
@@ -237,6 +242,8 @@ app.get('/user/:username', async (req, res, next)=>{
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
brews.forEach(brew => brew.stubbed = true); //All brews from MongoDB are "stubbed"
|
||||||
|
|
||||||
if(ownAccount && req?.account?.googleId){
|
if(ownAccount && req?.account?.googleId){
|
||||||
const auth = await GoogleActions.authCheck(req.account, res);
|
const auth = await GoogleActions.authCheck(req.account, res);
|
||||||
let googleBrews = await GoogleActions.listGoogleBrews(auth)
|
let googleBrews = await GoogleActions.listGoogleBrews(auth)
|
||||||
@@ -244,12 +251,12 @@ app.get('/user/:username', async (req, res, next)=>{
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If stub matches file from Google, use Google metadata over stub metadata
|
||||||
if(googleBrews && googleBrews.length > 0) {
|
if(googleBrews && googleBrews.length > 0) {
|
||||||
for (const brew of brews.filter((brew)=>brew.googleId)) {
|
for (const brew of brews.filter((brew)=>brew.googleId)) {
|
||||||
const match = googleBrews.findIndex((b)=>b.editId === brew.editId);
|
const match = googleBrews.findIndex((b)=>b.editId === brew.editId);
|
||||||
if(match !== -1) {
|
if(match !== -1) {
|
||||||
brew.googleId = googleBrews[match].googleId;
|
brew.googleId = googleBrews[match].googleId;
|
||||||
brew.stubbed = true;
|
|
||||||
brew.pageCount = googleBrews[match].pageCount;
|
brew.pageCount = googleBrews[match].pageCount;
|
||||||
brew.renderer = googleBrews[match].renderer;
|
brew.renderer = googleBrews[match].renderer;
|
||||||
brew.version = googleBrews[match].version;
|
brew.version = googleBrews[match].version;
|
||||||
@@ -258,6 +265,7 @@ app.get('/user/:username', async (req, res, next)=>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Remaining unstubbed google brews display current user as author
|
||||||
googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] }));
|
googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] }));
|
||||||
brews = _.concat(brews, googleBrews);
|
brews = _.concat(brews, googleBrews);
|
||||||
}
|
}
|
||||||
@@ -374,22 +382,12 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
|||||||
let googleCount = [];
|
let googleCount = [];
|
||||||
if(req.account) {
|
if(req.account) {
|
||||||
if(req.account.googleId) {
|
if(req.account.googleId) {
|
||||||
try {
|
auth = await GoogleActions.authCheck(req.account, res, false)
|
||||||
auth = await GoogleActions.authCheck(req.account, res, false);
|
|
||||||
} catch (e) {
|
googleCount = await GoogleActions.listGoogleBrews(auth)
|
||||||
auth = undefined;
|
.catch((err)=>{
|
||||||
console.log('Google auth check failed!');
|
console.error(err);
|
||||||
console.log(e);
|
});
|
||||||
}
|
|
||||||
if(auth.credentials.access_token) {
|
|
||||||
try {
|
|
||||||
googleCount = await GoogleActions.listGoogleBrews(auth);
|
|
||||||
} catch (e) {
|
|
||||||
googleCount = undefined;
|
|
||||||
console.log('List Google files failed!');
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = { authors: req.account.username, googleId: { $exists: false } };
|
const query = { authors: req.account.username, googleId: { $exists: false } };
|
||||||
@@ -403,7 +401,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
|||||||
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),
|
||||||
authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
|
authCheck : Boolean(req.account.googleId && auth?.credentials.access_token),
|
||||||
mongoCount : mongoCount,
|
mongoCount : mongoCount,
|
||||||
googleCount : googleCount?.length
|
googleCount : googleCount?.length
|
||||||
};
|
};
|
||||||
@@ -433,6 +431,11 @@ if(isLocalEnvironment){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Vault Page
|
||||||
|
app.get('/vault', asyncHandler(async(req, res, next)=>{
|
||||||
|
return next();
|
||||||
|
}));
|
||||||
|
|
||||||
//Send rendered page
|
//Send rendered page
|
||||||
app.use(asyncHandler(async (req, res, next)=>{
|
app.use(asyncHandler(async (req, res, next)=>{
|
||||||
if (!req.route) return res.redirect('/'); // Catch-all for invalid routes
|
if (!req.route) return res.redirect('/'); // Catch-all for invalid routes
|
||||||
@@ -492,7 +495,7 @@ app.use(async (err, req, res, next)=>{
|
|||||||
err.originalUrl = req.originalUrl;
|
err.originalUrl = req.originalUrl;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
if(err.originalUrl?.startsWith('/api/')) {
|
if(err.originalUrl?.startsWith('/api')) {
|
||||||
// console.log('API error');
|
// console.log('API error');
|
||||||
res.status(err.status || err.response?.status || 500).send(err);
|
res.status(err.status || err.response?.status || 500).send(err);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ if(!config.get('service_account')){
|
|||||||
|
|
||||||
const defaultAuth = serviceAuth || config.get('google_api_key');
|
const defaultAuth = serviceAuth || config.get('google_api_key');
|
||||||
|
|
||||||
|
const retryConfig = {
|
||||||
|
retry: 3, // Number of retry attempts
|
||||||
|
retryDelay: 100, // Initial delay in milliseconds
|
||||||
|
retryDelayMultiplier: 2, // Multiplier for exponential backoff
|
||||||
|
maxRetryDelay: 32000, // Maximum delay in milliseconds
|
||||||
|
httpMethodsToRetry: ['PATCH'], // Only retry PATCH requests
|
||||||
|
statusCodesToRetry: [[429, 429]], // Only retry on 429 status code
|
||||||
|
};
|
||||||
|
|
||||||
const GoogleActions = {
|
const GoogleActions = {
|
||||||
|
|
||||||
authCheck : (account, res, updateTokens=true)=>{
|
authCheck : (account, res, updateTokens=true)=>{
|
||||||
@@ -112,9 +121,7 @@ const GoogleActions = {
|
|||||||
})
|
})
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log(`Error Listing Google Brews`);
|
console.log(`Error Listing Google Brews`);
|
||||||
console.error(err);
|
|
||||||
throw (err);
|
throw (err);
|
||||||
//TODO: Should break out here, but continues on for some reason.
|
|
||||||
});
|
});
|
||||||
fileList.push(...obj.data.files);
|
fileList.push(...obj.data.files);
|
||||||
NextPageToken = obj.data.nextPageToken;
|
NextPageToken = obj.data.nextPageToken;
|
||||||
@@ -147,8 +154,9 @@ const GoogleActions = {
|
|||||||
return brews;
|
return brews;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateGoogleBrew : async (brew)=>{
|
updateGoogleBrew : async (brew, auth = defaultAuth, userIp)=>{
|
||||||
const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
|
const drive = googleDrive.drive({ version: 'v3', auth: auth });
|
||||||
|
console.log(auth == defaultAuth ? 'UPDATE w SERVICEACC' : 'UPDATE w USERACC')
|
||||||
|
|
||||||
await drive.files.update({
|
await drive.files.update({
|
||||||
fileId : brew.googleId,
|
fileId : brew.googleId,
|
||||||
@@ -168,11 +176,14 @@ const GoogleActions = {
|
|||||||
media : {
|
media : {
|
||||||
mimeType : 'text/plain',
|
mimeType : 'text/plain',
|
||||||
body : brew.text
|
body : brew.text
|
||||||
}
|
},
|
||||||
|
headers: {
|
||||||
|
'X-Forwarded-For': userIp, // Set the X-Forwarded-For header
|
||||||
|
},
|
||||||
|
retryConfig
|
||||||
})
|
})
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log('Error saving to google');
|
console.log('Error saving to google');
|
||||||
console.error(err);
|
|
||||||
throw (err);
|
throw (err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const yaml = require('js-yaml');
|
|||||||
const asyncHandler = require('express-async-handler');
|
const asyncHandler = require('express-async-handler');
|
||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
const { splitTextStyleAndMetadata } = require('../shared/helpers.js');
|
const { splitTextStyleAndMetadata } = require('../shared/helpers.js');
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
|
|
||||||
const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
|
const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ const api = {
|
|||||||
stub = stub?.toObject();
|
stub = stub?.toObject();
|
||||||
|
|
||||||
if(stub?.lock?.locked && accessType != 'edit') {
|
if(stub?.lock?.locked && accessType != 'edit') {
|
||||||
throw { HBErrorCode: '100', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title };
|
throw { HBErrorCode: '51', code: stub.lock.code, message: stub.lock.shareMessage, 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
|
||||||
@@ -242,11 +243,8 @@ const api = {
|
|||||||
|
|
||||||
let googleId, saved;
|
let googleId, saved;
|
||||||
if(saveToGoogle) {
|
if(saveToGoogle) {
|
||||||
googleId = await api.newGoogleBrew(req.account, newHomebrew, res)
|
googleId = await api.newGoogleBrew(req.account, newHomebrew, res);
|
||||||
.catch((err)=>{
|
|
||||||
console.error(err);
|
|
||||||
res.status(err?.status || err?.response?.status || 500).send(err?.message || err);
|
|
||||||
});
|
|
||||||
if(!googleId) return;
|
if(!googleId) return;
|
||||||
api.excludeStubProps(newHomebrew);
|
api.excludeStubProps(newHomebrew);
|
||||||
newHomebrew.googleId = googleId;
|
newHomebrew.googleId = googleId;
|
||||||
@@ -351,19 +349,13 @@ const api = {
|
|||||||
brew.googleId = undefined;
|
brew.googleId = undefined;
|
||||||
} else if(!brew.googleId && saveToGoogle) {
|
} else if(!brew.googleId && saveToGoogle) {
|
||||||
// If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew
|
// If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew
|
||||||
brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res)
|
brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res);
|
||||||
.catch((err)=>{
|
|
||||||
console.error(err);
|
|
||||||
res.status(err.status || err.response.status).send(err.message || err);
|
|
||||||
});
|
|
||||||
if(!brew.googleId) return;
|
if(!brew.googleId) return;
|
||||||
} else if(brew.googleId) {
|
} else if(brew.googleId) {
|
||||||
// If the google id exists and no other actions are being performed, update the google brew
|
// If the google id exists and no other actions are being performed, update the google brew
|
||||||
const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew))
|
const updated = await api.updateGoogleBrew(req.account, api.excludeGoogleProps(brew), res, req);
|
||||||
.catch((err)=>{
|
|
||||||
console.error(err);
|
|
||||||
res.status(err?.response?.status || 500).send(err);
|
|
||||||
});
|
|
||||||
if(!updated) return;
|
if(!updated) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,6 +398,15 @@ const api = {
|
|||||||
|
|
||||||
res.status(200).send(saved);
|
res.status(200).send(saved);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateGoogleBrew : async (account, brew, res, req)=>{
|
||||||
|
//let oAuth2Client;
|
||||||
|
//if(account.googleId)
|
||||||
|
// oAuth2Client = GoogleActions.authCheck(account, res);
|
||||||
|
|
||||||
|
return await GoogleActions.updateGoogleBrew(brew, undefined, req.ip);
|
||||||
|
},
|
||||||
|
|
||||||
deleteGoogleBrew : async (account, id, editId, res)=>{
|
deleteGoogleBrew : async (account, id, editId, res)=>{
|
||||||
const auth = await GoogleActions.authCheck(account, res);
|
const auth = await GoogleActions.authCheck(account, res);
|
||||||
await GoogleActions.deleteGoogleBrew(auth, id, editId);
|
await GoogleActions.deleteGoogleBrew(auth, id, editId);
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ describe('Tests for api', ()=>{
|
|||||||
const req = { brew: {} };
|
const req = { brew: {} };
|
||||||
const next = jest.fn();
|
const next = jest.fn();
|
||||||
|
|
||||||
await expect(fn(req, null, next)).rejects.toEqual({ 'HBErrorCode': '100', 'brewId': '1', 'brewTitle': 'test brew', 'code': 404, 'message': 'brew locked' });
|
await expect(fn(req, null, next)).rejects.toEqual({ 'HBErrorCode': '51', 'brewId': '1', 'brewTitle': 'test brew', 'code': 404, 'message': 'brew locked' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -560,16 +560,6 @@ brew`);
|
|||||||
views : 0
|
views : 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle google error', async()=>{
|
|
||||||
google.newGoogleBrew = jest.fn(()=>{
|
|
||||||
throw 'err';
|
|
||||||
});
|
|
||||||
await api.newBrew({ body: { text: 'asdf', title: '' }, query: { saveToGoogle: true }, account: { username: 'test user' } }, res);
|
|
||||||
|
|
||||||
expect(res.status).toHaveBeenCalledWith(500);
|
|
||||||
expect(res.send).toHaveBeenCalledWith('err');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteGoogleBrew', ()=>{
|
describe('deleteGoogleBrew', ()=>{
|
||||||
|
|||||||
102
server/vault.api.js
Normal file
102
server/vault.api.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const asyncHandler = require('express-async-handler');
|
||||||
|
const HomebrewModel = require('./homebrew.model.js').model;
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const titleConditions = (title)=>{
|
||||||
|
if(!title) return {};
|
||||||
|
return {
|
||||||
|
$text : {
|
||||||
|
$search : title,
|
||||||
|
$caseSensitive : false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorConditions = (author)=>{
|
||||||
|
if(!author) return {};
|
||||||
|
return { authors: author };
|
||||||
|
};
|
||||||
|
|
||||||
|
const rendererConditions = (legacy, v3)=>{
|
||||||
|
if(legacy === 'true' && v3 !== 'true')
|
||||||
|
return { renderer: 'legacy' };
|
||||||
|
|
||||||
|
if(v3 === 'true' && legacy !== 'true')
|
||||||
|
return { renderer: 'V3' };
|
||||||
|
|
||||||
|
return {}; // If all renderers selected, renderer field not needed in query for speed
|
||||||
|
};
|
||||||
|
|
||||||
|
const findBrews = async (req, res)=>{
|
||||||
|
const title = req.query.title || '';
|
||||||
|
const author = req.query.author || '';
|
||||||
|
const page = Math.max(parseInt(req.query.page) || 1, 1);
|
||||||
|
const count = Math.max(parseInt(req.query.count) || 20, 10);
|
||||||
|
const skip = (page - 1) * count;
|
||||||
|
|
||||||
|
const combinedQuery = {
|
||||||
|
$and : [
|
||||||
|
{ published: true },
|
||||||
|
rendererConditions(req.query.legacy, req.query.v3),
|
||||||
|
titleConditions(title),
|
||||||
|
authorConditions(author)
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const projection = {
|
||||||
|
editId : 0,
|
||||||
|
googleId : 0,
|
||||||
|
text : 0,
|
||||||
|
textBin : 0,
|
||||||
|
version : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
await HomebrewModel.find(combinedQuery, projection)
|
||||||
|
.skip(skip)
|
||||||
|
.limit(count)
|
||||||
|
.maxTimeMS(5000)
|
||||||
|
.exec()
|
||||||
|
.then((brews)=>{
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
|
const processedBrews = brews.map((brew)=>{
|
||||||
|
brew.authors = brew.authors.map((author)=>emailRegex.test(author) ? 'hidden' : author
|
||||||
|
);
|
||||||
|
return brew;
|
||||||
|
});
|
||||||
|
res.json({ brews: processedBrews, page });
|
||||||
|
})
|
||||||
|
.catch((error)=>{
|
||||||
|
throw { ...error, message: 'Error finding brews in Vault search', HBErrorCode: 90 };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const findTotal = async (req, res)=>{
|
||||||
|
const title = req.query.title || '';
|
||||||
|
const author = req.query.author || '';
|
||||||
|
|
||||||
|
const combinedQuery = {
|
||||||
|
$and : [
|
||||||
|
{ published: true },
|
||||||
|
rendererConditions(req.query.legacy, req.query.v3),
|
||||||
|
titleConditions(title),
|
||||||
|
authorConditions(author)
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await HomebrewModel.countDocuments(combinedQuery)
|
||||||
|
.then((totalBrews)=>{
|
||||||
|
console.log(`when returning, the total of brews is ${totalBrews} for the query ${JSON.stringify(combinedQuery)}`);
|
||||||
|
res.json({ totalBrews });
|
||||||
|
})
|
||||||
|
.catch((error)=>{
|
||||||
|
throw { ...error, message: 'Error finding brews in Vault search findTotal function', HBErrorCode: 91 };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
router.get('/api/vault/total', asyncHandler(findTotal));
|
||||||
|
router.get('/api/vault', asyncHandler(findBrews));
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -8,7 +8,8 @@ const SplitPane = createClass({
|
|||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
storageKey : 'naturalcrit-pane-split',
|
storageKey : 'naturalcrit-pane-split',
|
||||||
onDragFinish : function(){} //fires when dragging
|
onDragFinish : function(){}, //fires when dragging
|
||||||
|
showDividerButtons : true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -142,9 +143,11 @@ const SplitPane = createClass({
|
|||||||
width={this.state.currentDividerPos}
|
width={this.state.currentDividerPos}
|
||||||
>
|
>
|
||||||
{React.cloneElement(this.props.children[0], {
|
{React.cloneElement(this.props.children[0], {
|
||||||
|
...(this.props.showDividerButtons && {
|
||||||
moveBrew: this.state.moveBrew,
|
moveBrew: this.state.moveBrew,
|
||||||
moveSource: this.state.moveSource,
|
moveSource: this.state.moveSource,
|
||||||
setMoveArrows : this.setMoveArrows
|
setMoveArrows: this.setMoveArrows,
|
||||||
|
}),
|
||||||
})}
|
})}
|
||||||
</Pane>
|
</Pane>
|
||||||
{this.renderDivider()}
|
{this.renderDivider()}
|
||||||
|
|||||||
Reference in New Issue
Block a user