mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-04 03:52:40 +00:00
Merge branch 'master' into dependabot/npm_and_yarn/core-js-3.40.0
This commit is contained in:
@@ -16,8 +16,11 @@ const Frame = require('react-frame-component').default;
|
|||||||
const dedent = require('dedent-tabs').default;
|
const dedent = require('dedent-tabs').default;
|
||||||
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||||
|
|
||||||
|
import HeaderNav from './headerNav/headerNav.jsx';
|
||||||
|
|
||||||
import { safeHTML } from './safeHTML.js';
|
import { safeHTML } from './safeHTML.js';
|
||||||
|
|
||||||
|
|
||||||
const PAGE_HEIGHT = 1056;
|
const PAGE_HEIGHT = 1056;
|
||||||
|
|
||||||
const INITIAL_CONTENT = dedent`
|
const INITIAL_CONTENT = dedent`
|
||||||
@@ -50,8 +53,8 @@ const BrewPage = (props)=>{
|
|||||||
props.onVisibilityChange(props.index + 1, true, false); // add page to array of visible pages.
|
props.onVisibilityChange(props.index + 1, true, false); // add page to array of visible pages.
|
||||||
else
|
else
|
||||||
props.onVisibilityChange(props.index + 1, false, false);
|
props.onVisibilityChange(props.index + 1, false, false);
|
||||||
}
|
});
|
||||||
)},
|
},
|
||||||
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
|
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -61,8 +64,8 @@ const BrewPage = (props)=>{
|
|||||||
entries.forEach((entry)=>{
|
entries.forEach((entry)=>{
|
||||||
if(entry.isIntersecting)
|
if(entry.isIntersecting)
|
||||||
props.onVisibilityChange(props.index + 1, true, true); // Set this page as the center page
|
props.onVisibilityChange(props.index + 1, true, true); // Set this page as the center page
|
||||||
}
|
});
|
||||||
)},
|
},
|
||||||
{ threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center
|
{ threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -115,7 +118,10 @@ const BrewRenderer = (props)=>{
|
|||||||
pageShadows : true
|
pageShadows : true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [headerState, setHeaderState] = useState(false);
|
||||||
|
|
||||||
const mainRef = useRef(null);
|
const mainRef = useRef(null);
|
||||||
|
const pagesRef = useRef(null);
|
||||||
|
|
||||||
if(props.renderer == 'legacy') {
|
if(props.renderer == 'legacy') {
|
||||||
rawPages = props.text.split('\\page');
|
rawPages = props.text.split('\\page');
|
||||||
@@ -287,7 +293,7 @@ const BrewRenderer = (props)=>{
|
|||||||
<NotificationPopup />
|
<NotificationPopup />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length}/>
|
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
|
||||||
|
|
||||||
{/*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}
|
||||||
@@ -306,12 +312,13 @@ const BrewRenderer = (props)=>{
|
|||||||
&&
|
&&
|
||||||
<>
|
<>
|
||||||
{renderedStyle}
|
{renderedStyle}
|
||||||
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle}>
|
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle} ref={pagesRef}>
|
||||||
{renderedPages}
|
{renderedPages}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
|
||||||
</Frame>
|
</Frame>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
|
|
||||||
.pane { position : relative; }
|
.pane { position : relative; }
|
||||||
|
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
.toolBar { display : none; }
|
.toolBar { display : none; }
|
||||||
.brewRenderer {
|
.brewRenderer {
|
||||||
@@ -82,4 +83,7 @@
|
|||||||
& > .page { box-shadow : unset; }
|
& > .page { box-shadow : unset; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.headerNav {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
106
client/homebrew/brewRenderer/headerNav/headerNav.jsx
Normal file
106
client/homebrew/brewRenderer/headerNav/headerNav.jsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
require('./headerNav.less');
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
|
||||||
|
const MAX_TEXT_LENGTH = 40;
|
||||||
|
|
||||||
|
const HeaderNav = React.forwardRef(({}, pagesRef)=>{
|
||||||
|
|
||||||
|
const renderHeaderLinks = ()=>{
|
||||||
|
if(!pagesRef.current) return;
|
||||||
|
|
||||||
|
const selector = [
|
||||||
|
'.pages > .page', // All page elements, which by definition have IDs
|
||||||
|
'.page:not(:has(.toc)) > [id]', // All direct children of non-ToC .page with an ID (Legacy)
|
||||||
|
'.page:not(:has(.toc)) > .columnWrapper > [id]', // All direct children of non-ToC .page > .columnWrapper with an ID (V3)
|
||||||
|
'.page:not(:has(.toc)) h2', // All non-ToC H2 titles, like Monster frame titles
|
||||||
|
];
|
||||||
|
const elements = pagesRef.current.querySelectorAll(selector.join(','));
|
||||||
|
if(!elements) return;
|
||||||
|
const navList = [];
|
||||||
|
|
||||||
|
// navList is a list of objects which have the following structure:
|
||||||
|
// {
|
||||||
|
// depth : how deeply indented the item should be
|
||||||
|
// text : the text to display in the nav link
|
||||||
|
// link : the hyperlink to navigate to when clicked
|
||||||
|
// className : [optional] the class to apply to the nav link for styling
|
||||||
|
// }
|
||||||
|
|
||||||
|
elements.forEach((el)=>{
|
||||||
|
if(el.className.match(/\bpage\b/)) {
|
||||||
|
let text = `Page ${el.id.slice(1)}`; // The ID of a page *should* always be equal to `p` followed by the page number
|
||||||
|
if(el.querySelector('.toc')){ // If the page contains a table of contents, add "- Contents" to the display text
|
||||||
|
text += ' - Contents';
|
||||||
|
};
|
||||||
|
navList.push({
|
||||||
|
depth : 0, // Pages are always at the least indented level
|
||||||
|
text : text,
|
||||||
|
link : el.id,
|
||||||
|
className : 'pageLink'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
|
||||||
|
navList.push({
|
||||||
|
depth : el.localName[1], // Depth is set by the header level
|
||||||
|
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
|
||||||
|
link : el.id
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navList.push({
|
||||||
|
depth : 7, // All unmatched elements with IDs are set to the maximum depth (7)
|
||||||
|
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
|
||||||
|
link : el.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return _.map(navList, (navItem, index)=>{
|
||||||
|
return <HeaderNavItem {...navItem} key={index} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return <nav className='headerNav'>
|
||||||
|
<ul>
|
||||||
|
{renderHeaderLinks()}
|
||||||
|
</ul>
|
||||||
|
</nav>;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const HeaderNavItem = ({ link, text, depth, className })=>{
|
||||||
|
|
||||||
|
const trimString = (text, prefixLength = 0)=>{
|
||||||
|
// Sanity check nav link strings
|
||||||
|
let output = text;
|
||||||
|
|
||||||
|
// If the string has a line break, only use the first line
|
||||||
|
if(text.indexOf('\n')){
|
||||||
|
output = text.split('\n')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim unecessary spaces from string
|
||||||
|
output = output.trim();
|
||||||
|
|
||||||
|
// Reduce excessively long strings
|
||||||
|
const maxLength = MAX_TEXT_LENGTH - prefixLength;
|
||||||
|
if(output.length > maxLength){
|
||||||
|
return `${output.slice(0, maxLength).trim()}...`;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!link || !text) return;
|
||||||
|
|
||||||
|
return <li>
|
||||||
|
<a href={`#${link}`} target='_self' className={`depth-${depth} ${className ?? ''}`}>
|
||||||
|
{trimString(text, depth)}
|
||||||
|
</a>
|
||||||
|
</li>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderNav;
|
||||||
47
client/homebrew/brewRenderer/headerNav/headerNav.less
Normal file
47
client/homebrew/brewRenderer/headerNav/headerNav.less
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.headerNav {
|
||||||
|
position: fixed;
|
||||||
|
top: 32px;
|
||||||
|
left: 0px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
max-height: calc(100vh - 32px);
|
||||||
|
max-width: 40vw;
|
||||||
|
overflow-y: auto;
|
||||||
|
&.active {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
.navIcon {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navIcon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
&.pageLink {
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
@depths: 1,2,3,4,5,6,7;
|
||||||
|
|
||||||
|
each(@depths, {
|
||||||
|
&.depth-@{value} {
|
||||||
|
padding-left: ((@value - 1) * 0.5em);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anch
|
|||||||
const MAX_ZOOM = 300;
|
const MAX_ZOOM = 300;
|
||||||
const MIN_ZOOM = 10;
|
const MIN_ZOOM = 10;
|
||||||
|
|
||||||
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages })=>{
|
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages, headerState, setHeaderState })=>{
|
||||||
|
|
||||||
const [pageNum, setPageNum] = useState(1);
|
const [pageNum, setPageNum] = useState(1);
|
||||||
const [toolsVisible, setToolsVisible] = useState(true);
|
const [toolsVisible, setToolsVisible] = useState(true);
|
||||||
@@ -62,7 +62,7 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
|
|||||||
// 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.
|
||||||
if(displayOptions.spread === 'facing')
|
if(displayOptions.spread === 'facing')
|
||||||
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth / 2), Infinity); // if 'facing' spread, fit two pages in view
|
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth / 2), Infinity); // if 'facing' spread, fit two pages in view
|
||||||
else
|
else
|
||||||
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
|
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
|
||||||
|
|
||||||
desiredZoom = minDimRatio * 100;
|
desiredZoom = minDimRatio * 100;
|
||||||
@@ -76,7 +76,10 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
|
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
|
||||||
<button className='toggleButton' title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
|
<div className='toggleButton'>
|
||||||
|
<button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
|
||||||
|
<button title={`${headerState ? 'Hide' : 'Show'} Header Navigation`} onClick={()=>{setHeaderState(!headerState);}}><i className='fas fa-rectangle-list' /></button>
|
||||||
|
</div>
|
||||||
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
|
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
|
||||||
<div className='group' role='group' aria-label='Zoom' aria-hidden={!toolsVisible}>
|
<div className='group' role='group' aria-label='Zoom' aria-hidden={!toolsVisible}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -166,7 +166,7 @@
|
|||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
flex-wrap : nowrap;
|
flex-wrap : nowrap;
|
||||||
width : 32px;
|
width : 92px;
|
||||||
overflow : hidden;
|
overflow : hidden;
|
||||||
background-color : unset;
|
background-color : unset;
|
||||||
opacity : 0.5;
|
opacity : 0.5;
|
||||||
@@ -178,10 +178,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button.toggleButton {
|
.toggleButton {
|
||||||
position : absolute;
|
position : absolute;
|
||||||
left : 0;
|
left : 0;
|
||||||
z-index : 5;
|
z-index : 5;
|
||||||
width : 32px;
|
width : 32px;
|
||||||
min-width : unset;
|
min-width : unset;
|
||||||
|
height : 100%;
|
||||||
|
display : flex;
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,9 @@ const SharePage = (props)=>{
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
|
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
|
||||||
updateState({ currentBrewRendererPageNum: pageNumber });
|
setState((prevState)=>({
|
||||||
|
currentBrewRendererPageNum : pageNumber,
|
||||||
|
...prevState }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleControlKeys = (e)=>{
|
const handleControlKeys = (e)=>{
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -41,7 +41,7 @@
|
|||||||
"marked-smartypants-lite": "^1.0.2",
|
"marked-smartypants-lite": "^1.0.2",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^8.9.3",
|
"mongoose": "^8.9.4",
|
||||||
"nanoid": "5.0.9",
|
"nanoid": "5.0.9",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -10168,9 +10168,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose": {
|
"node_modules/mongoose": {
|
||||||
"version": "8.9.3",
|
"version": "8.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.4.tgz",
|
||||||
"integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==",
|
"integrity": "sha512-DndoI01aV/q40P9DiYDXsYjhj8vZjmmuFwcC3Tro5wFznoE1z6Fe2JgMnbLR6ghglym5ziYizSfAJykp+UPZWg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^6.10.1",
|
"bson": "^6.10.1",
|
||||||
"kareem": "2.6.3",
|
"kareem": "2.6.3",
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
"marked-smartypants-lite": "^1.0.2",
|
"marked-smartypants-lite": "^1.0.2",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^8.9.3",
|
"mongoose": "^8.9.4",
|
||||||
"nanoid": "5.0.9",
|
"nanoid": "5.0.9",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user