mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-05-07 18:48:39 +00:00
@@ -106,6 +106,7 @@ const BrewRenderer = (props)=>{
|
|||||||
currentBrewRendererPageNum : 1,
|
currentBrewRendererPageNum : 1,
|
||||||
themeBundle : {},
|
themeBundle : {},
|
||||||
onPageChange : ()=>{},
|
onPageChange : ()=>{},
|
||||||
|
showToolbar : true,
|
||||||
...props
|
...props
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,7 +272,6 @@ const BrewRenderer = (props)=>{
|
|||||||
|
|
||||||
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
|
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
|
||||||
scrollToHash(window.location.hash);
|
scrollToHash(window.location.hash);
|
||||||
|
|
||||||
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
|
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
|
||||||
renderPages(); //Make sure page is renderable before showing
|
renderPages(); //Make sure page is renderable before showing
|
||||||
setState((prevState)=>({
|
setState((prevState)=>({
|
||||||
@@ -301,6 +301,53 @@ const BrewRenderer = (props)=>{
|
|||||||
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
|
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
|
||||||
renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]);
|
renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]);
|
||||||
|
|
||||||
|
const toolbarEl = <ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>;
|
||||||
|
|
||||||
|
const brewRenderFrameContents = (
|
||||||
|
<>
|
||||||
|
<div className='brewRenderer'
|
||||||
|
onKeyDown={handleControlKeys}
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
||||||
|
{state.isMounted
|
||||||
|
&&
|
||||||
|
<>
|
||||||
|
{renderedStyle}
|
||||||
|
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle} ref={pagesRef}>
|
||||||
|
{renderedPages}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const brewRenderFrameWrapper = (
|
||||||
|
<>
|
||||||
|
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
|
||||||
|
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||||
|
contentDidMount={frameDidMount}
|
||||||
|
onClick={()=>{emitClick();}}
|
||||||
|
>
|
||||||
|
{brewRenderFrameContents}
|
||||||
|
</Frame>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const brewRenderDivWrapper = (
|
||||||
|
<>
|
||||||
|
<div id='BrewRendererFlat'
|
||||||
|
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||||
|
>
|
||||||
|
{brewRenderFrameContents}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!props.showToolbar && state.visibility != 'visible') { frameDidMount(); }
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/*render dummy page while iFrame is mounting.*/}
|
{/*render dummy page while iFrame is mounting.*/}
|
||||||
@@ -318,32 +365,13 @@ const BrewRenderer = (props)=>{
|
|||||||
<NotificationPopup />
|
<NotificationPopup />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
|
{props.showToolbar ? toolbarEl : ''}
|
||||||
|
|
||||||
{/*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}
|
{props.showToolbar ? brewRenderFrameWrapper:brewRenderDivWrapper}
|
||||||
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
{state.isMounted &&
|
||||||
contentDidMount={frameDidMount}
|
<div id='brewRendered'></div>
|
||||||
onClick={()=>{emitClick();}}
|
}
|
||||||
>
|
|
||||||
<div className='brewRenderer'
|
|
||||||
onKeyDown={handleControlKeys}
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
|
|
||||||
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
|
||||||
{state.isMounted
|
|
||||||
&&
|
|
||||||
<>
|
|
||||||
{renderedStyle}
|
|
||||||
<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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import HomePage from './pages/homePage/homePage.jsx';
|
|||||||
import EditPage from './pages/editPage/editPage.jsx';
|
import EditPage from './pages/editPage/editPage.jsx';
|
||||||
import UserPage from './pages/userPage/userPage.jsx';
|
import UserPage from './pages/userPage/userPage.jsx';
|
||||||
import SharePage from './pages/sharePage/sharePage.jsx';
|
import SharePage from './pages/sharePage/sharePage.jsx';
|
||||||
|
import EmbedPage from './pages/embedPage/embedPage.jsx';
|
||||||
import NewPage from './pages/newPage/newPage.jsx';
|
import NewPage from './pages/newPage/newPage.jsx';
|
||||||
import ErrorPage from './pages/errorPage/errorPage.jsx';
|
import ErrorPage from './pages/errorPage/errorPage.jsx';
|
||||||
import VaultPage from './pages/vaultPage/vaultPage.jsx';
|
import VaultPage from './pages/vaultPage/vaultPage.jsx';
|
||||||
@@ -70,7 +71,8 @@ const Homebrew = (props)=>{
|
|||||||
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
|
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
|
||||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} />} />
|
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} share={true} />} />
|
||||||
|
<Route path='/embed/:id' element={<WithRoute el={EmbedPage} brew={brew} share={false} />} />
|
||||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={brew} userThemes={userThemes}/>} />
|
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={brew} userThemes={userThemes}/>} />
|
||||||
<Route path='/new' element={<WithRoute el={NewPage} userThemes={userThemes}/> } />
|
<Route path='/new' element={<WithRoute el={NewPage} userThemes={userThemes}/> } />
|
||||||
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={brews} />} />
|
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={brews} />} />
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const Nav = require('client/homebrew/navbar/nav.jsx');
|
||||||
|
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||||
|
|
||||||
|
module.exports = function(props){
|
||||||
|
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||||
|
get PDF
|
||||||
|
</Nav.item>;
|
||||||
|
};
|
||||||
@@ -1,9 +1,20 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Nav from './nav.jsx';
|
import Nav from './nav.jsx';
|
||||||
import { printCurrentBrew } from '@shared/helpers.js';
|
import { printCurrentBrew, scrapeBrewHTML, scrapeBrewZip } from '@shared/helpers.js';
|
||||||
|
|
||||||
export default function(){
|
export default function(props){
|
||||||
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
return <Nav.dropdown>
|
||||||
get PDF
|
<Nav.item color='grey' icon='fas fa-question-circle'>
|
||||||
</Nav.item>;
|
export
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||||
|
get PDF
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item onClick={scrapeBrewHTML} color='orange' icon='fas fa-file-code'>
|
||||||
|
get HTML
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item onClick={scrapeBrewZip} color='orange' icon='fas fa-file-archive'>
|
||||||
|
get HTML (Zip)
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.dropdown>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ const EditPage = (props)=>{
|
|||||||
{renderAutoSaveButton()}
|
{renderAutoSaveButton()}
|
||||||
</Nav.dropdown>}
|
</Nav.dropdown>}
|
||||||
<NewBrewItem />
|
<NewBrewItem />
|
||||||
<PrintNavItem />
|
<PrintNavItem brew={currentBrew}/>
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
<VaultNavItem />
|
<VaultNavItem />
|
||||||
<ShareNavItem brew={currentBrew} />
|
<ShareNavItem brew={currentBrew} />
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import './embedPage.less';
|
||||||
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
|
import Headtags from '../../../../vitreum/headtags.js';
|
||||||
|
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
||||||
|
import Markdown from '@shared/markdown.js';
|
||||||
|
|
||||||
|
const Meta = Headtags.Meta;
|
||||||
|
|
||||||
|
import Nav from '@navbar/nav.jsx';
|
||||||
|
import Navbar from '@navbar/navbar.jsx';
|
||||||
|
import MetadataNav from '@navbar/metadata.navitem.jsx';
|
||||||
|
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||||
|
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||||
|
const { both: RecentNavItem } = RecentNavItems;
|
||||||
|
import Account from '@navbar/account.navitem.jsx';
|
||||||
|
import safeHTML from '../../brewRenderer/safeHTML.js';
|
||||||
|
|
||||||
|
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||||
|
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
|
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
|
||||||
|
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
|
||||||
|
|
||||||
|
let renderedPages = [];
|
||||||
|
let rawPages = [];
|
||||||
|
|
||||||
|
const BrewPage = (props)=>{
|
||||||
|
props = {
|
||||||
|
contents : '',
|
||||||
|
index : 0,
|
||||||
|
...props
|
||||||
|
};
|
||||||
|
const pageRef = useRef(null);
|
||||||
|
const cleanText = safeHTML(props.contents);
|
||||||
|
|
||||||
|
return <div className={props.className} id={`p${props.index + 1}`} data-index={props.index} ref={pageRef} style={props.style} {...props.attributes}>
|
||||||
|
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const EmbedPage = (props)=>{
|
||||||
|
const [displayOptions, setDisplayOptions] = useState({
|
||||||
|
zoomLevel : 100,
|
||||||
|
spread : 'single',
|
||||||
|
startOnRight : true,
|
||||||
|
pageShadows : true,
|
||||||
|
rowGap : 5,
|
||||||
|
columnGap : 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
if(props.renderer == 'legacy') {
|
||||||
|
rawPages = props.brew.text.split(PAGEBREAK_REGEX_LEGACY);
|
||||||
|
} else {
|
||||||
|
rawPages = props.brew.text.split(PAGEBREAK_REGEX_V3);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pagesStyle = {
|
||||||
|
zoom : `${displayOptions.zoomLevel}%`,
|
||||||
|
columnGap : `${displayOptions.columnGap}px`,
|
||||||
|
rowGap : `${displayOptions.rowGap}px`,
|
||||||
|
overflowY : 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
const { brew = DEFAULT_BREW_LOAD, disableMeta = false, share = true } = props;
|
||||||
|
|
||||||
|
const [themeBundle, setThemeBundle] = useState({});
|
||||||
|
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||||
|
|
||||||
|
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
|
||||||
|
setCurrentBrewRendererPageNum(pageNumber);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleControlKeys = (e)=>{
|
||||||
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
|
const P_KEY = 80;
|
||||||
|
if(e.keyCode === P_KEY) {
|
||||||
|
printCurrentBrew();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
document.addEventListener('keydown', handleControlKeys);
|
||||||
|
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
|
||||||
|
|
||||||
|
return ()=>{
|
||||||
|
document.removeEventListener('keydown', handleControlKeys);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderStyle = ()=>{
|
||||||
|
const themeStyles = themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
|
||||||
|
const cleanStyle = safeHTML(`${themeStyles} \n\n <style> ${props.brew.style} </style>`);
|
||||||
|
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: cleanStyle }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPage = (pageText, index)=>{
|
||||||
|
|
||||||
|
let styles = {
|
||||||
|
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
|
||||||
|
// Add more conditions as needed
|
||||||
|
};
|
||||||
|
let classes = 'page';
|
||||||
|
let attributes = {};
|
||||||
|
|
||||||
|
if(props.renderer == 'legacy') {
|
||||||
|
pageText.replace(COLUMNBREAK_REGEX_LEGACY, '```\n````\n'); // Allow Legacy brews to use `\column(break)`
|
||||||
|
const html = MarkdownLegacy.render(pageText);
|
||||||
|
|
||||||
|
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} />;
|
||||||
|
} else {
|
||||||
|
if(pageText.startsWith('\\page')) {
|
||||||
|
const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
|
||||||
|
const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
|
||||||
|
if(injectedTags) {
|
||||||
|
styles = { ...styles, ...injectedTags.styles };
|
||||||
|
styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
|
||||||
|
classes = [classes, injectedTags.classes].join(' ').trim();
|
||||||
|
attributes = injectedTags.attributes;
|
||||||
|
}
|
||||||
|
pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME.
|
||||||
|
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||||
|
|
||||||
|
const html = Markdown.render(pageText, index);
|
||||||
|
|
||||||
|
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPages = ()=>{
|
||||||
|
if(props.errors && props.errors.length)
|
||||||
|
return renderedPages;
|
||||||
|
|
||||||
|
renderedPages.length = 0;
|
||||||
|
|
||||||
|
_.forEach(rawPages, (page, index)=>{
|
||||||
|
{
|
||||||
|
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return renderedPages;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
|
{renderStyle()}
|
||||||
|
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle}>
|
||||||
|
{renderPages()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmbedPage;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.page {
|
||||||
|
page-break-before : auto;
|
||||||
|
page-break-after : auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homebrew:not(:has(>.sitePage)) {
|
||||||
|
background: unset !important;
|
||||||
|
}
|
||||||
@@ -182,7 +182,7 @@ const HomePage =(props)=>{
|
|||||||
? <ErrorNavItem error={error} clearError={clearError} />
|
? <ErrorNavItem error={error} clearError={clearError} />
|
||||||
: renderSaveButton()}
|
: renderSaveButton()}
|
||||||
<NewBrewItem />
|
<NewBrewItem />
|
||||||
<PrintNavItem />
|
<PrintNavItem brew={currentBrew}/>
|
||||||
<HelpNavItem />
|
<HelpNavItem />
|
||||||
<VaultNavItem />
|
<VaultNavItem />
|
||||||
<RecentNavItem />
|
<RecentNavItem />
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
|||||||
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
||||||
|
|
||||||
const SharePage = (props)=>{
|
const SharePage = (props)=>{
|
||||||
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
|
const { brew = DEFAULT_BREW_LOAD, disableMeta = false, share = true } = props;
|
||||||
|
|
||||||
const [themeBundle, setThemeBundle] = useState({});
|
const [themeBundle, setThemeBundle] = useState({});
|
||||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||||
@@ -66,40 +66,43 @@ const SharePage = (props)=>{
|
|||||||
</Nav.item>
|
</Nav.item>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showNav = (
|
||||||
|
<Navbar>
|
||||||
|
<Nav.section className='titleSection'>
|
||||||
|
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
|
||||||
|
</Nav.section>
|
||||||
|
|
||||||
|
<Nav.section>
|
||||||
|
{brew.shareId && (
|
||||||
|
<>
|
||||||
|
<PrintNavItem brew={brew}/>
|
||||||
|
<Nav.dropdown>
|
||||||
|
<Nav.item color='red' icon='fas fa-code'>
|
||||||
|
source
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${processShareId()}`}>
|
||||||
|
view
|
||||||
|
</Nav.item>
|
||||||
|
{renderEditLink()}
|
||||||
|
<Nav.item color='blue' icon='fas fa-download' href={`/download/${processShareId()}`}>
|
||||||
|
download
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${processShareId()}`}>
|
||||||
|
clone to new
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.dropdown>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<RecentNavItem brew={brew} storageKey='view' />
|
||||||
|
<Account />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='sharePage sitePage'>
|
<div className='sharePage sitePage'>
|
||||||
<Meta name='robots' content='noindex, nofollow' />
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
<Navbar>
|
{share ? showNav : ''}
|
||||||
<Nav.section className='titleSection'>
|
|
||||||
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
|
|
||||||
</Nav.section>
|
|
||||||
|
|
||||||
<Nav.section>
|
|
||||||
{brew.shareId && (
|
|
||||||
<>
|
|
||||||
<PrintNavItem />
|
|
||||||
<Nav.dropdown>
|
|
||||||
<Nav.item color='red' icon='fas fa-code'>
|
|
||||||
source
|
|
||||||
</Nav.item>
|
|
||||||
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${processShareId()}`}>
|
|
||||||
view
|
|
||||||
</Nav.item>
|
|
||||||
{renderEditLink()}
|
|
||||||
<Nav.item color='blue' icon='fas fa-download' href={`/download/${processShareId()}`}>
|
|
||||||
download
|
|
||||||
</Nav.item>
|
|
||||||
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${processShareId()}`}>
|
|
||||||
clone to new
|
|
||||||
</Nav.item>
|
|
||||||
</Nav.dropdown>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<RecentNavItem brew={brew} storageKey='view' />
|
|
||||||
<Account />
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<BrewRenderer
|
<BrewRenderer
|
||||||
text={brew.text}
|
text={brew.text}
|
||||||
@@ -111,6 +114,7 @@ const SharePage = (props)=>{
|
|||||||
onPageChange={handleBrewRendererPageChange}
|
onPageChange={handleBrewRendererPageChange}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
|
showToolbar={share}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Generated
+46
-30
@@ -3805,9 +3805,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.34.48",
|
"version": "0.34.49",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
|
||||||
"integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==",
|
"integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -4110,9 +4110,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||||
"version": "5.0.5",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5050,9 +5050,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.13",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6908,9 +6908,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/flatted": {
|
"node_modules/flatted": {
|
||||||
"version": "3.4.2",
|
"version": "3.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz",
|
||||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
"integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@@ -7293,9 +7293,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob/node_modules/brace-expansion": {
|
"node_modules/glob/node_modules/brace-expansion": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
@@ -9911,6 +9911,22 @@
|
|||||||
"url": "https://opencollective.com/mongoose"
|
"url": "https://opencollective.com/mongoose"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mongoose/node_modules/gcp-metadata": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"gaxios": "^7.0.0",
|
||||||
|
"google-logging-utils": "^1.0.0",
|
||||||
|
"json-bigint": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mongoose/node_modules/mongodb": {
|
"node_modules/mongoose/node_modules/mongodb": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz",
|
||||||
@@ -10627,9 +10643,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "8.4.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||||
"integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==",
|
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -10643,9 +10659,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -12560,9 +12576,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -12838,9 +12854,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "7.24.6",
|
"version": "7.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
|
||||||
"integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==",
|
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -13135,9 +13151,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/picomatch": {
|
"node_modules/vite/node_modules/picomatch": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|||||||
+16
-5
@@ -12,7 +12,7 @@ import _ from 'lodash';
|
|||||||
import jwt from 'jwt-simple';
|
import jwt from 'jwt-simple';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import config from './config.js';
|
import config from './config.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
import api from './homebrew.api.js';
|
import api from './homebrew.api.js';
|
||||||
@@ -80,10 +80,9 @@ export default async function createApp(vite) {
|
|||||||
|
|
||||||
const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app
|
const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app
|
||||||
|
|
||||||
if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) {
|
if(!origin || origin === 'null' || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) {
|
||||||
callback(null, true);
|
callback(null, true);
|
||||||
} else {
|
} else {
|
||||||
console.log(origin, 'not allowed');
|
|
||||||
callback(new Error('Not allowed by CORS, if you think this is an error, please contact us'));
|
callback(new Error('Not allowed by CORS, if you think this is an error, please contact us'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -438,8 +437,9 @@ export default async function createApp(vite) {
|
|||||||
return next();
|
return next();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
//Share Page
|
|
||||||
app.get('/share/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
|
const shareEmbedCommon = async(req, res)=>{
|
||||||
|
|
||||||
const { brew } = req;
|
const { brew } = req;
|
||||||
req.ogMeta = { ...defaultMetaTags,
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
title : `${req.brew.title || 'Untitled Brew'} - ${req.brew.authors[0] || 'No author.'}`,
|
title : `${req.brew.title || 'Untitled Brew'} - ${req.brew.authors[0] || 'No author.'}`,
|
||||||
@@ -462,6 +462,17 @@ export default async function createApp(vite) {
|
|||||||
|
|
||||||
brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share');
|
brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share');
|
||||||
splitTextStyleAndMetadata(req.brew);
|
splitTextStyleAndMetadata(req.brew);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Share Page
|
||||||
|
app.get('/share/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
|
||||||
|
await shareEmbedCommon(req,res);
|
||||||
|
return next();
|
||||||
|
}));
|
||||||
|
|
||||||
|
//Embed Page - More work will be done on this later...
|
||||||
|
app.get('/embed/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
|
||||||
|
await shareEmbedCommon(req,res);
|
||||||
return next();
|
return next();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
+45
-2
@@ -1,6 +1,14 @@
|
|||||||
|
/* eslint-disable max-lines */
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import request from '../client/homebrew/utils/request-middleware.js';
|
import request from '../client/homebrew/utils/request-middleware.js';
|
||||||
|
import Markdown from '../shared/markdown.js';
|
||||||
|
import packageJSON from '../package.json' with { type: 'json' };
|
||||||
|
|
||||||
|
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
|
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
|
||||||
|
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
|
||||||
|
|
||||||
|
|
||||||
// Convert the templates from a brew to a Snippets Structure.
|
// Convert the templates from a brew to a Snippets Structure.
|
||||||
const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=null, full=true)=>{
|
const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=null, full=true)=>{
|
||||||
@@ -130,7 +138,7 @@ const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{
|
|||||||
const themeBundle = res.body;
|
const themeBundle = res.body;
|
||||||
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
|
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
|
||||||
setThemeBundle(themeBundle);
|
setThemeBundle(themeBundle);
|
||||||
setError(null);
|
if(setError) { setError(null); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
|
const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
|
||||||
@@ -168,10 +176,45 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrapeBrew = ()=>{
|
||||||
|
const htmlBody = `<html>\n${window.frames['BrewRenderer'].contentDocument.documentElement.innerHTML}\n</html>`;
|
||||||
|
return htmlBody;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const downloadBlob = (brewHtml, fileName)=>{
|
||||||
|
const blob = new Blob([brewHtml], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName || 'download';
|
||||||
|
const clickHandler = ()=>{
|
||||||
|
setTimeout(()=>{
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
removeEventListener('click', clickHandler);
|
||||||
|
}, 150);
|
||||||
|
};
|
||||||
|
a.addEventListener('click', clickHandler, false);
|
||||||
|
a.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrapeBrewZip = ()=>{
|
||||||
|
const htmlBody = scrapeBrew();
|
||||||
|
// DO STUFF!
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrapeBrewHTML = ()=>{
|
||||||
|
const htmlBody = scrapeBrew();
|
||||||
|
// Manipulate the body to change all relative path references to full URLs
|
||||||
|
downloadBlob(htmlBody, 'testDownload.html');
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
splitTextStyleAndMetadata,
|
splitTextStyleAndMetadata,
|
||||||
printCurrentBrew,
|
printCurrentBrew,
|
||||||
fetchThemeBundle,
|
fetchThemeBundle,
|
||||||
brewSnippetsToJSON,
|
brewSnippetsToJSON,
|
||||||
debugTextMismatch
|
debugTextMismatch,
|
||||||
|
scrapeBrewHTML,
|
||||||
|
scrapeBrewZip,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,5 +34,6 @@ export default defineConfig({
|
|||||||
fs : {
|
fs : {
|
||||||
allow : ['.'],
|
allow : ['.'],
|
||||||
},
|
},
|
||||||
|
allowedHosts : ['fedora.copy.to']
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user