mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-08 22:32:41 +00:00
Merge branch 'more-style-snippets' of https://github.com/5e-Cleric/homebrewery into more-style-snippets
This commit is contained in:
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1 +1,3 @@
|
|||||||
package-lock.json binary
|
package-lock.json binary
|
||||||
|
|
||||||
|
*.json text eol=lf
|
||||||
36
.github/pull_request_template.md
vendored
Normal file
36
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!--
|
||||||
|
Before submitting a Pull Request, please consider the following to speed up reviews:
|
||||||
|
- 👷♀️ Create small PRs. Large PRs can usually be broken down into incremental PRs.
|
||||||
|
- 🚩 Do you already have several open PRs? Consider finishing or asking for help with existing PRs first.
|
||||||
|
- 🔧 Does your PR reference a discussed and approved issue, especially for personal or edge-case requests?
|
||||||
|
- 💡 Is the solution agreed upon? Save rework time by discussing strategy before coding.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
|
||||||
|
## Related Issues or Discussions
|
||||||
|
|
||||||
|
- Closes #
|
||||||
|
|
||||||
|
## QA Instructions, Screenshots, Recordings
|
||||||
|
|
||||||
|
_Please replace this line with instructions on how to test or view your changes, as well as any before/after
|
||||||
|
images for UI changes._
|
||||||
|
|
||||||
|
### Reviewer Checklist
|
||||||
|
|
||||||
|
_Please replace the list below with specific features you want reviewers to look at._
|
||||||
|
|
||||||
|
*Reviewers, refer to this list when testing features, or suggest new items *
|
||||||
|
- [ ] Verify new features are functional
|
||||||
|
- [ ] Feature A does X
|
||||||
|
- [ ] Feature B does Y
|
||||||
|
- [ ] Verify old features have not broken
|
||||||
|
- [ ] Feature Z can still be used
|
||||||
|
- [ ] Test for edge cases / try to break things
|
||||||
|
- [ ] Feature A handles negative numbers
|
||||||
|
- [ ] Identify opportunities for simplification and refactoring
|
||||||
|
- [ ] Check for code legibility and appropriate comments
|
||||||
|
|
||||||
|
<details><summary>Copy this list</summary>
|
||||||
57
changelog.md
57
changelog.md
@@ -84,9 +84,54 @@ 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).
|
||||||
|
|
||||||
### Tuesday 8/27/2024 - v3.14.2
|
### Wednesday 9/04/2024 - v3.15.0
|
||||||
{{taskList
|
|
||||||
|
|
||||||
|
{{taskList
|
||||||
|
##### 5e-Cleric, abquintic, calculuschild, Gazook89, G-Ambatte, Ericsheid, Kaiburr
|
||||||
|
|
||||||
|
* [x] New {{openSans **VAULT** {{fas,fa-dungeon}}}} page 🎉🎉🎉
|
||||||
|
:
|
||||||
|
All **PUBLISHED** brews ({{openSans :fas_circle_info: **Properties**}} menu) will be searchable, by title or author, and filtered by renderer. More features and adjustments will be coming.
|
||||||
|
:
|
||||||
|
Note: If any of your own brews are not showing up in search (particularly if stored on Google Drive), please edit and re-save to ensure our database has the data needed from document to be searchable.
|
||||||
|
|
||||||
|
Fixes issue [#697](https://github.com/naturalcrit/homebrewery/issues/697)
|
||||||
|
|
||||||
|
##### Gazook89
|
||||||
|
|
||||||
|
* [x] Auto-focus on text editor when switching editor tabs
|
||||||
|
}}
|
||||||
|
|
||||||
|
### Wednesday 8/28/2024 - v3.14.3
|
||||||
|
|
||||||
|
{{taskList
|
||||||
|
##### calculuschild, G-Ambatte
|
||||||
|
|
||||||
|
* [x] New {{openSans **IMAGES → {{fac,image-wrap-left}} IMAGE WRAP LEFT/RIGHT**}} snippets
|
||||||
|
|
||||||
|
Fixes issue [#380](https://github.com/naturalcrit/homebrewery/issues/380)
|
||||||
|
|
||||||
|
* [x] Fix v3.14.2 bug with `꞉꞉꞉꞉` failing after tables
|
||||||
|
|
||||||
|
##### 5e-Cleric
|
||||||
|
|
||||||
|
* [x] Fix Account page crash when not logged in
|
||||||
|
|
||||||
|
Fixes issue [#3605](https://github.com/naturalcrit/homebrewery/issues/3605)
|
||||||
|
|
||||||
|
##### abquintic
|
||||||
|
|
||||||
|
* [x] Fix jump hotkeys conflicting with `CTRL + SHIFT`. Preview and Source movement shortcuts now use `CTRL + SHIFT + META + LEFT\RIGHTARROW`
|
||||||
|
|
||||||
|
##### G-Ambatte
|
||||||
|
|
||||||
|
* [x] Fix display issue with image wrap icons
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
### Tuesday 8/27/2024 - v3.14.2
|
||||||
|
|
||||||
|
{{taskList
|
||||||
##### calculuschild
|
##### calculuschild
|
||||||
|
|
||||||
* [x] Reroute invalid urls to homepage
|
* [x] Reroute invalid urls to homepage
|
||||||
@@ -119,7 +164,7 @@ Fixes issues [#3572](https://github.com/naturalcrit/homebrewery/issues/3572)
|
|||||||
|
|
||||||
Fixes issues [#1430](https://github.com/naturalcrit/homebrewery/issues/1430)
|
Fixes issues [#1430](https://github.com/naturalcrit/homebrewery/issues/1430)
|
||||||
|
|
||||||
* [x] Fix colon `:::` being parsed in codeblocks
|
* [x] Fix colon `꞉꞉꞉꞉` being parsed in codeblocks
|
||||||
|
|
||||||
* [x] Prevent crashes when loading undefined renderer or theme bundle
|
* [x] Prevent crashes when loading undefined renderer or theme bundle
|
||||||
|
|
||||||
@@ -133,12 +178,11 @@ Fixes issues [#1430](https://github.com/naturalcrit/homebrewery/issues/1430)
|
|||||||
##### 5e-Cleric, Gazook89
|
##### 5e-Cleric, Gazook89
|
||||||
|
|
||||||
* [x] Viewer tools for zoom/page navigation
|
* [x] Viewer tools for zoom/page navigation
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
### Tuesday 8/13/2024 - v3.14.1
|
### Tuesday 8/13/2024 - v3.14.1
|
||||||
{{taskList
|
|
||||||
|
|
||||||
|
{{taskList
|
||||||
##### abquintic
|
##### abquintic
|
||||||
|
|
||||||
* [x] Allow Table of Contents to flow across columns
|
* [x] Allow Table of Contents to flow across columns
|
||||||
@@ -181,16 +225,13 @@ Fixes issues [#3613](https://github.com/naturalcrit/homebrewery/issues/3613)
|
|||||||
|
|
||||||
Fixes issues [#3622](https://github.com/naturalcrit/homebrewery/issues/3622)
|
Fixes issues [#3622](https://github.com/naturalcrit/homebrewery/issues/3622)
|
||||||
|
|
||||||
|
|
||||||
##### calculuschild
|
##### calculuschild
|
||||||
|
|
||||||
* [x] Fix `/migrate` page using an editor context instead of share context
|
* [x] Fix `/migrate` page using an editor context instead of share context
|
||||||
|
|
||||||
|
|
||||||
##### 5e-Cleric
|
##### 5e-Cleric
|
||||||
|
|
||||||
* [x] Fix Monster Stat Blocks losing color in Safari
|
* [x] Fix Monster Stat Blocks losing color in Safari
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
\page
|
\page
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
|
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
|
||||||
require('./brewRenderer.less');
|
require('./brewRenderer.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const { useState, useRef, useEffect } = React;
|
const { useState, useRef, useEffect, useCallback } = React;
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
|
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
|
||||||
@@ -49,23 +49,25 @@ let rawPages = [];
|
|||||||
|
|
||||||
const BrewRenderer = (props)=>{
|
const BrewRenderer = (props)=>{
|
||||||
props = {
|
props = {
|
||||||
text : '',
|
text : '',
|
||||||
style : '',
|
style : '',
|
||||||
renderer : 'legacy',
|
renderer : 'legacy',
|
||||||
theme : '5ePHB',
|
theme : '5ePHB',
|
||||||
lang : '',
|
lang : '',
|
||||||
errors : [],
|
errors : [],
|
||||||
currentEditorPage : 0,
|
currentEditorCursorPageNum : 0,
|
||||||
themeBundle : {},
|
currentEditorViewPageNum : 0,
|
||||||
|
currentBrewRendererPageNum : 0,
|
||||||
|
themeBundle : {},
|
||||||
|
onPageChange : ()=>{},
|
||||||
...props
|
...props
|
||||||
};
|
};
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
height : PAGE_HEIGHT,
|
height : PAGE_HEIGHT,
|
||||||
isMounted : false,
|
isMounted : false,
|
||||||
visibility : 'hidden',
|
visibility : 'hidden',
|
||||||
zoom : 100,
|
zoom : 100
|
||||||
currentPageNumber : 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mainRef = useRef(null);
|
const mainRef = useRef(null);
|
||||||
@@ -87,25 +89,22 @@ const BrewRenderer = (props)=>{
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCurrentPage = (e)=>{
|
const updateCurrentPage = useCallback(_.throttle((e)=>{
|
||||||
const { scrollTop, clientHeight, scrollHeight } = e.target;
|
const { scrollTop, clientHeight, scrollHeight } = e.target;
|
||||||
const totalScrollableHeight = scrollHeight - clientHeight;
|
const totalScrollableHeight = scrollHeight - clientHeight;
|
||||||
const currentPageNumber = Math.ceil((scrollTop / totalScrollableHeight) * rawPages.length);
|
const currentPageNumber = Math.max(Math.ceil((scrollTop / totalScrollableHeight) * rawPages.length), 1);
|
||||||
|
|
||||||
setState((prevState)=>({
|
props.onPageChange(currentPageNumber);
|
||||||
...prevState,
|
}, 200), []);
|
||||||
currentPageNumber : currentPageNumber || 1
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const isInView = (index)=>{
|
const isInView = (index)=>{
|
||||||
if(!state.isMounted)
|
if(!state.isMounted)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(index == props.currentEditorPage) //Already rendered before this step
|
if(index == props.currentEditorCursorPageNum - 1) //Already rendered before this step
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(Math.abs(index - state.currentPageNumber) <= 3)
|
if(Math.abs(index - props.currentBrewRendererPageNum - 1) <= 3)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -142,7 +141,7 @@ const BrewRenderer = (props)=>{
|
|||||||
renderedPages.length = 0;
|
renderedPages.length = 0;
|
||||||
|
|
||||||
// Render currently-edited page first so cross-page effects (variables, links) can propagate out first
|
// Render currently-edited page first so cross-page effects (variables, links) can propagate out first
|
||||||
renderedPages[props.currentEditorPage] = renderPage(rawPages[props.currentEditorPage], props.currentEditorPage);
|
renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1);
|
||||||
|
|
||||||
_.forEach(rawPages, (page, index)=>{
|
_.forEach(rawPages, (page, index)=>{
|
||||||
if((isInView(index) || !renderedPages[index]) && typeof window !== 'undefined'){
|
if((isInView(index) || !renderedPages[index]) && typeof window !== 'undefined'){
|
||||||
@@ -192,7 +191,7 @@ const BrewRenderer = (props)=>{
|
|||||||
<>
|
<>
|
||||||
{/*render dummy page while iFrame is mounting.*/}
|
{/*render dummy page while iFrame is mounting.*/}
|
||||||
{!state.isMounted
|
{!state.isMounted
|
||||||
? <div className='brewRenderer' onScroll={getCurrentPage}>
|
? <div className='brewRenderer' onScroll={updateCurrentPage}>
|
||||||
<div className='pages'>
|
<div className='pages'>
|
||||||
{renderDummyPage(1)}
|
{renderDummyPage(1)}
|
||||||
</div>
|
</div>
|
||||||
@@ -205,7 +204,7 @@ const BrewRenderer = (props)=>{
|
|||||||
<NotificationPopup />
|
<NotificationPopup />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ToolBar onZoomChange={handleZoom} currentPage={state.currentPageNumber} totalPages={rawPages.length}/>
|
<ToolBar onZoomChange={handleZoom} currentPage={props.currentBrewRendererPageNum} totalPages={rawPages.length}/>
|
||||||
|
|
||||||
{/*render in iFrame so broken code doesn't crash the site.*/}
|
{/*render in iFrame so broken code doesn't crash the site.*/}
|
||||||
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
|
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
|
||||||
@@ -214,7 +213,7 @@ const BrewRenderer = (props)=>{
|
|||||||
onClick={()=>{emitClick();}}
|
onClick={()=>{emitClick();}}
|
||||||
>
|
>
|
||||||
<div className={'brewRenderer'}
|
<div className={'brewRenderer'}
|
||||||
onScroll={getCurrentPage}
|
onScroll={updateCurrentPage}
|
||||||
onKeyDown={handleControlKeys}
|
onKeyDown={handleControlKeys}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{ height: state.height }}>
|
style={{ height: state.height }}>
|
||||||
|
|||||||
@@ -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_notification04-09-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,12 @@ 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='Vault'>
|
||||||
<em>Don't store IMAGES in Google Drive</em><br />
|
<em>Search brews with our new page!</em><br />
|
||||||
Google Drive is not an image service, and will block images from being used
|
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!
|
||||||
in brews if they get more views than expected. Google has confirmed they won't fix
|
All PUBLISHED brews will be available to anyone searching there, by title or author, and filtering by renderer.
|
||||||
this, so we recommend you look for another image hosting service such as imgur, ImgBB or Google Photos.
|
|
||||||
|
More features will be coming.
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li key='googleDriveFolder'>
|
<li key='googleDriveFolder'>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
|
|||||||
|
|
||||||
const [zoomLevel, setZoomLevel] = useState(100);
|
const [zoomLevel, setZoomLevel] = useState(100);
|
||||||
const [pageNum, setPageNum] = useState(currentPage);
|
const [pageNum, setPageNum] = useState(currentPage);
|
||||||
|
const [toolsVisible, setToolsVisible] = useState(true);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
onZoomChange(zoomLevel);
|
onZoomChange(zoomLevel);
|
||||||
@@ -55,7 +56,7 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
|
|||||||
|
|
||||||
} else if(mode == 'fit'){
|
} else if(mode == 'fit'){
|
||||||
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
|
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
|
||||||
const minDimRatio = [...pages].reduce((minRatio, page) => Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
|
const minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
|
||||||
|
|
||||||
desiredZoom = minDimRatio * 100;
|
desiredZoom = minDimRatio * 100;
|
||||||
}
|
}
|
||||||
@@ -67,7 +68,8 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='toolBar'>
|
<div className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`}>
|
||||||
|
<button className='toggleButton' title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
|
||||||
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
|
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
font-family : 'Open Sans', sans-serif;
|
font-family : 'Open Sans', sans-serif;
|
||||||
color : #CCCCCC;
|
color : #CCCCCC;
|
||||||
background-color : #555555;
|
background-color : #555555;
|
||||||
|
& > *:not(.toggleButton) {
|
||||||
|
opacity: 1;
|
||||||
|
transition: all .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.group {
|
.group {
|
||||||
box-sizing : border-box;
|
box-sizing : border-box;
|
||||||
@@ -100,4 +104,25 @@
|
|||||||
font-size:1.2em;
|
font-size:1.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
width: 32px;
|
||||||
|
transition: all .3s ease;
|
||||||
|
flex-wrap:nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: unset;
|
||||||
|
opacity: .5;
|
||||||
|
& > *:not(.toggleButton) {
|
||||||
|
opacity: 0;
|
||||||
|
transition: all .2s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.toggleButton {
|
||||||
|
z-index : 5;
|
||||||
|
position:absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 32px;
|
||||||
|
min-width: unset;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
|
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
|
||||||
require('./editor.less');
|
require('./editor.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const createClass = require('create-react-class');
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
|
||||||
const dedent = require('dedent-tabs').default;
|
const dedent = require('dedent-tabs').default;
|
||||||
const Markdown = require('../../../shared/naturalcrit/markdown.js');
|
const Markdown = require('../../../shared/naturalcrit/markdown.js');
|
||||||
|
|
||||||
@@ -22,6 +21,7 @@ const DEFAULT_STYLE_TEXT = dedent`
|
|||||||
color: black;
|
color: black;
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
let isJumping = false;
|
||||||
|
|
||||||
const Editor = createClass({
|
const Editor = createClass({
|
||||||
displayName : 'Editor',
|
displayName : 'Editor',
|
||||||
@@ -37,8 +37,15 @@ const Editor = createClass({
|
|||||||
onMetaChange : ()=>{},
|
onMetaChange : ()=>{},
|
||||||
reportError : ()=>{},
|
reportError : ()=>{},
|
||||||
|
|
||||||
|
onCursorPageChange : ()=>{},
|
||||||
|
onViewPageChange : ()=>{},
|
||||||
|
|
||||||
editorTheme : 'default',
|
editorTheme : 'default',
|
||||||
renderer : 'legacy'
|
renderer : 'legacy',
|
||||||
|
|
||||||
|
currentEditorCursorPageNum : 1,
|
||||||
|
currentEditorViewPageNum : 1,
|
||||||
|
currentBrewRendererPageNum : 1,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
@@ -56,12 +63,16 @@ const Editor = createClass({
|
|||||||
isMeta : function() {return this.state.view == 'meta';},
|
isMeta : function() {return this.state.view == 'meta';},
|
||||||
|
|
||||||
componentDidMount : function() {
|
componentDidMount : function() {
|
||||||
|
|
||||||
this.updateEditorSize();
|
this.updateEditorSize();
|
||||||
this.highlightCustomMarkdown();
|
this.highlightCustomMarkdown();
|
||||||
window.addEventListener('resize', this.updateEditorSize);
|
window.addEventListener('resize', this.updateEditorSize);
|
||||||
document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys);
|
document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys);
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
|
|
||||||
|
this.codeEditor.current.codeMirror.on('cursorActivity', (cm)=>{this.updateCurrentCursorPage(cm.getCursor());});
|
||||||
|
this.codeEditor.current.codeMirror.on('scroll', _.throttle(()=>{this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine());}, 200));
|
||||||
|
|
||||||
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
||||||
if(editorTheme) {
|
if(editorTheme) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -75,28 +86,37 @@ const Editor = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate : function(prevProps, prevState, snapshot) {
|
componentDidUpdate : function(prevProps, prevState, snapshot) {
|
||||||
|
|
||||||
this.highlightCustomMarkdown();
|
this.highlightCustomMarkdown();
|
||||||
if(prevProps.moveBrew !== this.props.moveBrew) {
|
if(prevProps.moveBrew !== this.props.moveBrew)
|
||||||
this.brewJump();
|
this.brewJump();
|
||||||
};
|
|
||||||
if(prevProps.moveSource !== this.props.moveSource) {
|
if(prevProps.moveSource !== this.props.moveSource)
|
||||||
this.sourceJump();
|
this.sourceJump();
|
||||||
};
|
|
||||||
|
if(this.props.liveScroll) {
|
||||||
|
if(prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) {
|
||||||
|
this.sourceJump(this.props.currentBrewRendererPageNum, false);
|
||||||
|
} else if(prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) {
|
||||||
|
this.brewJump(this.props.currentEditorViewPageNum, false);
|
||||||
|
} else if(prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) {
|
||||||
|
this.brewJump(this.props.currentEditorCursorPageNum, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleControlKeys : function(e){
|
handleControlKeys : function(e){
|
||||||
if(!(e.ctrlKey && e.metaKey)) return;
|
if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return;
|
||||||
const LEFTARROW_KEY = 37;
|
const LEFTARROW_KEY = 37;
|
||||||
const RIGHTARROW_KEY = 39;
|
const RIGHTARROW_KEY = 39;
|
||||||
if (e.shiftKey && (e.keyCode == RIGHTARROW_KEY)) this.brewJump();
|
if(e.keyCode == RIGHTARROW_KEY) this.brewJump();
|
||||||
if (e.shiftKey && (e.keyCode == LEFTARROW_KEY)) this.sourceJump();
|
if(e.keyCode == LEFTARROW_KEY) this.sourceJump();
|
||||||
if ((e.keyCode == LEFTARROW_KEY) || (e.keyCode == RIGHTARROW_KEY)) {
|
if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
updateEditorSize : function() {
|
updateEditorSize : function() {
|
||||||
if(this.codeEditor.current) {
|
if(this.codeEditor.current) {
|
||||||
let paneHeight = this.editor.current.parentNode.clientHeight;
|
let paneHeight = this.editor.current.parentNode.clientHeight;
|
||||||
@@ -105,6 +125,20 @@ const Editor = createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateCurrentCursorPage : function(cursor) {
|
||||||
|
const lines = this.props.brew.text.split('\n').slice(0, cursor.line + 1);
|
||||||
|
const pageRegex = this.props.brew.renderer == 'V3' ? /^\\page$/ : /\\page/;
|
||||||
|
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
|
||||||
|
this.props.onCursorPageChange(currentPage);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCurrentViewPage : function(topScrollLine) {
|
||||||
|
const lines = this.props.brew.text.split('\n').slice(0, topScrollLine + 1);
|
||||||
|
const pageRegex = this.props.brew.renderer == 'V3' ? /^\\page$/ : /\\page/;
|
||||||
|
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
|
||||||
|
this.props.onViewPageChange(currentPage);
|
||||||
|
},
|
||||||
|
|
||||||
handleInject : function(injectText){
|
handleInject : function(injectText){
|
||||||
this.codeEditor.current?.injectText(injectText, false);
|
this.codeEditor.current?.injectText(injectText, false);
|
||||||
},
|
},
|
||||||
@@ -113,19 +147,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();
|
||||||
getCurrentPage : function(){
|
}); //TODO: not sure if updateeditorsize needed
|
||||||
const lines = this.props.brew.text.split('\n').slice(0, this.codeEditor.current.getCursorPosition().line + 1);
|
|
||||||
return _.reduce(lines, (r, line)=>{
|
|
||||||
if(
|
|
||||||
(this.props.renderer == 'legacy' && line.indexOf('\\page') !== -1)
|
|
||||||
||
|
|
||||||
(this.props.renderer == 'V3' && line.match(/^\\page$/))
|
|
||||||
) r++;
|
|
||||||
return r;
|
|
||||||
}, 1);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
highlightCustomMarkdown : function(){
|
highlightCustomMarkdown : function(){
|
||||||
@@ -136,13 +161,13 @@ const Editor = createClass({
|
|||||||
codeMirror.operation(()=>{ // Batch CodeMirror styling
|
codeMirror.operation(()=>{ // Batch CodeMirror styling
|
||||||
|
|
||||||
const foldLines = [];
|
const foldLines = [];
|
||||||
|
|
||||||
//reset custom text styles
|
//reset custom text styles
|
||||||
const customHighlights = codeMirror.getAllMarks().filter((mark)=>{
|
const customHighlights = codeMirror.getAllMarks().filter((mark)=>{
|
||||||
// Record details of folded sections
|
// Record details of folded sections
|
||||||
if(mark.__isFold) {
|
if(mark.__isFold) {
|
||||||
const fold = mark.find();
|
const fold = mark.find();
|
||||||
foldLines.push({from: fold.from?.line, to: fold.to?.line});
|
foldLines.push({ from: fold.from?.line, to: fold.to?.line });
|
||||||
}
|
}
|
||||||
return !mark.__isFold;
|
return !mark.__isFold;
|
||||||
}); //Don't undo code folding
|
}); //Don't undo code folding
|
||||||
@@ -160,7 +185,7 @@ const Editor = createClass({
|
|||||||
|
|
||||||
// Don't process lines inside folded text
|
// Don't process lines inside folded text
|
||||||
// If the current lineNumber is inside any folded marks, skip line styling
|
// If the current lineNumber is inside any folded marks, skip line styling
|
||||||
if (foldLines.some(fold => lineNumber >= fold.from && lineNumber <= fold.to))
|
if(foldLines.some((fold)=>lineNumber >= fold.from && lineNumber <= fold.to))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Styling for \page breaks
|
// Styling for \page breaks
|
||||||
@@ -186,7 +211,7 @@ const Editor = createClass({
|
|||||||
|
|
||||||
// definition lists
|
// definition lists
|
||||||
if(line.includes('::')){
|
if(line.includes('::')){
|
||||||
if(/^:*$/.test(line) == true){ return };
|
if(/^:*$/.test(line) == true){ return; };
|
||||||
const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error.
|
const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error.
|
||||||
let match;
|
let match;
|
||||||
while ((match = regex.exec(line)) != null){
|
while ((match = regex.exec(line)) != null){
|
||||||
@@ -194,10 +219,10 @@ const Editor = createClass({
|
|||||||
codeMirror.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' });
|
codeMirror.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' });
|
||||||
codeMirror.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' });
|
codeMirror.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' });
|
||||||
const ddIndex = match.indices[2][0];
|
const ddIndex = match.indices[2][0];
|
||||||
let colons = /::/g;
|
const colons = /::/g;
|
||||||
let colonMatches = colons.exec(match[2]);
|
const colonMatches = colons.exec(match[2]);
|
||||||
if(colonMatches !== null){
|
if(colonMatches !== null){
|
||||||
codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight'} )
|
codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,12 +232,12 @@ const Editor = createClass({
|
|||||||
let startIndex = line.indexOf('^');
|
let startIndex = line.indexOf('^');
|
||||||
const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy;
|
const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy;
|
||||||
const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy;
|
const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy;
|
||||||
|
|
||||||
while (startIndex >= 0) {
|
while (startIndex >= 0) {
|
||||||
superRegex.lastIndex = subRegex.lastIndex = startIndex;
|
superRegex.lastIndex = subRegex.lastIndex = startIndex;
|
||||||
let isSuper = false;
|
let isSuper = false;
|
||||||
let match = subRegex.exec(line) || superRegex.exec(line);
|
const match = subRegex.exec(line) || superRegex.exec(line);
|
||||||
if (match) {
|
if(match) {
|
||||||
isSuper = !subRegex.lastIndex;
|
isSuper = !subRegex.lastIndex;
|
||||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' });
|
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' });
|
||||||
}
|
}
|
||||||
@@ -262,18 +287,18 @@ const Editor = createClass({
|
|||||||
|
|
||||||
while (startIndex >= 0) {
|
while (startIndex >= 0) {
|
||||||
emojiRegex.lastIndex = startIndex;
|
emojiRegex.lastIndex = startIndex;
|
||||||
let match = emojiRegex.exec(line);
|
const match = emojiRegex.exec(line);
|
||||||
if (match) {
|
if(match) {
|
||||||
let tokens = Markdown.marked.lexer(match[0]);
|
let tokens = Markdown.marked.lexer(match[0]);
|
||||||
tokens = tokens[0].tokens.filter(t => t.type == 'emoji')
|
tokens = tokens[0].tokens.filter((t)=>t.type == 'emoji');
|
||||||
if (!tokens.length)
|
if(!tokens.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let startPos = { line: lineNumber, ch: match.index };
|
const startPos = { line: lineNumber, ch: match.index };
|
||||||
let endPos = { line: lineNumber, ch: match.index + match[0].length };
|
const endPos = { line: lineNumber, ch: match.index + match[0].length };
|
||||||
|
|
||||||
// Iterate over conflicting marks and clear them
|
// Iterate over conflicting marks and clear them
|
||||||
var marks = codeMirror.findMarks(startPos, endPos);
|
const marks = codeMirror.findMarks(startPos, endPos);
|
||||||
marks.forEach(function(marker) {
|
marks.forEach(function(marker) {
|
||||||
if(!marker.__isFold) marker.clear();
|
if(!marker.__isFold) marker.clear();
|
||||||
});
|
});
|
||||||
@@ -288,75 +313,93 @@ const Editor = createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
brewJump : function(targetPage=this.getCurrentPage()){
|
brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){
|
||||||
if(!window) return;
|
if(!window || isJumping)
|
||||||
// console.log(`Scroll to: p${targetPage}`);
|
return;
|
||||||
|
|
||||||
|
// Get current brewRenderer scroll position and calculate target position
|
||||||
const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0];
|
const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0];
|
||||||
const currentPos = brewRenderer.scrollTop;
|
const currentPos = brewRenderer.scrollTop;
|
||||||
const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top;
|
const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top;
|
||||||
const interimPos = targetPos >= 0 ? -30 : 30;
|
|
||||||
|
|
||||||
const bounceDelay = 100;
|
const checkIfScrollComplete = ()=>{
|
||||||
const scrollDelay = 500;
|
let scrollingTimeout;
|
||||||
|
clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs
|
||||||
if(!this.throttleBrewMove) {
|
scrollingTimeout = setTimeout(()=>{
|
||||||
this.throttleBrewMove = _.throttle((currentPos, interimPos, targetPos)=>{
|
isJumping = false;
|
||||||
brewRenderer.scrollTo({ top: currentPos + interimPos, behavior: 'smooth' });
|
brewRenderer.removeEventListener('scroll', checkIfScrollComplete);
|
||||||
setTimeout(()=>{
|
}, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done
|
||||||
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' });
|
|
||||||
}, bounceDelay);
|
|
||||||
}, scrollDelay, { leading: true, trailing: false });
|
|
||||||
};
|
};
|
||||||
this.throttleBrewMove(currentPos, interimPos, targetPos);
|
|
||||||
|
|
||||||
// const hashPage = (page != 1) ? `p${page}` : '';
|
isJumping = true;
|
||||||
// window.location.hash = hashPage;
|
checkIfScrollComplete();
|
||||||
|
brewRenderer.addEventListener('scroll', checkIfScrollComplete);
|
||||||
|
|
||||||
|
if(smooth) {
|
||||||
|
const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling
|
||||||
|
const bounceDelay = 100;
|
||||||
|
const scrollDelay = 500;
|
||||||
|
|
||||||
|
if(!this.throttleBrewMove) {
|
||||||
|
this.throttleBrewMove = _.throttle((currentPos, bouncePos, targetPos)=>{
|
||||||
|
brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' });
|
||||||
|
setTimeout(()=>{
|
||||||
|
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' });
|
||||||
|
}, bounceDelay);
|
||||||
|
}, scrollDelay, { leading: true, trailing: false });
|
||||||
|
};
|
||||||
|
this.throttleBrewMove(currentPos, bouncePos, targetPos);
|
||||||
|
} else {
|
||||||
|
brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sourceJump : function(targetLine=null){
|
sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){
|
||||||
if(this.isText()) {
|
if(!this.isText || isJumping)
|
||||||
if(targetLine == null) {
|
return;
|
||||||
targetLine = 0;
|
|
||||||
|
|
||||||
const pageCollection = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('page');
|
const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/;
|
||||||
const brewRendererHeight = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer').item(0).getBoundingClientRect().height;
|
const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit);
|
||||||
|
const targetLine = textString.match('\n') ? textString.split('\n').length - 1 : -1;
|
||||||
|
|
||||||
let currentPage = 1;
|
let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top;
|
||||||
for (const page of pageCollection) {
|
let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
|
||||||
if(page.getBoundingClientRect().bottom > (brewRendererHeight / 2)) {
|
|
||||||
currentPage = parseInt(page.id.slice(1)) || 1;
|
const checkIfScrollComplete = ()=>{
|
||||||
break;
|
let scrollingTimeout;
|
||||||
}
|
clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs
|
||||||
|
scrollingTimeout = setTimeout(()=>{
|
||||||
|
isJumping = false;
|
||||||
|
this.codeEditor.current.codeMirror.off('scroll', checkIfScrollComplete);
|
||||||
|
}, 150); // If 150 ms pass without a scroll event, assume scrolling is done
|
||||||
|
};
|
||||||
|
|
||||||
|
isJumping = true;
|
||||||
|
checkIfScrollComplete();
|
||||||
|
this.codeEditor.current.codeMirror.on('scroll', checkIfScrollComplete);
|
||||||
|
|
||||||
|
if(smooth) {
|
||||||
|
//Scroll 1/10 of the way every 10ms until 1px off.
|
||||||
|
const incrementalScroll = setInterval(()=>{
|
||||||
|
currentY += (targetY - currentY) / 10;
|
||||||
|
this.codeEditor.current.codeMirror.scrollTo(null, currentY);
|
||||||
|
|
||||||
|
// Update target: target height is not accurate until within +-10 lines of the visible window
|
||||||
|
if(Math.abs(targetY - currentY > 100))
|
||||||
|
targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
|
||||||
|
|
||||||
|
// End when close enough
|
||||||
|
if(Math.abs(targetY - currentY) < 1) {
|
||||||
|
this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference
|
||||||
|
this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 });
|
||||||
|
this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
|
||||||
|
clearInterval(incrementalScroll);
|
||||||
}
|
}
|
||||||
|
}, 10);
|
||||||
const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/;
|
} else {
|
||||||
const textString = this.props.brew.text.split(textSplit).slice(0, currentPage-1).join(textSplit);
|
this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference
|
||||||
const textPosition = textString.length;
|
this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 });
|
||||||
const lineCount = textString.match('\n') ? textString.slice(0, textPosition).split('\n').length : 0;
|
this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
|
||||||
|
|
||||||
targetLine = lineCount - 1; //Scroll to `\page`, which is one line back.
|
|
||||||
|
|
||||||
let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top;
|
|
||||||
let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
|
|
||||||
|
|
||||||
//Scroll 1/10 of the way every 10ms until 1px off.
|
|
||||||
const incrementalScroll = setInterval(()=>{
|
|
||||||
currentY += (targetY - currentY) / 10;
|
|
||||||
this.codeEditor.current.codeMirror.scrollTo(null, currentY);
|
|
||||||
|
|
||||||
// Update target: target height is not accurate until within +-10 lines of the visible window
|
|
||||||
if(Math.abs(targetY - currentY > 100))
|
|
||||||
targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
|
|
||||||
|
|
||||||
// End when close enough
|
|
||||||
if(Math.abs(targetY - currentY) < 1) {
|
|
||||||
this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference
|
|
||||||
this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 });
|
|
||||||
this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
|
|
||||||
clearInterval(incrementalScroll);
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -457,7 +500,9 @@ const Editor = createClass({
|
|||||||
currentEditorTheme={this.state.editorTheme}
|
currentEditorTheme={this.state.editorTheme}
|
||||||
updateEditorTheme={this.updateEditorTheme}
|
updateEditorTheme={this.updateEditorTheme}
|
||||||
snippetBundle={this.props.snippetBundle}
|
snippetBundle={this.props.snippetBundle}
|
||||||
cursorPos={this.codeEditor.current?.getCursorPosition() || {}} />
|
cursorPos={this.codeEditor.current?.getCursorPosition() || {}}
|
||||||
|
updateBrew={this.props.updateBrew}
|
||||||
|
/>
|
||||||
|
|
||||||
{this.renderEditor()}
|
{this.renderEditor()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const createClass = require('create-react-class');
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
import { getHistoryItems, historyExists } from '../../utils/versionHistory.js';
|
||||||
|
|
||||||
//Import all themes
|
//Import all themes
|
||||||
const ThemeSnippets = {};
|
const ThemeSnippets = {};
|
||||||
ThemeSnippets['Legacy_5ePHB'] = require('themes/Legacy/5ePHB/snippets.js');
|
ThemeSnippets['Legacy_5ePHB'] = require('themes/Legacy/5ePHB/snippets.js');
|
||||||
@@ -38,7 +40,8 @@ const Snippetbar = createClass({
|
|||||||
unfoldCode : ()=>{},
|
unfoldCode : ()=>{},
|
||||||
updateEditorTheme : ()=>{},
|
updateEditorTheme : ()=>{},
|
||||||
cursorPos : {},
|
cursorPos : {},
|
||||||
snippetBundle : []
|
snippetBundle : [],
|
||||||
|
updateBrew : ()=>{}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -46,7 +49,8 @@ const Snippetbar = createClass({
|
|||||||
return {
|
return {
|
||||||
renderer : this.props.renderer,
|
renderer : this.props.renderer,
|
||||||
themeSelector : false,
|
themeSelector : false,
|
||||||
snippets : []
|
snippets : [],
|
||||||
|
historyExists : false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -59,18 +63,20 @@ const Snippetbar = createClass({
|
|||||||
|
|
||||||
componentDidUpdate : async function(prevProps) {
|
componentDidUpdate : async function(prevProps) {
|
||||||
if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) {
|
if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) {
|
||||||
const snippets = this.compileSnippets();
|
|
||||||
this.setState({
|
this.setState({
|
||||||
snippets : snippets
|
snippets : this.compileSnippets()
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
},
|
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
historyExists : historyExists(this.props.brew)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
mergeCustomizer : function(oldValue, newValue, key) {
|
mergeCustomizer : function(oldValue, newValue, key) {
|
||||||
if(key == 'snippets') {
|
if(key == 'snippets') {
|
||||||
const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme
|
const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme
|
||||||
return _.filter(result, 'gen'); //Only keep snippets with a 'gen' property.
|
return result.filter((snip)=>snip.gen || snip.subsnippets);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -138,6 +144,36 @@ const Snippetbar = createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
replaceContent : function(item){
|
||||||
|
return this.props.updateBrew(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHistoryItems : function() {
|
||||||
|
const historyItems = getHistoryItems(this.props.brew);
|
||||||
|
|
||||||
|
return <div className='dropdown'>
|
||||||
|
{_.map(historyItems, (item, index)=>{
|
||||||
|
if(!item.savedAt) return;
|
||||||
|
|
||||||
|
const saveTime = new Date(item.savedAt);
|
||||||
|
const diffMs = new Date() - saveTime;
|
||||||
|
const diffSecs = Math.floor(diffMs / 1000);
|
||||||
|
|
||||||
|
let diffString = `about ${diffSecs} seconds ago`;
|
||||||
|
|
||||||
|
if(diffSecs > 60) diffString = `about ${Math.floor(diffSecs / 60)} minutes ago`;
|
||||||
|
if(diffSecs > (60 * 60)) diffString = `about ${Math.floor(diffSecs / (60 * 60))} hours ago`;
|
||||||
|
if(diffSecs > (24 * 60 * 60)) diffString = `about ${Math.floor(diffSecs / (24 * 60 * 60))} days ago`;
|
||||||
|
if(diffSecs > (7 * 24 * 60 * 60)) diffString = `about ${Math.floor(diffSecs / (7 * 24 * 60 * 60))} weeks ago`;
|
||||||
|
|
||||||
|
return <div className='snippet' key={index} onClick={()=>{this.replaceContent(item);}} >
|
||||||
|
<i className={`fas fa-${index+1}`} />
|
||||||
|
<span className='name' title={saveTime.toISOString()}>v{item.version} : {diffString}</span>
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
renderEditorButtons : function(){
|
renderEditorButtons : function(){
|
||||||
if(!this.props.showEditButtons) return;
|
if(!this.props.showEditButtons) return;
|
||||||
|
|
||||||
@@ -158,6 +194,10 @@ const Snippetbar = createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <div className='editors'>
|
return <div className='editors'>
|
||||||
|
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`} >
|
||||||
|
<i className='fas fa-clock-rotate-left' />
|
||||||
|
{this.state.historyExists && this.renderHistoryItems() }
|
||||||
|
</div>
|
||||||
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
|
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
|
||||||
onClick={this.props.undo} >
|
onClick={this.props.undo} >
|
||||||
<i className='fas fa-undo' />
|
<i className='fas fa-undo' />
|
||||||
|
|||||||
@@ -53,6 +53,21 @@
|
|||||||
font-size : 0.75em;
|
font-size : 0.75em;
|
||||||
color : inherit;
|
color : inherit;
|
||||||
}
|
}
|
||||||
|
&.history {
|
||||||
|
.tooltipLeft('History');
|
||||||
|
font-size : 0.75em;
|
||||||
|
color : grey;
|
||||||
|
position : relative;
|
||||||
|
&.active {
|
||||||
|
color : inherit;
|
||||||
|
}
|
||||||
|
&>.dropdown{
|
||||||
|
right : -1px;
|
||||||
|
&>.snippet{
|
||||||
|
padding-right : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
&.editorTheme {
|
&.editorTheme {
|
||||||
.tooltipLeft('Editor Themes');
|
.tooltipLeft('Editor Themes');
|
||||||
font-size : 0.75em;
|
font-size : 0.75em;
|
||||||
|
|||||||
@@ -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} />} />
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ const ErrorNavItem = createClass({
|
|||||||
Looks like there was a problem retreiving
|
Looks like there was a problem retreiving
|
||||||
the theme, or a theme that it inherits,
|
the theme, or a theme that it inherits,
|
||||||
for this brew. Verify that brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
|
for this brew. Verify that brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
|
||||||
{response.body.brewId}</a> still exists!
|
{response.body.brewId}</a> still exists!
|
||||||
</div>
|
</div>
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
|
|||||||
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'
|
||||||
{index < brew.authors.length - 1 && ', '}
|
? <span title="Username contained an email address; hidden to protect user's privacy">{author}</span>
|
||||||
</>))}
|
: <a href={`/user/${author}`}>{author}</a>
|
||||||
|
}
|
||||||
|
{index < brew.authors.length - 1 && ', '}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
require('./editPage.less');
|
require('./editPage.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const createClass = require('create-react-class');
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
|
||||||
const request = require('../../utils/request-middleware.js');
|
const request = require('../../utils/request-middleware.js');
|
||||||
const { Meta } = require('vitreum/headtags');
|
const { Meta } = require('vitreum/headtags');
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ const Markdown = require('naturalcrit/markdown.js');
|
|||||||
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
|
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
|
||||||
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
|
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
|
||||||
|
|
||||||
|
import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js';
|
||||||
|
|
||||||
const googleDriveIcon = require('../../googleDrive.svg');
|
const googleDriveIcon = require('../../googleDrive.svg');
|
||||||
|
|
||||||
const SAVE_TIMEOUT = 3000;
|
const SAVE_TIMEOUT = 3000;
|
||||||
@@ -41,22 +44,24 @@ const EditPage = createClass({
|
|||||||
|
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
brew : this.props.brew,
|
brew : this.props.brew,
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
isPending : false,
|
isPending : false,
|
||||||
alertTrashedGoogleBrew : this.props.brew.trashed,
|
alertTrashedGoogleBrew : this.props.brew.trashed,
|
||||||
alertLoginToTransfer : false,
|
alertLoginToTransfer : false,
|
||||||
saveGoogle : this.props.brew.googleId ? true : false,
|
saveGoogle : this.props.brew.googleId ? true : false,
|
||||||
confirmGoogleTransfer : false,
|
confirmGoogleTransfer : false,
|
||||||
error : null,
|
error : null,
|
||||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
htmlErrors : Markdown.validate(this.props.brew.text),
|
||||||
url : '',
|
url : '',
|
||||||
autoSave : true,
|
autoSave : true,
|
||||||
autoSaveWarning : false,
|
autoSaveWarning : false,
|
||||||
unsavedTime : new Date(),
|
unsavedTime : new Date(),
|
||||||
currentEditorPage : 0,
|
currentEditorViewPageNum : 1,
|
||||||
displayLockMessage : this.props.brew.lock || false,
|
currentEditorCursorPageNum : 1,
|
||||||
themeBundle : {}
|
currentBrewRendererPageNum : 1,
|
||||||
|
displayLockMessage : this.props.brew.lock || false,
|
||||||
|
themeBundle : {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -113,16 +118,27 @@ const EditPage = createClass({
|
|||||||
this.editor.current.update();
|
this.editor.current.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleEditorViewPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentEditorViewPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEditorCursorPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentEditorCursorPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
|
handleBrewRendererPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentBrewRendererPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleTextChange : function(text){
|
||||||
//If there are errors, run the validator on every change to give quick feedback
|
//If there are errors, run the validator on every change to give quick feedback
|
||||||
let htmlErrors = this.state.htmlErrors;
|
let htmlErrors = this.state.htmlErrors;
|
||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
brew : { ...prevState.brew, text: text },
|
brew : { ...prevState.brew, text: text },
|
||||||
isPending : true,
|
isPending : true,
|
||||||
htmlErrors : htmlErrors,
|
htmlErrors : htmlErrors,
|
||||||
currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
|
|
||||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -150,6 +166,16 @@ const EditPage = createClass({
|
|||||||
return !_.isEqual(this.state.brew, this.savedBrew);
|
return !_.isEqual(this.state.brew, this.savedBrew);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateBrew : function(newData){
|
||||||
|
this.setState((prevState)=>({
|
||||||
|
brew : {
|
||||||
|
...prevState.brew,
|
||||||
|
style : newData.style,
|
||||||
|
text : newData.text
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
trySave : function(immediate=false){
|
trySave : function(immediate=false){
|
||||||
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
||||||
if(this.hasChanges()){
|
if(this.hasChanges()){
|
||||||
@@ -202,6 +228,9 @@ const EditPage = createClass({
|
|||||||
htmlErrors : Markdown.validate(prevState.brew.text)
|
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
updateHistory(this.state.brew);
|
||||||
|
versionHistoryGarbageCollection();
|
||||||
|
|
||||||
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
|
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
|
||||||
|
|
||||||
const brew = this.state.brew;
|
const brew = this.state.brew;
|
||||||
@@ -413,6 +442,12 @@ const EditPage = createClass({
|
|||||||
renderer={this.state.brew.renderer}
|
renderer={this.state.brew.renderer}
|
||||||
userThemes={this.props.userThemes}
|
userThemes={this.props.userThemes}
|
||||||
snippetBundle={this.state.themeBundle.snippets}
|
snippetBundle={this.state.themeBundle.snippets}
|
||||||
|
updateBrew={this.updateBrew}
|
||||||
|
onCursorPageChange={this.handleEditorCursorPageChange}
|
||||||
|
onViewPageChange={this.handleEditorViewPageChange}
|
||||||
|
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||||
|
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
||||||
|
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
||||||
/>
|
/>
|
||||||
<BrewRenderer
|
<BrewRenderer
|
||||||
text={this.state.brew.text}
|
text={this.state.brew.text}
|
||||||
@@ -422,7 +457,10 @@ const EditPage = createClass({
|
|||||||
themeBundle={this.state.themeBundle}
|
themeBundle={this.state.themeBundle}
|
||||||
errors={this.state.htmlErrors}
|
errors={this.state.htmlErrors}
|
||||||
lang={this.state.brew.lang}
|
lang={this.state.brew.lang}
|
||||||
currentEditorPage={this.state.currentEditorPage}
|
onPageChange={this.handleBrewRendererPageChange}
|
||||||
|
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||||
|
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
||||||
|
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
|||||||
@@ -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.`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
require('./homePage.less');
|
require('./homePage.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const createClass = require('create-react-class');
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
const request = require('../../utils/request-middleware.js');
|
const request = require('../../utils/request-middleware.js');
|
||||||
const { Meta } = require('vitreum/headtags');
|
const { Meta } = require('vitreum/headtags');
|
||||||
@@ -10,12 +9,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');
|
||||||
@@ -32,11 +31,13 @@ const HomePage = createClass({
|
|||||||
},
|
},
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
brew : this.props.brew,
|
brew : this.props.brew,
|
||||||
welcomeText : this.props.brew.text,
|
welcomeText : this.props.brew.text,
|
||||||
error : undefined,
|
error : undefined,
|
||||||
currentEditorPage : 0,
|
currentEditorViewPageNum : 1,
|
||||||
themeBundle : {}
|
currentEditorCursorPageNum : 1,
|
||||||
|
currentBrewRendererPageNum : 1,
|
||||||
|
themeBundle : {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -61,10 +62,22 @@ const HomePage = createClass({
|
|||||||
handleSplitMove : function(){
|
handleSplitMove : function(){
|
||||||
this.editor.current.update();
|
this.editor.current.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleEditorViewPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentEditorViewPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEditorCursorPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentEditorCursorPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
|
handleBrewRendererPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentBrewRendererPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleTextChange : function(text){
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
brew : { ...prevState.brew, text: text },
|
brew : { ...prevState.brew, text: text },
|
||||||
currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
|
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
renderNavbar : function(){
|
renderNavbar : function(){
|
||||||
@@ -76,6 +89,7 @@ const HomePage = createClass({
|
|||||||
}
|
}
|
||||||
<NewBrewItem />
|
<NewBrewItem />
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
|
<VaultNavItem />
|
||||||
<RecentNavItem />
|
<RecentNavItem />
|
||||||
<AccountNavItem />
|
<AccountNavItem />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
@@ -96,12 +110,20 @@ const HomePage = createClass({
|
|||||||
renderer={this.state.brew.renderer}
|
renderer={this.state.brew.renderer}
|
||||||
showEditButtons={false}
|
showEditButtons={false}
|
||||||
snippetBundle={this.state.themeBundle.snippets}
|
snippetBundle={this.state.themeBundle.snippets}
|
||||||
|
onCursorPageChange={this.handleEditorCursorPageChange}
|
||||||
|
onViewPageChange={this.handleEditorViewPageChange}
|
||||||
|
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||||
|
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
||||||
|
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
||||||
/>
|
/>
|
||||||
<BrewRenderer
|
<BrewRenderer
|
||||||
text={this.state.brew.text}
|
text={this.state.brew.text}
|
||||||
style={this.state.brew.style}
|
style={this.state.brew.style}
|
||||||
renderer={this.state.brew.renderer}
|
renderer={this.state.brew.renderer}
|
||||||
currentEditorPage={this.state.currentEditorPage}
|
onPageChange={this.handleBrewRendererPageChange}
|
||||||
|
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||||
|
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
||||||
|
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
||||||
themeBundle={this.state.themeBundle}
|
themeBundle={this.state.themeBundle}
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
|||||||
@@ -39,13 +39,15 @@ const NewPage = createClass({
|
|||||||
const brew = this.props.brew;
|
const brew = this.props.brew;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
brew : brew,
|
brew : brew,
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
saveGoogle : (global.account && global.account.googleId ? true : false),
|
saveGoogle : (global.account && global.account.googleId ? true : false),
|
||||||
error : null,
|
error : null,
|
||||||
htmlErrors : Markdown.validate(brew.text),
|
htmlErrors : Markdown.validate(brew.text),
|
||||||
currentEditorPage : 0,
|
currentEditorViewPageNum : 1,
|
||||||
themeBundle : {}
|
currentEditorCursorPageNum : 1,
|
||||||
|
currentBrewRendererPageNum : 1,
|
||||||
|
themeBundle : {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -108,15 +110,26 @@ const NewPage = createClass({
|
|||||||
this.editor.current.update();
|
this.editor.current.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleEditorViewPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentEditorViewPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEditorCursorPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentEditorCursorPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
|
handleBrewRendererPageChange : function(pageNumber){
|
||||||
|
this.setState({ currentBrewRendererPageNum: pageNumber });
|
||||||
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleTextChange : function(text){
|
||||||
//If there are errors, run the validator on every change to give quick feedback
|
//If there are errors, run the validator on every change to give quick feedback
|
||||||
let htmlErrors = this.state.htmlErrors;
|
let htmlErrors = this.state.htmlErrors;
|
||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
brew : { ...prevState.brew, text: text },
|
brew : { ...prevState.brew, text: text },
|
||||||
htmlErrors : htmlErrors,
|
htmlErrors : htmlErrors,
|
||||||
currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
|
|
||||||
}));
|
}));
|
||||||
localStorage.setItem(BREWKEY, text);
|
localStorage.setItem(BREWKEY, text);
|
||||||
},
|
},
|
||||||
@@ -221,6 +234,11 @@ const NewPage = createClass({
|
|||||||
renderer={this.state.brew.renderer}
|
renderer={this.state.brew.renderer}
|
||||||
userThemes={this.props.userThemes}
|
userThemes={this.props.userThemes}
|
||||||
snippetBundle={this.state.themeBundle.snippets}
|
snippetBundle={this.state.themeBundle.snippets}
|
||||||
|
onCursorPageChange={this.handleEditorCursorPageChange}
|
||||||
|
onViewPageChange={this.handleEditorViewPageChange}
|
||||||
|
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||||
|
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
||||||
|
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
||||||
/>
|
/>
|
||||||
<BrewRenderer
|
<BrewRenderer
|
||||||
text={this.state.brew.text}
|
text={this.state.brew.text}
|
||||||
@@ -230,7 +248,10 @@ const NewPage = createClass({
|
|||||||
themeBundle={this.state.themeBundle}
|
themeBundle={this.state.themeBundle}
|
||||||
errors={this.state.htmlErrors}
|
errors={this.state.htmlErrors}
|
||||||
lang={this.state.brew.lang}
|
lang={this.state.brew.lang}
|
||||||
currentEditorPage={this.state.currentEditorPage}
|
onPageChange={this.handleBrewRendererPageChange}
|
||||||
|
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
|
||||||
|
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
|
||||||
|
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
client/homebrew/utils/versionHistory.js
Normal file
116
client/homebrew/utils/versionHistory.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY';
|
||||||
|
export const HISTORY_SLOTS = 5;
|
||||||
|
|
||||||
|
// History values in minutes
|
||||||
|
const DEFAULT_HISTORY_SAVE_DELAYS = {
|
||||||
|
'0' : 0,
|
||||||
|
'1' : 2,
|
||||||
|
'2' : 10,
|
||||||
|
'3' : 60,
|
||||||
|
'4' : 12 * 60,
|
||||||
|
'5' : 2 * 24 * 60
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_GARBAGE_COLLECT_DELAY = 28 * 24 * 60;
|
||||||
|
|
||||||
|
const HISTORY_SAVE_DELAYS = global.config?.historyData?.HISTORY_SAVE_DELAYS ?? DEFAULT_HISTORY_SAVE_DELAYS;
|
||||||
|
const GARBAGE_COLLECT_DELAY = global.config?.historyData?.GARBAGE_COLLECT_DELAY ?? DEFAULT_GARBAGE_COLLECT_DELAY;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getKeyBySlot(brew, slot){
|
||||||
|
return `${HISTORY_PREFIX}-${brew.shareId}-${slot}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getVersionBySlot(brew, slot){
|
||||||
|
// Read stored brew data
|
||||||
|
// - If it exists, parse data to object
|
||||||
|
// - If it doesn't exist, pass default object
|
||||||
|
const key = getKeyBySlot(brew, slot);
|
||||||
|
const storedVersion = localStorage.getItem(key);
|
||||||
|
const output = storedVersion ? JSON.parse(storedVersion) : { expireAt: '2000-01-01T00:00:00.000Z', shareId: brew.shareId, noData: true };
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateStoredBrew(brew, slot = 0) {
|
||||||
|
const archiveBrew = {
|
||||||
|
title : brew.title,
|
||||||
|
text : brew.text,
|
||||||
|
style : brew.style,
|
||||||
|
version : brew.version,
|
||||||
|
shareId : brew.shareId,
|
||||||
|
savedAt : brew?.savedAt || new Date(),
|
||||||
|
expireAt : new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]);
|
||||||
|
|
||||||
|
const key = getKeyBySlot(brew, slot);
|
||||||
|
localStorage.setItem(key, JSON.stringify(archiveBrew));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function historyExists(brew){
|
||||||
|
return Object.keys(localStorage)
|
||||||
|
.some((key)=>{
|
||||||
|
return key.startsWith(`${HISTORY_PREFIX}-${brew.shareId}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadHistory(brew){
|
||||||
|
const history = {};
|
||||||
|
|
||||||
|
// Load data from local storage to History object
|
||||||
|
for (let i = 1; i <= HISTORY_SLOTS; i++){
|
||||||
|
history[i] = getVersionBySlot(brew, i);
|
||||||
|
};
|
||||||
|
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateHistory(brew) {
|
||||||
|
const history = loadHistory(brew);
|
||||||
|
|
||||||
|
// Walk each version position
|
||||||
|
for (let slot = HISTORY_SLOTS; slot > 0; slot--){
|
||||||
|
const storedVersion = history[slot];
|
||||||
|
|
||||||
|
// If slot has expired, update all lower slots and break
|
||||||
|
if(new Date() >= new Date(storedVersion.expireAt)){
|
||||||
|
for (let updateSlot = slot - 1; updateSlot>0; updateSlot--){
|
||||||
|
// Move data from updateSlot to updateSlot + 1
|
||||||
|
!history[updateSlot]?.noData && updateStoredBrew(history[updateSlot], updateSlot + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the most recent brew
|
||||||
|
updateStoredBrew(brew, 1);
|
||||||
|
|
||||||
|
// Break out of data checks because we found an expired value
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getHistoryItems(brew){
|
||||||
|
const historyArray = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= HISTORY_SLOTS; i++){
|
||||||
|
historyArray.push(getVersionBySlot(brew, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return historyArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function versionHistoryGarbageCollection(){
|
||||||
|
Object.keys(localStorage)
|
||||||
|
.filter((key)=>{
|
||||||
|
return key.startsWith(HISTORY_PREFIX);
|
||||||
|
})
|
||||||
|
.forEach((key)=>{
|
||||||
|
const collectAt = new Date(JSON.parse(localStorage.getItem(key)).savedAt);
|
||||||
|
collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY);
|
||||||
|
if(new Date() > collectAt){
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
232
package-lock.json
generated
232
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"version": "3.14.2",
|
"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": {
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"@babel/plugin-transform-runtime": "^7.25.4",
|
"@babel/plugin-transform-runtime": "^7.25.4",
|
||||||
"@babel/preset-env": "^7.25.4",
|
"@babel/preset-env": "^7.25.4",
|
||||||
"@babel/preset-react": "^7.24.7",
|
"@babel/preset-react": "^7.24.7",
|
||||||
"@googleapis/drive": "^8.13.1",
|
"@googleapis/drive": "^8.14.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"codemirror": "^5.65.6",
|
"codemirror": "^5.65.6",
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
"dedent-tabs": "^0.10.3",
|
"dedent-tabs": "^0.10.3",
|
||||||
"dompurify": "^3.1.6",
|
"dompurify": "^3.1.6",
|
||||||
"expr-eval": "^2.0.2",
|
"expr-eval": "^2.0.2",
|
||||||
"express": "^4.19.2",
|
"express": "^4.21.0",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.8",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
@@ -38,22 +38,22 @@
|
|||||||
"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.2",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-frame-component": "^4.1.3",
|
"react-frame-component": "^4.1.3",
|
||||||
"react-router-dom": "6.26.1",
|
"react-router-dom": "6.26.2",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"superagent": "^10.1.0",
|
"superagent": "^10.1.0",
|
||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^3.0.1",
|
"@stylistic/stylelint-plugin": "^3.0.1",
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^9.10.0",
|
||||||
"eslint-plugin-jest": "^28.8.0",
|
"eslint-plugin-jest": "^28.8.3",
|
||||||
"eslint-plugin-react": "^7.35.0",
|
"eslint-plugin-react": "^7.36.1",
|
||||||
"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",
|
||||||
@@ -2073,9 +2073,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.9.1",
|
"version": "9.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
|
||||||
"integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==",
|
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2090,10 +2090,22 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint/plugin-kit": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"levn": "^0.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@googleapis/drive": {
|
"node_modules/@googleapis/drive": {
|
||||||
"version": "8.13.1",
|
"version": "8.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.14.0.tgz",
|
||||||
"integrity": "sha512-ODfl4VUIKNox570DFA6AzAEHQcKI1EQs0xzzupeAIa+S/kFan85TItXU7XywK8mDORnfkgqwXQ5N/u/BFBj5lw==",
|
"integrity": "sha512-AOokfpP6pCdcJXWA8khaCEgbGpWYavWTdAAhL4idbbf2VCQcJ2f7vPalAYNu6a4Sfj0Ly4Ehnd1xw9J9TixB1A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"googleapis-common": "^7.0.0"
|
"googleapis-common": "^7.0.0"
|
||||||
},
|
},
|
||||||
@@ -3013,9 +3025,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@remix-run/router": {
|
"node_modules/@remix-run/router": {
|
||||||
"version": "1.19.1",
|
"version": "1.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
|
||||||
"integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==",
|
"integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
@@ -4128,10 +4140,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "3.1.2",
|
||||||
"content-type": "~1.0.5",
|
"content-type": "~1.0.5",
|
||||||
@@ -4141,7 +4152,7 @@
|
|||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"iconv-lite": "0.4.24",
|
"iconv-lite": "0.4.24",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"qs": "6.11.0",
|
"qs": "6.13.0",
|
||||||
"raw-body": "2.5.2",
|
"raw-body": "2.5.2",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
@@ -5589,10 +5600,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
"version": "1.0.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -5819,16 +5829,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.9.1",
|
"version": "9.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
|
||||||
"integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==",
|
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.18.0",
|
"@eslint/config-array": "^0.18.0",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.9.1",
|
"@eslint/js": "9.10.0",
|
||||||
|
"@eslint/plugin-kit": "^0.1.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
@@ -5851,7 +5862,6 @@
|
|||||||
"is-glob": "^4.0.0",
|
"is-glob": "^4.0.0",
|
||||||
"is-path-inside": "^3.0.3",
|
"is-path-inside": "^3.0.3",
|
||||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||||
"levn": "^0.4.1",
|
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -5878,9 +5888,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-jest": {
|
"node_modules/eslint-plugin-jest": {
|
||||||
"version": "28.8.0",
|
"version": "28.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.3.tgz",
|
||||||
"integrity": "sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw==",
|
"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 +5913,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-react": {
|
"node_modules/eslint-plugin-react": {
|
||||||
"version": "7.35.0",
|
"version": "7.36.1",
|
||||||
"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.36.1.tgz",
|
||||||
"integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==",
|
"integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==",
|
||||||
"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",
|
||||||
@@ -6306,37 +6315,36 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.19.2",
|
"version": "4.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
|
||||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"content-type": "~1.0.4",
|
"content-type": "~1.0.4",
|
||||||
"cookie": "0.6.0",
|
"cookie": "0.6.0",
|
||||||
"cookie-signature": "1.0.6",
|
"cookie-signature": "1.0.6",
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"etag": "~1.8.1",
|
"etag": "~1.8.1",
|
||||||
"finalhandler": "1.2.0",
|
"finalhandler": "1.3.1",
|
||||||
"fresh": "0.5.2",
|
"fresh": "0.5.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"merge-descriptors": "1.0.1",
|
"merge-descriptors": "1.0.3",
|
||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.7",
|
"path-to-regexp": "0.1.10",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.11.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
"safe-buffer": "5.2.1",
|
"safe-buffer": "5.2.1",
|
||||||
"send": "0.18.0",
|
"send": "0.19.0",
|
||||||
"serve-static": "1.15.0",
|
"serve-static": "1.16.2",
|
||||||
"setprototypeof": "1.2.0",
|
"setprototypeof": "1.2.0",
|
||||||
"statuses": "2.0.1",
|
"statuses": "2.0.1",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
@@ -6354,12 +6362,11 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/express-static-gzip": {
|
"node_modules/express-static-gzip": {
|
||||||
"version": "2.1.7",
|
"version": "2.1.8",
|
||||||
"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.8.tgz",
|
||||||
"integrity": "sha512-QOCZUC+lhPPCjIJKpQGu1Oa61Axg9Mq09Qvit8Of7kzpMuwDeMSqjjQteQS3OVw/GkENBoSBheuQDWPlngImvw==",
|
"integrity": "sha512-g8tiJuI9Y9Ffy59ehVXvqb0hhP83JwZiLxzanobPaMbkB5qBWA8nuVgd+rcd5qzH3GkgogTALlc0BaADYwnMbQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"serve-static": "^1.14.1"
|
"serve-static": "^1.16.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express/node_modules/cookie": {
|
"node_modules/express/node_modules/cookie": {
|
||||||
@@ -6575,13 +6582,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
@@ -6596,7 +6602,6 @@
|
|||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
@@ -6604,8 +6609,7 @@
|
|||||||
"node_modules/finalhandler/node_modules/ms": {
|
"node_modules/finalhandler/node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/find-up": {
|
"node_modules/find-up": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
@@ -10524,10 +10528,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
|
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||||
"license": "MIT"
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/merge-stream": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -10795,9 +10801,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose": {
|
"node_modules/mongoose": {
|
||||||
"version": "8.6.0",
|
"version": "8.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.2.tgz",
|
||||||
"integrity": "sha512-p6VSbYKvD4ZIabqo8C0kS5eKX1Xpji+opTAIJ9wyuPJ8Y/FblgXSMnFRXnB40bYZLKPQT089K5KU8+bqIXtFdw==",
|
"integrity": "sha512-ErbDVvuUzUfyQpXvJ6sXznmZDICD8r6wIsa0VKjJtB6/LZncqwUn5Um040G1BaNo6L3Jz+xItLSwT0wZmSmUaQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^6.7.0",
|
"bson": "^6.7.0",
|
||||||
"kareem": "2.6.3",
|
"kareem": "2.6.3",
|
||||||
@@ -11638,10 +11644,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
|
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/path-type": {
|
"node_modules/path-type": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -12075,12 +12080,11 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.11.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.0.4"
|
"side-channel": "^1.0.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
@@ -12205,11 +12209,11 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "6.26.1",
|
"version": "6.26.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz",
|
||||||
"integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==",
|
"integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/router": "1.19.1"
|
"@remix-run/router": "1.19.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -12219,12 +12223,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "6.26.1",
|
"version": "6.26.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz",
|
||||||
"integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==",
|
"integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/router": "1.19.1",
|
"@remix-run/router": "1.19.2",
|
||||||
"react-router": "6.26.1"
|
"react-router": "6.26.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -12722,10 +12726,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.18.0",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
@@ -12749,7 +12752,6 @@
|
|||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
@@ -12757,25 +12759,30 @@
|
|||||||
"node_modules/send/node_modules/debug/node_modules/ms": {
|
"node_modules/send/node_modules/debug/node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
"license": "MIT"
|
},
|
||||||
|
"node_modules/send/node_modules/encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/send/node_modules/ms": {
|
"node_modules/send/node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "1.15.0",
|
"version": "1.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"send": "0.18.0"
|
"send": "0.19.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
@@ -14617,21 +14624,6 @@
|
|||||||
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
|
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/url/node_modules/qs": {
|
|
||||||
"version": "6.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
|
||||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"side-channel": "^1.0.6"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/use": {
|
"node_modules/use": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -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.2",
|
"version": "3.15.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": "^10.2.x",
|
"npm": "^10.2.x",
|
||||||
"node": "^20.8.x"
|
"node": "^20.8.x"
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"@babel/plugin-transform-runtime": "^7.25.4",
|
"@babel/plugin-transform-runtime": "^7.25.4",
|
||||||
"@babel/preset-env": "^7.25.4",
|
"@babel/preset-env": "^7.25.4",
|
||||||
"@babel/preset-react": "^7.24.7",
|
"@babel/preset-react": "^7.24.7",
|
||||||
"@googleapis/drive": "^8.13.1",
|
"@googleapis/drive": "^8.14.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"codemirror": "^5.65.6",
|
"codemirror": "^5.65.6",
|
||||||
@@ -98,9 +98,9 @@
|
|||||||
"dedent-tabs": "^0.10.3",
|
"dedent-tabs": "^0.10.3",
|
||||||
"dompurify": "^3.1.6",
|
"dompurify": "^3.1.6",
|
||||||
"expr-eval": "^2.0.2",
|
"expr-eval": "^2.0.2",
|
||||||
"express": "^4.19.2",
|
"express": "^4.21.0",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.8",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
@@ -113,22 +113,22 @@
|
|||||||
"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.2",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-frame-component": "^4.1.3",
|
"react-frame-component": "^4.1.3",
|
||||||
"react-router-dom": "6.26.1",
|
"react-router-dom": "6.26.2",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"superagent": "^10.1.0",
|
"superagent": "^10.1.0",
|
||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^3.0.1",
|
"@stylistic/stylelint-plugin": "^3.0.1",
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^9.10.0",
|
||||||
"eslint-plugin-jest": "^28.8.0",
|
"eslint-plugin-jest": "^28.8.3",
|
||||||
"eslint-plugin-react": "^7.35.0",
|
"eslint-plugin-react": "^7.36.1",
|
||||||
"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",
|
||||||
|
|||||||
1094
server/app.js
1094
server/app.js
File diff suppressed because it is too large
Load Diff
@@ -99,7 +99,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
|
||||||
|
|||||||
@@ -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' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -934,7 +934,7 @@ brew`);
|
|||||||
expect(req.brew).toEqual(testBrew);
|
expect(req.brew).toEqual(testBrew);
|
||||||
expect(req.brew).toHaveProperty('style', '\nI Have a style!\n');
|
expect(req.brew).toHaveProperty('style', '\nI Have a style!\n');
|
||||||
expect(res.status).toHaveBeenCalledWith(200);
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
expect(res.send).toHaveBeenCalledWith("\nI Have a style!\n");
|
expect(res.send).toHaveBeenCalledWith('\nI Have a style!\n');
|
||||||
expect(res.set).toHaveBeenCalledWith({
|
expect(res.set).toHaveBeenCalledWith({
|
||||||
'Cache-Control' : 'no-cache',
|
'Cache-Control' : 'no-cache',
|
||||||
'Content-Type' : 'text/css'
|
'Content-Type' : 'text/css'
|
||||||
|
|||||||
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;
|
||||||
@@ -397,6 +397,11 @@ const CodeEditor = createClass({
|
|||||||
getCursorPosition : function(){
|
getCursorPosition : function(){
|
||||||
return this.codeMirror.getCursor();
|
return this.codeMirror.getCursor();
|
||||||
},
|
},
|
||||||
|
getTopVisibleLine : function(){
|
||||||
|
const rect = this.codeMirror.getWrapperElement().getBoundingClientRect();
|
||||||
|
const topVisibleLine = this.codeMirror.lineAtHeight(rect.top, 'window');
|
||||||
|
return topVisibleLine;
|
||||||
|
},
|
||||||
updateSize : function(){
|
updateSize : function(){
|
||||||
this.codeMirror.refresh();
|
this.codeMirror.refresh();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -105,16 +105,16 @@ renderer.link = function (href, title, text) {
|
|||||||
// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS
|
// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS
|
||||||
renderer.image = function (href, title, text) {
|
renderer.image = function (href, title, text) {
|
||||||
href = cleanUrl(href);
|
href = cleanUrl(href);
|
||||||
if (href === null)
|
if(href === null)
|
||||||
return text;
|
return text;
|
||||||
|
|
||||||
let out = `<img src="${href}" alt="${text}" style="--HB_src:url(${href});"`;
|
let out = `<img src="${href}" alt="${text}" style="--HB_src:url(${href});"`;
|
||||||
if (title)
|
if(title)
|
||||||
out += ` title="${title}"`;
|
out += ` title="${title}"`;
|
||||||
|
|
||||||
out += '>';
|
out += '>';
|
||||||
return out;
|
return out;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Disable default reflink behavior, as it steps on our variables extension
|
// Disable default reflink behavior, as it steps on our variables extension
|
||||||
tokenizer.def = function () {
|
tokenizer.def = function () {
|
||||||
@@ -745,7 +745,7 @@ const tableTerminators = [
|
|||||||
`:+\\n`, // hardBreak
|
`:+\\n`, // hardBreak
|
||||||
` *{[^\n]+}`, // blockInjector
|
` *{[^\n]+}`, // blockInjector
|
||||||
` *{{[^{\n]*\n.*?\n}}` // mustacheDiv
|
` *{{[^{\n]*\n.*?\n}}` // mustacheDiv
|
||||||
]
|
];
|
||||||
|
|
||||||
Marked.use(MarkedVariables());
|
Marked.use(MarkedVariables());
|
||||||
Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, superSubScripts,
|
Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, superSubScripts,
|
||||||
@@ -755,12 +755,12 @@ Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
|
|||||||
Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
|
Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
|
||||||
|
|
||||||
function cleanUrl(href) {
|
function cleanUrl(href) {
|
||||||
try {
|
try {
|
||||||
href = encodeURI(href).replace(/%25/g, '%');
|
href = encodeURI(href).replace(/%25/g, '%');
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return href;
|
return href;
|
||||||
}
|
}
|
||||||
|
|
||||||
const escapeTest = /[&<>"']/;
|
const escapeTest = /[&<>"']/;
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ const SplitPane = createClass({
|
|||||||
displayName : 'SplitPane',
|
displayName : 'SplitPane',
|
||||||
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
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ const SplitPane = createClass({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', this.handleWindowResize);
|
window.addEventListener('resize', this.handleWindowResize);
|
||||||
|
|
||||||
|
// This lives here instead of in the initial render because you cannot touch localStorage until the componant mounts.
|
||||||
|
const loadLiveScroll = window.localStorage.getItem('liveScroll') === 'true';
|
||||||
|
this.setState({ liveScroll: loadLiveScroll });
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount : function() {
|
componentWillUnmount : function() {
|
||||||
@@ -88,6 +93,11 @@ const SplitPane = createClass({
|
|||||||
userSetDividerPos : newSize
|
userSetDividerPos : newSize
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
liveScrollToggle : function() {
|
||||||
|
window.localStorage.setItem('liveScroll', String(!this.state.liveScroll));
|
||||||
|
this.setState({ liveScroll: !this.state.liveScroll });
|
||||||
|
},
|
||||||
/*
|
/*
|
||||||
unFocus : function() {
|
unFocus : function() {
|
||||||
if(document.selection){
|
if(document.selection){
|
||||||
@@ -119,13 +129,18 @@ const SplitPane = createClass({
|
|||||||
onClick={()=>this.setState({ moveBrew: !this.state.moveBrew })} >
|
onClick={()=>this.setState({ moveBrew: !this.state.moveBrew })} >
|
||||||
<i className='fas fa-arrow-right' />
|
<i className='fas fa-arrow-right' />
|
||||||
</div>
|
</div>
|
||||||
|
<div id='scrollToggleDiv' className={this.state.liveScroll ? 'arrow lock' : 'arrow unlock'}
|
||||||
|
style={{ left: this.state.currentDividerPos-4 }}
|
||||||
|
onClick={this.liveScrollToggle} >
|
||||||
|
<i id='scrollToggle' className={this.state.liveScroll ? 'fas fa-lock' : 'fas fa-unlock'} />
|
||||||
|
</div>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
renderDivider : function(){
|
renderDivider : function(){
|
||||||
return <>
|
return <>
|
||||||
{this.renderMoveArrows()}
|
{this.props.showDividerButtons && this.renderMoveArrows()}
|
||||||
<div className='divider' onPointerDown={this.handleDown} >
|
<div className='divider' onPointerDown={this.handleDown} >
|
||||||
<div className='dots'>
|
<div className='dots'>
|
||||||
<i className='fas fa-circle' />
|
<i className='fas fa-circle' />
|
||||||
@@ -142,9 +157,12 @@ const SplitPane = createClass({
|
|||||||
width={this.state.currentDividerPos}
|
width={this.state.currentDividerPos}
|
||||||
>
|
>
|
||||||
{React.cloneElement(this.props.children[0], {
|
{React.cloneElement(this.props.children[0], {
|
||||||
moveBrew : this.state.moveBrew,
|
...(this.props.showDividerButtons && {
|
||||||
moveSource : this.state.moveSource,
|
moveBrew : this.state.moveBrew,
|
||||||
setMoveArrows : this.setMoveArrows
|
moveSource : this.state.moveSource,
|
||||||
|
liveScroll : this.state.liveScroll,
|
||||||
|
setMoveArrows : this.setMoveArrows,
|
||||||
|
}),
|
||||||
})}
|
})}
|
||||||
</Pane>
|
</Pane>
|
||||||
{this.renderDivider()}
|
{this.renderDivider()}
|
||||||
|
|||||||
@@ -53,6 +53,15 @@
|
|||||||
.tooltipRight('Jump to location in Preview');
|
.tooltipRight('Jump to location in Preview');
|
||||||
top : 60px;
|
top : 60px;
|
||||||
}
|
}
|
||||||
|
&.lock{
|
||||||
|
.tooltipRight('De-sync Editor and Preview locations.');
|
||||||
|
top : 90px;
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
&.unlock{
|
||||||
|
.tooltipRight('Sync Editor and Preview locations');
|
||||||
|
top : 90px;
|
||||||
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
background-color: #666;
|
background-color: #666;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,35 +27,154 @@ module.exports = [
|
|||||||
experimental : true,
|
experimental : true,
|
||||||
subsnippets : [
|
subsnippets : [
|
||||||
{
|
{
|
||||||
name : 'Table of Contents',
|
name : 'Generate Table of Contents',
|
||||||
icon : 'fas fa-book',
|
icon : 'fas fa-book',
|
||||||
gen : TableOfContentsGen,
|
gen : TableOfContentsGen,
|
||||||
experimental : true
|
experimental : true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Include in ToC up to H3',
|
name : 'Table of Contents Individual Inclusion',
|
||||||
icon : 'fas fa-dice-three',
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocInclude# CHANGE # to your header level
|
||||||
|
}}\n`,
|
||||||
|
subsnippets : [
|
||||||
|
{
|
||||||
|
name : 'Individual Inclusion H1',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocIncludeH1 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Inclusion H2',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocIncludeH2 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Inclusion H3',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocIncludeH3 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Inclusion H4',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocIncludeH4 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Inclusion H5',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocIncludeH5 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Inclusion H6',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocIncludeH6 \n
|
||||||
|
}}\n`,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Table of Contents Range Inclusion',
|
||||||
|
icon : 'fas fa-book',
|
||||||
gen : dedent `\n{{tocDepthH3
|
gen : dedent `\n{{tocDepthH3
|
||||||
}}\n`,
|
}}\n`,
|
||||||
|
subsnippets : [
|
||||||
|
{
|
||||||
|
name : 'Include in ToC up to H3',
|
||||||
|
icon : 'fas fa-dice-three',
|
||||||
|
gen : dedent `\n{{tocDepthH3
|
||||||
|
}}\n`,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Include in ToC up to H4',
|
||||||
|
icon : 'fas fa-dice-four',
|
||||||
|
gen : dedent `\n{{tocDepthH4
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Include in ToC up to H5',
|
||||||
|
icon : 'fas fa-dice-five',
|
||||||
|
gen : dedent `\n{{tocDepthH5
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Include in ToC up to H6',
|
||||||
|
icon : 'fas fa-dice-six',
|
||||||
|
gen : dedent `\n{{tocDepthH6
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Include in ToC up to H4',
|
name : 'Table of Contents Individual Exclusion',
|
||||||
icon : 'fas fa-dice-four',
|
icon : 'fas fa-book',
|
||||||
gen : dedent `\n{{tocDepthH4
|
gen : dedent `\n{{tocExcludeH1 \n
|
||||||
}}\n`,
|
}}\n`,
|
||||||
|
subsnippets : [
|
||||||
|
{
|
||||||
|
name : 'Individual Exclusion H1',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocExcludeH1 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Exclusion H2',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocExcludeH2 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Exclusion H3',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocExcludeH3 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Exclusion H4',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocExcludeH4 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Exclusion H5',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocExcludeH5 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Individual Exclusion H6',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : dedent `\n{{tocExcludeH6 \n
|
||||||
|
}}\n`,
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : 'Include in ToC up to H5',
|
name : 'Table of Contents Toggles',
|
||||||
icon : 'fas fa-dice-five',
|
icon : 'fas fa-book',
|
||||||
gen : dedent `\n{{tocDepthH5
|
gen : `{{tocGlobalH4}}\n\n`,
|
||||||
}}\n`,
|
subsnippets : [
|
||||||
},
|
{
|
||||||
{
|
name : 'Enable H1-H4 all pages',
|
||||||
name : 'Include in ToC up to H6',
|
icon : 'fas fa-dice-four',
|
||||||
icon : 'fas fa-dice-six',
|
gen : `{{tocGlobalH4}}\n\n`,
|
||||||
gen : dedent `\n{{tocDepthH6
|
},
|
||||||
}}\n`,
|
{
|
||||||
|
name : 'Enable H1-H5 all pages',
|
||||||
|
icon : 'fas fa-dice-five',
|
||||||
|
gen : `{{tocGlobalH5}}\n\n`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Enable H1-H6 all pages',
|
||||||
|
icon : 'fas fa-dice-six',
|
||||||
|
gen : `{{tocGlobalH6}}\n\n`,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -94,7 +213,7 @@ module.exports = [
|
|||||||
background-image: linear-gradient(-45deg, #322814, #998250, #322814);
|
background-image: linear-gradient(-45deg, #322814, #998250, #322814);
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}\n\n`
|
}\n\n`
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +1,78 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const dedent = require('dedent-tabs').default;
|
const dedent = require('dedent-tabs').default;
|
||||||
|
|
||||||
const getTOC = (pages)=>{
|
// Map each actual page to its footer label, accounting for skips or numbering resets
|
||||||
|
const mapPages = (pages)=>{
|
||||||
|
let actualPage = 0;
|
||||||
|
let mappedPage = 0; // Number displayed in footer
|
||||||
|
const pageMap = [];
|
||||||
|
|
||||||
const recursiveAdd = (title, page, targetDepth, child, curDepth=0)=>{
|
pages.forEach((page)=>{
|
||||||
if(curDepth > 5) return; // Something went wrong.
|
actualPage++;
|
||||||
if(curDepth == targetDepth) {
|
const doSkip = page.querySelector('.skipCounting');
|
||||||
child.push({
|
const doReset = page.querySelector('.resetCounting');
|
||||||
title : title,
|
|
||||||
page : page,
|
if(doReset)
|
||||||
children : []
|
mappedPage = 1;
|
||||||
});
|
if(!doSkip && !doReset)
|
||||||
} else {
|
mappedPage++;
|
||||||
if(child.length == 0) {
|
|
||||||
child.push({
|
pageMap[actualPage] = {
|
||||||
title : null,
|
mappedPage : mappedPage,
|
||||||
page : page,
|
showPage : !doSkip
|
||||||
children : []
|
};
|
||||||
});
|
});
|
||||||
|
return pageMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMarkdown = (headings, pageMap)=>{
|
||||||
|
const levelPad = ['- ###', ' - ####', ' -', ' -', ' -', ' -'];
|
||||||
|
|
||||||
|
const allMarkdown = [];
|
||||||
|
const depthChain = [0];
|
||||||
|
|
||||||
|
headings.forEach((heading)=>{
|
||||||
|
const page = parseInt(heading.closest('.page').id?.replace(/^p/, ''));
|
||||||
|
const mappedPage = pageMap[page].mappedPage;
|
||||||
|
const showPage = pageMap[page].showPage;
|
||||||
|
const title = heading.textContent.trim();
|
||||||
|
const ToCExclude = getComputedStyle(heading).getPropertyValue('--TOC');
|
||||||
|
const depth = parseInt(heading.tagName.substring(1));
|
||||||
|
|
||||||
|
if(!title || !showPage || ToCExclude == 'exclude')
|
||||||
|
return;
|
||||||
|
|
||||||
|
//If different header depth than last, remove indents until nearest higher-level header, then indent once
|
||||||
|
if(depth !== depthChain[depthChain.length -1]) {
|
||||||
|
while (depth <= depthChain[depthChain.length - 1]) {
|
||||||
|
depthChain.pop();
|
||||||
}
|
}
|
||||||
recursiveAdd(title, page, targetDepth, _.last(child).children, curDepth+1,);
|
depthChain.push(depth);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const res = [];
|
const markdown = `${levelPad[depthChain.length - 2]} [{{ ${title}}}{{ ${mappedPage}}}](#p${page})`;
|
||||||
|
allMarkdown.push(markdown);
|
||||||
|
});
|
||||||
|
return allMarkdown.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTOC = ()=>{
|
||||||
const iframe = document.getElementById('BrewRenderer');
|
const iframe = document.getElementById('BrewRenderer');
|
||||||
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
|
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
|
||||||
const headings = iframeDocument.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
const headings = iframeDocument.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
||||||
const headerDepth = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
|
const pages = iframeDocument.querySelectorAll('.page');
|
||||||
|
|
||||||
_.each(headings, (heading)=>{
|
const pageMap = mapPages(pages);
|
||||||
const onPage = parseInt(heading.closest('.page').id?.replace(/^p/, ''));
|
return getMarkdown(headings, pageMap);
|
||||||
const ToCExclude = getComputedStyle(heading).getPropertyValue('--TOC');
|
|
||||||
|
|
||||||
if(ToCExclude != 'exclude') {
|
|
||||||
recursiveAdd(heading.textContent.trim(), onPage, headerDepth.indexOf(heading.tagName), res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const ToCIterate = (entries, curDepth=0)=>{
|
|
||||||
const levelPad = ['- ###', ' - ####', ' - ', ' - ', ' - ', ' - '];
|
|
||||||
const toc = [];
|
|
||||||
if(entries.title !== null){
|
|
||||||
toc.push(`${levelPad[curDepth]} [{{ ${entries.title}}}{{ ${entries.page}}}](#p${entries.page})`);
|
|
||||||
}
|
|
||||||
if(entries.children.length) {
|
|
||||||
_.each(entries.children, (entry, idx)=>{
|
|
||||||
const children = ToCIterate(entry, entry.title == null ? curDepth : curDepth+1);
|
|
||||||
if(children.length) {
|
|
||||||
toc.push(...children);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return toc;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
const pages = props.brew.text.split('\\page');
|
const TOC = getTOC();
|
||||||
const TOC = getTOC(pages);
|
|
||||||
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
|
|
||||||
r.push(ToCIterate(g1).join('\n'));
|
|
||||||
return r;
|
|
||||||
}, []).join('\n');
|
|
||||||
|
|
||||||
return dedent`
|
return dedent`
|
||||||
{{toc,wide
|
{{toc,wide
|
||||||
# Contents
|
# Contents
|
||||||
|
|
||||||
${markdown}
|
${TOC}
|
||||||
}}
|
}}
|
||||||
\n`;
|
\n`;
|
||||||
};
|
};
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
--HB_Color_CaptionText : #766649; // Brown
|
--HB_Color_CaptionText : #766649; // Brown
|
||||||
--HB_Color_WatercolorStain : #BBAD82; // Light brown
|
--HB_Color_WatercolorStain : #BBAD82; // Light brown
|
||||||
--HB_Color_Footnotes : #C9AD6A; // Gold
|
--HB_Color_Footnotes : #C9AD6A; // Gold
|
||||||
|
--TOC : 'include';
|
||||||
}
|
}
|
||||||
|
|
||||||
.useSansSerif() {
|
.useSansSerif() {
|
||||||
@@ -797,7 +798,7 @@
|
|||||||
// *****************************/
|
// *****************************/
|
||||||
|
|
||||||
// Default Exclusions
|
// Default Exclusions
|
||||||
// Anything not exlcuded is included, default Headers are H1, H2, and H3.
|
// Anything not excluded is included, default Headers are H1, H2, and H3.
|
||||||
h4,
|
h4,
|
||||||
h5,
|
h5,
|
||||||
h6,
|
h6,
|
||||||
@@ -808,12 +809,23 @@ h6,
|
|||||||
.noToC,
|
.noToC,
|
||||||
.toc { --TOC: exclude; }
|
.toc { --TOC: exclude; }
|
||||||
|
|
||||||
.tocDepthH2 :is(h1, h2) {--TOC: include; }
|
|
||||||
.tocDepthH3 :is(h1, h2, h3) {--TOC: include; }
|
|
||||||
.tocDepthH4 :is(h1, h2, h3, h4) {--TOC: include; }
|
|
||||||
.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC: include; }
|
|
||||||
.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC: include; }
|
|
||||||
|
|
||||||
|
// Brew level default inclusion changes.
|
||||||
|
// These add Headers 'back' to inclusion.
|
||||||
|
.pages:has(.tocGlobalH4) {
|
||||||
|
h4 {--TOC: include; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages:has(.tocGlobalH5) {
|
||||||
|
h4, h5 {--TOC: include; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages:has(.tocGlobalH6) {
|
||||||
|
h4, h5, h6 {--TOC: include; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block level inclusion changes
|
||||||
|
// These include either a single (include) or a range (depth)
|
||||||
.tocIncludeH1 h1 {--TOC: include; }
|
.tocIncludeH1 h1 {--TOC: include; }
|
||||||
.tocIncludeH2 h2 {--TOC: include; }
|
.tocIncludeH2 h2 {--TOC: include; }
|
||||||
.tocIncludeH3 h3 {--TOC: include; }
|
.tocIncludeH3 h3 {--TOC: include; }
|
||||||
@@ -821,6 +833,21 @@ h6,
|
|||||||
.tocIncludeH5 h5 {--TOC: include; }
|
.tocIncludeH5 h5 {--TOC: include; }
|
||||||
.tocIncludeH6 h6 {--TOC: include; }
|
.tocIncludeH6 h6 {--TOC: include; }
|
||||||
|
|
||||||
|
.tocDepthH2 :is(h1, h2) {--TOC: include; }
|
||||||
|
.tocDepthH3 :is(h1, h2, h3) {--TOC: include; }
|
||||||
|
.tocDepthH4 :is(h1, h2, h3, h4) {--TOC: include; }
|
||||||
|
.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC: include; }
|
||||||
|
.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC: include; }
|
||||||
|
|
||||||
|
// Block level exclusion changes
|
||||||
|
// These exclude a single block level
|
||||||
|
.tocExcludeH1 h1 {--TOC: exclude; }
|
||||||
|
.tocExcludeH2 h2 {--TOC: exclude; }
|
||||||
|
.tocExcludeH3 h3 {--TOC: exclude; }
|
||||||
|
.tocExcludeH4 h4 {--TOC: exclude; }
|
||||||
|
.tocExcludeH5 h5 {--TOC: exclude; }
|
||||||
|
.tocExcludeH6 h6 {--TOC: exclude; }
|
||||||
|
|
||||||
.page:has(.partCover) {
|
.page:has(.partCover) {
|
||||||
--TOC: exclude;
|
--TOC: exclude;
|
||||||
& h1 {
|
& h1 {
|
||||||
|
|||||||
@@ -23,14 +23,30 @@ module.exports = [
|
|||||||
gen : '\n\\page\n'
|
gen : '\n\\page\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Page Number',
|
name : 'Page Numbering',
|
||||||
icon : 'fas fa-bookmark',
|
icon : 'fas fa-bookmark',
|
||||||
gen : '{{pageNumber 1}}\n'
|
subsnippets : [
|
||||||
},
|
{
|
||||||
{
|
name : 'Page Number',
|
||||||
name : 'Auto-incrementing Page Number',
|
icon : 'fas fa-bookmark',
|
||||||
icon : 'fas fa-sort-numeric-down',
|
gen : '{{pageNumber 1}}\n'
|
||||||
gen : '{{pageNumber,auto}}\n'
|
},
|
||||||
|
{
|
||||||
|
name : 'Auto-incrementing Page Number',
|
||||||
|
icon : 'fas fa-sort-numeric-down',
|
||||||
|
gen : '{{pageNumber,auto}}\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Skip Page Number Increment this Page',
|
||||||
|
icon : 'fas fa-xmark',
|
||||||
|
gen : '{{skipCounting}}\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Restart Numbering',
|
||||||
|
icon : 'fas fa-arrow-rotate-left',
|
||||||
|
gen : '{{resetCounting}}\n'
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Footer',
|
name : 'Footer',
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@page { margin : 0; }
|
@page { margin : 0; }
|
||||||
body { counter-reset : page-numbers; }
|
body { counter-reset : page-numbers 0; }
|
||||||
* { -webkit-print-color-adjust : exact; }
|
* { -webkit-print-color-adjust : exact; }
|
||||||
|
|
||||||
//*****************************
|
//*****************************
|
||||||
@@ -51,7 +51,6 @@ body { counter-reset : page-numbers; }
|
|||||||
height : 279.4mm;
|
height : 279.4mm;
|
||||||
padding : 1.4cm 1.9cm 1.7cm;
|
padding : 1.4cm 1.9cm 1.7cm;
|
||||||
overflow : hidden;
|
overflow : hidden;
|
||||||
counter-increment : page-numbers;
|
|
||||||
background-color : var(--HB_Color_Background);
|
background-color : var(--HB_Color_Background);
|
||||||
text-rendering : optimizeLegibility;
|
text-rendering : optimizeLegibility;
|
||||||
contain : size;
|
contain : size;
|
||||||
@@ -494,4 +493,13 @@ body { counter-reset : page-numbers; }
|
|||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
.pageNumber { left : 30px; }
|
.pageNumber { left : 30px; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.resetCounting {
|
||||||
|
counter-set : page-numbers 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:has(.skipCounting)) {
|
||||||
|
counter-increment : page-numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user