mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-24 20:42:43 +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 { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||
|
||||
import HeaderNav from './headerNav/headerNav.jsx';
|
||||
|
||||
import { safeHTML } from './safeHTML.js';
|
||||
|
||||
|
||||
const PAGE_HEIGHT = 1056;
|
||||
|
||||
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.
|
||||
else
|
||||
props.onVisibilityChange(props.index + 1, false, false);
|
||||
}
|
||||
)},
|
||||
});
|
||||
},
|
||||
{ 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)=>{
|
||||
if(entry.isIntersecting)
|
||||
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
|
||||
);
|
||||
|
||||
@@ -115,7 +118,10 @@ const BrewRenderer = (props)=>{
|
||||
pageShadows : true
|
||||
});
|
||||
|
||||
const [headerState, setHeaderState] = useState(false);
|
||||
|
||||
const mainRef = useRef(null);
|
||||
const pagesRef = useRef(null);
|
||||
|
||||
if(props.renderer == 'legacy') {
|
||||
rawPages = props.text.split('\\page');
|
||||
@@ -287,7 +293,7 @@ const BrewRenderer = (props)=>{
|
||||
<NotificationPopup />
|
||||
</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.*/}
|
||||
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
|
||||
@@ -306,12 +312,13 @@ const BrewRenderer = (props)=>{
|
||||
&&
|
||||
<>
|
||||
{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}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
|
||||
</Frame>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
|
||||
.pane { position : relative; }
|
||||
|
||||
|
||||
@media print {
|
||||
.toolBar { display : none; }
|
||||
.brewRenderer {
|
||||
@@ -82,4 +83,7 @@
|
||||
& > .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 MIN_ZOOM = 10;
|
||||
|
||||
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages })=>{
|
||||
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages, headerState, setHeaderState })=>{
|
||||
|
||||
const [pageNum, setPageNum] = useState(1);
|
||||
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.
|
||||
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
|
||||
else
|
||||
else
|
||||
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
|
||||
|
||||
desiredZoom = minDimRatio * 100;
|
||||
@@ -76,7 +76,10 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
|
||||
|
||||
return (
|
||||
<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*/}
|
||||
<div className='group' role='group' aria-label='Zoom' aria-hidden={!toolsVisible}>
|
||||
<button
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
|
||||
&.hidden {
|
||||
flex-wrap : nowrap;
|
||||
width : 32px;
|
||||
width : 92px;
|
||||
overflow : hidden;
|
||||
background-color : unset;
|
||||
opacity : 0.5;
|
||||
@@ -178,10 +178,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
button.toggleButton {
|
||||
.toggleButton {
|
||||
position : absolute;
|
||||
left : 0;
|
||||
z-index : 5;
|
||||
width : 32px;
|
||||
min-width : unset;
|
||||
height : 100%;
|
||||
display : flex;
|
||||
}
|
||||
@@ -23,7 +23,9 @@ const SharePage = (props)=>{
|
||||
});
|
||||
|
||||
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
|
||||
updateState({ currentBrewRendererPageNum: pageNumber });
|
||||
setState((prevState)=>({
|
||||
currentBrewRendererPageNum : pageNumber,
|
||||
...prevState }));
|
||||
}, []);
|
||||
|
||||
const handleControlKeys = (e)=>{
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -41,7 +41,7 @@
|
||||
"marked-smartypants-lite": "^1.0.2",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"moment": "^2.30.1",
|
||||
"mongoose": "^8.9.3",
|
||||
"mongoose": "^8.9.4",
|
||||
"nanoid": "5.0.9",
|
||||
"nconf": "^0.12.1",
|
||||
"react": "^18.3.1",
|
||||
@@ -10168,9 +10168,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose": {
|
||||
"version": "8.9.3",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz",
|
||||
"integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==",
|
||||
"version": "8.9.4",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.4.tgz",
|
||||
"integrity": "sha512-DndoI01aV/q40P9DiYDXsYjhj8vZjmmuFwcC3Tro5wFznoE1z6Fe2JgMnbLR6ghglym5ziYizSfAJykp+UPZWg==",
|
||||
"dependencies": {
|
||||
"bson": "^6.10.1",
|
||||
"kareem": "2.6.3",
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
"marked-smartypants-lite": "^1.0.2",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"moment": "^2.30.1",
|
||||
"mongoose": "^8.9.3",
|
||||
"mongoose": "^8.9.4",
|
||||
"nanoid": "5.0.9",
|
||||
"nconf": "^0.12.1",
|
||||
"react": "^18.3.1",
|
||||
|
||||
Reference in New Issue
Block a user