mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-11-04 22:38:43 +00:00
Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fit-listpage-into-vault
This commit is contained in:
@@ -6,6 +6,9 @@ updates:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 99
|
||||
groups:
|
||||
eslint-bundle:
|
||||
patterns: ["*eslint*"]
|
||||
update-types: ["patch", "minor", "major"]
|
||||
dev-dependencies:
|
||||
dependency-type: "development"
|
||||
patterns: ["*"]
|
||||
|
||||
+52
-2
@@ -93,6 +93,56 @@ pre {
|
||||
## changelog
|
||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||
|
||||
### Tuesday 9/29/2026 - v3.24.1
|
||||
{{taskList
|
||||
##### calculuschild
|
||||
* [x] Small hotfix for broken images thrashing the server
|
||||
}}
|
||||
|
||||
### Tuesday 9/29/2026 - v3.24.0
|
||||
{{taskList
|
||||
##### calculuschild
|
||||
* [x] Add a periodic warning to /new page if work has never been saved
|
||||
* [x] Small fix to variable hoisting logic
|
||||
|
||||
Fixes issue [#31](https://github.com/naturalcrit/marked-variables/issues/31)
|
||||
|
||||
##### Ericscheid
|
||||
* [x] Enable "fancy drop caps" on any paragraph, by placing `{drop-cap}` on the line below.
|
||||
|
||||
Fixes issue [#5024](https://github.com/naturalcrit/homebrewery/issues/5024)
|
||||
|
||||
* [x] Prevent crash if `<script>` tag in brew text
|
||||
|
||||
Fixes issue [#546](https://github.com/naturalcrit/homebrewery/issues/546)
|
||||
|
||||
##### G-Ambatte
|
||||
* [x] Add compatibility with Markdeep Ascii art diagrams. See [example brew](https://homebrewery.naturalcrit.com/share/TsZ5Iak3bapb)
|
||||
|
||||
* [x] Fix stray hyphens in PDF output titles
|
||||
|
||||
Fixes issue [#5069](https://github.com/naturalcrit/homebrewery/issues/5069)
|
||||
|
||||
##### 5e-Cleric
|
||||
* [x] Fix Google Drive files that can't be deleted
|
||||
|
||||
Fixes issue [#5020](https://github.com/naturalcrit/homebrewery/issues/5020)
|
||||
|
||||
* [x] Prevent co-authors moving brews between Google and Homebrewery storage.
|
||||
|
||||
Fixes issue [#4862](https://github.com/naturalcrit/homebrewery/issues/4862)
|
||||
|
||||
* [x] Fix cursor position for hotkeys, and other tweaks
|
||||
|
||||
Fixes issues [#4758](https://github.com/naturalcrit/homebrewery/issues/4758), [#4790](https://github.com/naturalcrit/homebrewery/issues/4790), [#4809](https://github.com/naturalcrit/homebrewery/issues/4809)
|
||||
|
||||
##### 5e-Cleric, G-Ambatte
|
||||
* [x] Share pages update live when file contents change
|
||||
|
||||
##### Abquintic, Calculuschild, G-Ambatte, Gazook89, 5e-Cleric, Ericscheid
|
||||
* [x] Multiple background fixes and code cleanups
|
||||
}}
|
||||
|
||||
### Monday 9/07/2026 - v3.23.0
|
||||
{{taskList
|
||||
##### firstmatekidd (new contributor!)
|
||||
@@ -140,6 +190,8 @@ Fixes issue [#4904](https://github.com/naturalcrit/homebrewery/issues/4904)
|
||||
* [x] Multiple background fixes and code cleanups
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Wednesday 5/13/2026 - v3.22.1
|
||||
{{taskList
|
||||
##### Frederlk (new Contributor!)
|
||||
@@ -157,8 +209,6 @@ Fixes issue [#4904](https://github.com/naturalcrit/homebrewery/issues/4904)
|
||||
Fixes issues [#4771](https://github.com/naturalcrit/homebrewery/issues/4771), [#4783](https://github.com/naturalcrit/homebrewery/issues/4783)
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Saturday 4/04/2026 - v3.21.0
|
||||
|
||||
{{taskList
|
||||
|
||||
@@ -107,14 +107,14 @@ body {
|
||||
}
|
||||
|
||||
th, td {
|
||||
max-width : 50ch;
|
||||
padding : 5px 10px;
|
||||
overflow : hidden;
|
||||
text-overflow : ellipsis;
|
||||
vertical-align : middle;
|
||||
text-align : center;
|
||||
white-space : nowrap;
|
||||
border-right : 1px solid;
|
||||
max-width:50ch;
|
||||
overflow:hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:last-child { border-right : none; }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const BrewCleanup = ({})=>{
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const find = async (type)=>{
|
||||
|
||||
|
||||
|
||||
if(type === 'junk') try {
|
||||
setPendingJunk(true);
|
||||
@@ -109,18 +109,18 @@ const BrewCleanup = ({})=>{
|
||||
<td style={{ width: '200px' }}>{Moment(brew.updatedAt).fromNow()}</td>
|
||||
<td>{brew.lastViewed ? Moment(brew.lastViewed).fromNow() : 'No last viewed date'}</td>
|
||||
<td>{brew.googleId ? 'Google' : 'Homebrewery'}</td>
|
||||
</tr>
|
||||
</tr>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</>;
|
||||
};
|
||||
const renderFound = (type)=>{
|
||||
const deleteButton = !(type === 'junk' && junkBrewCollection.length === 0 || type === 'lost' && lostBrewCollection.length === 0);
|
||||
const deleteButton = !(type === 'junk' && junkBrewCollection.length === 0 || type === 'lost' && lostBrewCollection.length === 0);
|
||||
|
||||
return <div className='result'>
|
||||
{deleteButton && <button onClick={()=>cleanup(type)} className='remove'>
|
||||
{pendingLost && type === "lost" || pendingJunk && type === "junk"
|
||||
{pendingLost && type === 'lost' || pendingJunk && type === 'junk'
|
||||
? <i className='fas fa-spin fa-spinner' />
|
||||
: <span><i className='fas fa-times' /> Remove</span>
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
margin-left : 10px;
|
||||
}
|
||||
&.checkbox {
|
||||
line-height: 1.5em;
|
||||
line-height : 1.5em;
|
||||
input {
|
||||
width : 1.5em;
|
||||
height : 1.5em;
|
||||
@@ -26,9 +26,9 @@
|
||||
}
|
||||
|
||||
.lockSuggestions {
|
||||
columns : 2;
|
||||
line-height : 1.2em;
|
||||
break-inside : avoid;
|
||||
columns : 2;
|
||||
h2 { column-span : all; }
|
||||
h3 { margin-top : 0px; }
|
||||
b { font-weight : 600; }
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
.anchored-box {
|
||||
position : absolute;
|
||||
inset-block-start : anchor(bottom);
|
||||
visibility : hidden;
|
||||
justify-self : anchor-center;
|
||||
inset-block-start : anchor(bottom);
|
||||
&.active { visibility : visible; }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file basically checks support for Anchor Positioning API in the browser,
|
||||
and then loads the Oddbird polyfill if support is lacking.
|
||||
and then loads the Oddbird polyfill if support is lacking.
|
||||
*/
|
||||
|
||||
let polyfillPromise;
|
||||
|
||||
@@ -46,7 +46,7 @@ const settingsCompartment = new Compartment();
|
||||
|
||||
import { generalKeymap, markdownKeymap, cssKeymap, formatCSS } from './extensions/customKeyMaps.js';
|
||||
import foldOnPages from './extensions/customFolding.js';
|
||||
import { customHighlightStyle , customHighlightPlugin } from './extensions/customHighlight.js';
|
||||
import { customHighlightStyle, customHighlightPlugin } from './extensions/customHighlight.js';
|
||||
import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js';
|
||||
|
||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
}
|
||||
|
||||
@keyframes slideacross {
|
||||
0% {
|
||||
bottom: -200px;
|
||||
left: -200px;
|
||||
}
|
||||
0% {
|
||||
bottom : -200px;
|
||||
left : -200px;
|
||||
}
|
||||
|
||||
100% {
|
||||
bottom:100%;
|
||||
left:100%;
|
||||
}
|
||||
100% {
|
||||
bottom : 100%;
|
||||
left : 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:where(.codeEditor) {
|
||||
@@ -37,22 +37,22 @@
|
||||
outline : none !important;
|
||||
|
||||
&.cm-flash {
|
||||
position:relative;
|
||||
position : relative;
|
||||
&::after {
|
||||
position:absolute;
|
||||
content:'';
|
||||
bottom:-200px;
|
||||
left:-200px;
|
||||
translate:-50%;
|
||||
width:200%;
|
||||
height:100px;
|
||||
background:linear-gradient(0deg, #89eafc00 0px, #89ebfc8e 50px, #89eafc00 100px, transparent);
|
||||
display:block;
|
||||
position : absolute;
|
||||
bottom : -200px;
|
||||
left : -200px;
|
||||
display : block;
|
||||
width : 200%;
|
||||
height : 100px;
|
||||
content : '';
|
||||
background : linear-gradient(0deg, #89EAFC00 0px, #89EBFC8E 50px, #89EAFC00 100px, transparent);
|
||||
|
||||
rotate:30deg;
|
||||
rotate : 30deg;
|
||||
translate : -50%;
|
||||
|
||||
@media screen and (prefers-reduced-motion: no-preference) {
|
||||
animation: .5s linear 1 slideacross ;
|
||||
@media screen and (prefers-reduced-motion : no-preference) {
|
||||
animation : 0.5s linear 1 slideacross;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,9 +134,7 @@
|
||||
vertical-align : sub;
|
||||
color : rgb(123, 123, 15);
|
||||
}
|
||||
.cm-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.cm-strikethrough { text-decoration : line-through; }
|
||||
|
||||
.cm-definitionList {
|
||||
.cm-definitionTerm { color : rgb(96, 117, 143); }
|
||||
@@ -196,30 +194,27 @@
|
||||
}
|
||||
|
||||
.cm-image {
|
||||
position:relative;
|
||||
position : relative;
|
||||
|
||||
.cm-preview {
|
||||
object-fit: contain;
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
left:0;
|
||||
height:200px;
|
||||
width:200px;
|
||||
height:200px;
|
||||
padding:5px;
|
||||
background-color: #fff;
|
||||
border-radius:10px;
|
||||
border:3px solid grey;
|
||||
pointer-events: none;
|
||||
opacity:0;
|
||||
transition:0.2s opacity 0.5s;
|
||||
translate:0 100%;
|
||||
z-index:1000;
|
||||
position : absolute;
|
||||
bottom : 0;
|
||||
left : 0;
|
||||
z-index : 1000;
|
||||
width : 200px;
|
||||
height : 200px;
|
||||
padding : 5px;
|
||||
pointer-events : none;
|
||||
object-fit : contain;
|
||||
background-color : #FFFFFF;
|
||||
border : 3px solid grey;
|
||||
border-radius : 10px;
|
||||
opacity : 0;
|
||||
translate : 0 100%;
|
||||
transition : 0.2s opacity 0.5s;
|
||||
}
|
||||
|
||||
&:hover .cm-preview {
|
||||
opacity:1;
|
||||
}
|
||||
&:hover .cm-preview { opacity : 1; }
|
||||
}
|
||||
|
||||
/* Tab character visualization (optional) */
|
||||
|
||||
@@ -354,10 +354,7 @@ class ImageWidget extends WidgetType {
|
||||
img.className = 'cm-preview';
|
||||
img.src = this.url;
|
||||
|
||||
img.onerror = ()=>{
|
||||
img.src = 'client/icons/broken-image.jpg';
|
||||
};
|
||||
|
||||
if (img.onerror) return document.createElement('span');
|
||||
return img;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint max-lines: ["error", { "max": 300 }] */
|
||||
import { keymap } from '@codemirror/view';
|
||||
import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands';
|
||||
import { EditorSelection } from '@codemirror/state';
|
||||
import { EditorSelection, ChangeSet } from '@codemirror/state';
|
||||
import { Prec } from '@codemirror/state';
|
||||
import * as prettier from 'prettier/standalone';
|
||||
import * as postcssPlugin from 'prettier/plugins/postcss';
|
||||
@@ -14,33 +14,30 @@ export async function formatCSS(view) {
|
||||
const code = empty ? fullDoc : selection;
|
||||
|
||||
let formatted = await prettier.format(code, {
|
||||
parser: 'css',
|
||||
plugins: [postcssPlugin],
|
||||
parser : 'css',
|
||||
plugins : [postcssPlugin],
|
||||
|
||||
// formatting options
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
printWidth: 100,
|
||||
singleQuote: false,
|
||||
trailingComma: 'all',
|
||||
bracketSpacing: true,
|
||||
endOfLine: 'lf'
|
||||
tabWidth : 2,
|
||||
useTabs : false,
|
||||
printWidth : 100,
|
||||
singleQuote : false,
|
||||
trailingComma : 'all',
|
||||
bracketSpacing : true,
|
||||
endOfLine : 'lf'
|
||||
});
|
||||
|
||||
//format manually single declaration rules to span one line.
|
||||
//Prettier can't do it by default, this is crude but it works
|
||||
formatted = formatted.replace(
|
||||
/([^{]+)\{\s*\n\s*([^;\n]+:[^;\n]+;)\s*\n\s*\}(\s*)/g,
|
||||
(_, selector, decl, whitespace) =>
|
||||
`${selector} { ${decl.trim()} }${whitespace}`
|
||||
/([^{]+)\{\s*\n\s*([^;\n]+:[^;\n]+;)\s*\n\s*\}(\s*)/g,
|
||||
(_, selector, decl, whitespace)=>`${selector} { ${decl.trim()} }${whitespace}`
|
||||
);
|
||||
if(formatted === code) return true;
|
||||
|
||||
const dom = view.dom;
|
||||
dom.classList.add('cm-flash');
|
||||
|
||||
view.dom.classList.add('cm-flash');
|
||||
setTimeout(()=>{
|
||||
dom.classList.remove('cm-flash');
|
||||
view.dom.classList.remove('cm-flash');
|
||||
|
||||
view.dispatch({
|
||||
changes : {
|
||||
@@ -54,186 +51,172 @@ export async function formatCSS(view) {
|
||||
} catch (err) {
|
||||
console.error('Error formatting css: ', err);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const forEachSelection = (view, fn)=>{
|
||||
view.dispatch(view.state.changeByRange((range)=>fn(view.state, range.from, range.to, view.state.doc.sliceString(range.from, range.to))));
|
||||
return true;
|
||||
};
|
||||
|
||||
const insertTab = (view)=>{
|
||||
// If any selection spans multiple lines, delegates to CodeMirror's indentMore
|
||||
// Otherwise inserts two spaces at each cursor/selection
|
||||
// Otherwise inserts two spaces at each cursor/selection
|
||||
const shouldIndent = view.state.selection.ranges.some((range)=>view.state.doc.lineAt(range.from).number !==
|
||||
view.state.doc.lineAt(range.to).number
|
||||
);
|
||||
|
||||
if(shouldIndent) return indentMore(view);
|
||||
|
||||
const changes = [];
|
||||
|
||||
for (const range of view.state.selection.ranges) {
|
||||
changes.push({
|
||||
from : range.from,
|
||||
to : range.to,
|
||||
insert : ' ' // Insert two spaces, not a tab char!
|
||||
});
|
||||
}
|
||||
// Create a transaction so we can map old positions to
|
||||
// their new positions after the edits are applied
|
||||
const mappedChanges = view.state.update({ changes });
|
||||
|
||||
view.dispatch({
|
||||
changes,
|
||||
selection : EditorSelection.create(
|
||||
view.state.selection.ranges.map((range)=>EditorSelection.cursor(
|
||||
mappedChanges.changes.mapPos(range.from, -1) + 2
|
||||
)
|
||||
)
|
||||
)
|
||||
return forEachSelection(view, (state, from, to, selected)=>{
|
||||
return {
|
||||
changes : { from, to, insert: ' ' }, // Insert two spaces, not a tab char!
|
||||
range : EditorSelection.cursor(from + 2)
|
||||
};
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const wrapSelection = (prefix, suffix)=>(view)=>{
|
||||
const changes = [];
|
||||
const wrapSelection = (prefix, suffix)=>(view)=>forEachSelection(view, (state, from, to, selected)=>{
|
||||
const noSelection = from === to;
|
||||
if(noSelection) {
|
||||
const insert = prefix + suffix;
|
||||
|
||||
for (const range of view.state.selection.ranges) {
|
||||
const { from, to } = range;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
|
||||
let text;
|
||||
|
||||
if(from === to) { text = prefix + suffix; } else if(selected.startsWith(prefix) && selected.endsWith(suffix)) {
|
||||
text = selected.slice(prefix.length, -suffix.length);
|
||||
} else {text = `${prefix}${selected}${suffix}`;}
|
||||
|
||||
changes.push({ from, to, insert: text });
|
||||
return {
|
||||
changes : { from, to, insert },
|
||||
range : EditorSelection.cursor(from + prefix.length)
|
||||
};
|
||||
}
|
||||
|
||||
view.dispatch({
|
||||
changes
|
||||
});
|
||||
const hasWrapper = selected.startsWith(prefix) && selected.endsWith(suffix);
|
||||
if(hasWrapper) {
|
||||
return {
|
||||
changes : [
|
||||
{ from: to - suffix.length, to, insert: '' },
|
||||
{ from, to: from + prefix.length, insert: '' }
|
||||
],
|
||||
range : EditorSelection.range(from, to - prefix.length - suffix.length)
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeNbsp = (view)=>{
|
||||
const { from } = view.state.selection.main;
|
||||
|
||||
const prev2 = from >= 2
|
||||
? view.state.doc.sliceString(from - 2, from)
|
||||
: '';
|
||||
return {
|
||||
changes : { from, to, insert: prefix + selected + suffix },
|
||||
range : EditorSelection.range(from, to + prefix.length + suffix.length)
|
||||
};
|
||||
});
|
||||
|
||||
const makeNbsp = (view)=>forEachSelection(view, (state, from, to, selected)=>{
|
||||
const prev2 = from >= 2 ? state.doc.sliceString(from - 2, from) : '';
|
||||
const insert = (prev2 === ':>' || prev2 === '>>') ? '>' : ':>';
|
||||
|
||||
view.dispatch({
|
||||
changes : { from, to: from, insert },
|
||||
selection : { anchor: from + insert.length },
|
||||
});
|
||||
return {
|
||||
changes : { from, to, insert },
|
||||
range : EditorSelection.cursor(from + insert.length)
|
||||
};
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeSpace = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const makeSpace = (view)=>forEachSelection(view, (state, from, to, selected)=>{
|
||||
let insert = '{{width:10% }}';
|
||||
const match = selected.match(/^{{width:(\d+)% }}$/);
|
||||
let newText = '{{width:10% }}';
|
||||
if(match) {
|
||||
const percent = Math.min(parseInt(match[1], 10) + 10, 100);
|
||||
newText = `{{width:${percent}% }}`;
|
||||
insert = `{{width:${percent}% }}`;
|
||||
}
|
||||
view.dispatch({ changes: { from, to, insert: newText } });
|
||||
return true;
|
||||
};
|
||||
return {
|
||||
changes : { from, to, insert },
|
||||
range : EditorSelection.range(from, from + insert.length)
|
||||
};
|
||||
});
|
||||
|
||||
const removeSpace = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const removeSpace = (view)=>forEachSelection(view, (state, from, to, selected)=>{
|
||||
const match = selected.match(/^{{width:(\d+)% }}$/);
|
||||
if(match) {
|
||||
const percent = parseInt(match[1], 10) - 10;
|
||||
const newText = percent > 0 ? `{{width:${percent}% }}` : '';
|
||||
view.dispatch({ changes: { from, to, insert: newText } });
|
||||
const insert = percent > 0 ? `{{width:${percent}% }}` : '';
|
||||
return { changes: { from, to, insert: insert }, range: EditorSelection.range(from, from + insert.length) };
|
||||
}
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
const makeSpan = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = selected.startsWith('{{') && selected.endsWith('}}')
|
||||
? selected.slice(2, -2)
|
||||
: `{{${selected}}}`;
|
||||
view.dispatch({ changes: { from, to, insert: text } });
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeDiv = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = selected.startsWith('{{') && selected.endsWith('}}')
|
||||
? selected.slice(2, -2)
|
||||
: `{{\n${selected}\n}}`;
|
||||
view.dispatch({ changes: { from, to, insert: text } });
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeComment = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const isHtmlComment = selected.startsWith('<!--') && selected.endsWith('-->');
|
||||
const text = isHtmlComment
|
||||
? selected.slice(4, -3)
|
||||
: `<!-- ${selected} -->`;
|
||||
view.dispatch({ changes: { from, to, insert: text } });
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeLink = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to).trim();
|
||||
const isLink = /^\[(.*)\]\((.*)\)$/.exec(selected);
|
||||
const text = isLink ? `${isLink[1]} ${isLink[2]}` : `[${selected || 'alt text'}](url)`;
|
||||
view.dispatch({ changes: { from, to, insert: text } });
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeList = (type)=>(view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const lines = [];
|
||||
for (let l = from; l <= to; l++) {
|
||||
const lineText = view.state.doc.line(l + 1).text;
|
||||
lines.push(lineText);
|
||||
const makeLink = (view)=>forEachSelection(view, (state, from, to, selected)=>{
|
||||
// If the selection is already a Markdown link, unwrap it
|
||||
const existingLink = /^\[(.*)\]\((.*)\)$/.exec(selected);
|
||||
if(existingLink) {
|
||||
const [, text, url] = existingLink;
|
||||
const insert = `${text} ${url}`;
|
||||
return {
|
||||
changes : { from, to, insert },
|
||||
range : EditorSelection.range(
|
||||
from,
|
||||
from + insert.length
|
||||
),
|
||||
};
|
||||
}
|
||||
const joined = lines.join('\n');
|
||||
let newText;
|
||||
if(type === 'UL') newText = joined.replace(/^/gm, '- ');
|
||||
else newText = joined.replace(/^/gm, (m, i)=>`${i + 1}. `);
|
||||
view.dispatch({ changes: { from, to, insert: newText } });
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeHeader = (level)=>(view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = `${'#'.repeat(level)} ${selected}`;
|
||||
view.dispatch({ changes: { from, to, insert: text } });
|
||||
return true;
|
||||
};
|
||||
// If the selection is a URL, use its domain as the link text.
|
||||
const isUrl =
|
||||
/^(https?:\/\/|www\.)\S+$/i.test(selected) ||
|
||||
/^[a-z0-9-]+(\.[a-z0-9-]+)+([/?#]\S*)?$/i.test(selected);
|
||||
if(isUrl) {
|
||||
const domain = selected
|
||||
.replace(/^https?:\/\//i, '')
|
||||
.replace(/^www\./i, '')
|
||||
.split(/[/?#]/)[0];
|
||||
|
||||
const newColumn = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
view.dispatch({ changes: { from, to, insert: '\n\\column\n\n' } });
|
||||
return true;
|
||||
};
|
||||
const name = domain.split('.')[0];
|
||||
const insert = `[${name}](${selected})`;
|
||||
return {
|
||||
changes : { from, to, insert },
|
||||
range : EditorSelection.range(from, from + insert.length),
|
||||
};
|
||||
}
|
||||
|
||||
const newPage = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
view.dispatch({ changes: { from, to, insert: '\n\\page\n\n' } });
|
||||
return true;
|
||||
};
|
||||
// If the selection is just text, use it as alt text and set url as placeholder
|
||||
const insert = `[${selected || 'alt text'}](url)`;
|
||||
return {
|
||||
changes : { from, to, insert },
|
||||
range : EditorSelection.range(from, from + insert.length),
|
||||
};
|
||||
});
|
||||
|
||||
const addPrefixAtLineStart = (prefix)=>(view)=>forEachSelection(view, (state, from, to, selected)=>{
|
||||
const startLine = state.doc.lineAt(from);
|
||||
const endLine = state.doc.lineAt(to);
|
||||
const selectedLines = [];
|
||||
|
||||
for (let lineNumber = startLine.number; lineNumber <= endLine.number; lineNumber++) { selectedLines.push(state.doc.line(lineNumber)); }
|
||||
|
||||
const allLinesHavePrefix = selectedLines.every((line)=>line.text.startsWith(prefix));
|
||||
|
||||
const changes = selectedLines.map((line)=>{
|
||||
const shouldRemovePrefix = allLinesHavePrefix && line.text.startsWith(prefix);
|
||||
|
||||
return {
|
||||
from : line.from,
|
||||
to : shouldRemovePrefix
|
||||
? line.from + prefix.length
|
||||
: line.from,
|
||||
insert : shouldRemovePrefix ? '' : prefix,
|
||||
};
|
||||
});
|
||||
const changeSet = ChangeSet.of(changes, state.doc.length);
|
||||
|
||||
return {
|
||||
changes,
|
||||
range : EditorSelection.range(
|
||||
changeSet.mapPos(from, -1),
|
||||
changeSet.mapPos(to, 1)
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const newBreak = (type)=>(view)=>forEachSelection(view, (state, from, to, selected)=>{
|
||||
const insert = `\n\\${type}\n\n`;
|
||||
return {
|
||||
changes : { from, to, insert },
|
||||
range : EditorSelection.cursor(from + insert.length)
|
||||
};
|
||||
});
|
||||
|
||||
export const generalKeymap = Prec.high(keymap.of([
|
||||
{ key: 'Tab', run: insertTab }, //runs indentMore if multiple lines selected in a single selection
|
||||
{ key: 'Tab', run: insertTab },
|
||||
{ key: 'Shift-Tab', run: indentLess },
|
||||
{ key: 'Mod-z', run: undo }, //it may be unnecessary
|
||||
{ key: 'Mod-Shift-z', run: redo },
|
||||
@@ -247,7 +230,6 @@ export const cssKeymap = Prec.highest(keymap.of([
|
||||
]));
|
||||
|
||||
export const markdownKeymap = Prec.highest(keymap.of([
|
||||
|
||||
{ key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold
|
||||
{ key: 'Mod-i', run: wrapSelection('*', '*') }, // makeItalic
|
||||
{ key: 'Mod-u', run: wrapSelection('<u>', '</u>') }, // makeUnderline
|
||||
@@ -256,18 +238,18 @@ export const markdownKeymap = Prec.highest(keymap.of([
|
||||
{ key: 'Mod-.', run: makeNbsp },
|
||||
{ key: 'Shift-Mod-.', run: makeSpace },
|
||||
{ key: 'Shift-Mod-,', run: removeSpace },
|
||||
{ key: 'Mod-m', run: makeSpan },
|
||||
{ key: 'Shift-Mod-m', run: makeDiv },
|
||||
{ key: 'Mod-/', run: makeComment },
|
||||
{ key: 'Mod-k', run: makeLink },
|
||||
{ key: 'Mod-l', run: makeList('UL') },
|
||||
{ key: 'Shift-Mod-l', run: makeList('OL') },
|
||||
{ key: 'Shift-Mod-1', run: makeHeader(1) },
|
||||
{ key: 'Shift-Mod-2', run: makeHeader(2) },
|
||||
{ key: 'Shift-Mod-3', run: makeHeader(3) },
|
||||
{ key: 'Shift-Mod-4', run: makeHeader(4) },
|
||||
{ key: 'Shift-Mod-5', run: makeHeader(5) },
|
||||
{ key: 'Shift-Mod-6', run: makeHeader(6) },
|
||||
{ key: 'Mod-Enter', run: newPage },
|
||||
{ key: 'Shift-Mod-Enter', run: newColumn },
|
||||
{ key: 'Mod-m', run: wrapSelection('{{', '}}') },
|
||||
{ key: 'Shift-Mod-m', run: wrapSelection('{{\n', '\n}}') },
|
||||
{ key: 'Mod-/', run: wrapSelection('<!-- ', ' -->') },
|
||||
{ key: 'Mod-Shift-k', run: makeLink },
|
||||
{ key: 'Mod-Shift-u', run: addPrefixAtLineStart('- ') },
|
||||
{ key: 'Mod-Shift-o', run: addPrefixAtLineStart('1. ') },
|
||||
{ key: 'Shift-Mod-1', run: addPrefixAtLineStart('# ') },
|
||||
{ key: 'Shift-Mod-2', run: addPrefixAtLineStart('## ') },
|
||||
{ key: 'Shift-Mod-3', run: addPrefixAtLineStart('### ') },
|
||||
{ key: 'Shift-Mod-4', run: addPrefixAtLineStart('#### ') },
|
||||
{ key: 'Shift-Mod-5', run: addPrefixAtLineStart('##### ') },
|
||||
{ key: 'Shift-Mod-6', run: addPrefixAtLineStart('###### ') },
|
||||
{ key: 'Mod-Enter', run: newBreak('page') },
|
||||
{ key: 'Shift-Mod-Enter', run: newBreak('column') },
|
||||
]));
|
||||
|
||||
@@ -7,7 +7,7 @@ const Combobox = createReactClass({
|
||||
displayName : 'Combobox',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
id : '',
|
||||
id : '',
|
||||
className : '',
|
||||
trigger : 'hover',
|
||||
default : '',
|
||||
|
||||
@@ -31,14 +31,12 @@
|
||||
font-family : 'Open Sans';
|
||||
font-size : 11px;
|
||||
cursor : default;
|
||||
&:hover {
|
||||
background-color : #ddd;
|
||||
}
|
||||
&:hover { background-color : #DDDDDD; }
|
||||
.detail {
|
||||
width : 100%;
|
||||
font-size : 9px;
|
||||
font-style : italic;
|
||||
color : #7c7c7c;
|
||||
color : #7C7C7C;
|
||||
text-align : left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* A dropdown menu component that uses the Anchor Positioning API to position the elements. It supports nested submenus as well.
|
||||
* Anchor Positioning is now supported in all major browsers. A polyfill is conditionally loaded for older browsers.
|
||||
*
|
||||
*
|
||||
* As-is, the menus will always open down aligned on left to trigger, submenus open to the right initially.
|
||||
* If no space, menus will still open down, but aligned to the right of the trigger. Submenus will flip to the other side of the top menu.
|
||||
* This could be customized either in more specific CSS, or as a `direction` prop on the component (in future iterations).
|
||||
*
|
||||
*
|
||||
* @param {string} props.groupName - Name of the menu. Appears as the trigger text.
|
||||
* @param {string} [props.icon] - Icon to display in the trigger.
|
||||
* @param {string} [props.color] - Color class to add to the trigger.
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
@property --menuColor {
|
||||
syntax: '<color>';
|
||||
inherits: true;
|
||||
initial-value: #DDD;
|
||||
syntax : '<color>';
|
||||
inherits : true;
|
||||
initial-value : #DDDDDD;
|
||||
}
|
||||
|
||||
@property --activeTriggerColor {
|
||||
syntax: '<color>';
|
||||
inherits: true;
|
||||
initial-value: #999;
|
||||
syntax : '<color>';
|
||||
inherits : true;
|
||||
initial-value : #999999;
|
||||
}
|
||||
|
||||
:root{
|
||||
--activeTriggerColor : var(--activeTriggerColor);
|
||||
}
|
||||
:root { --activeTriggerColor : var(--activeTriggerColor); }
|
||||
.menu-list {
|
||||
contain : content;
|
||||
position : fixed;
|
||||
top : anchor(bottom);
|
||||
left : anchor(left);
|
||||
position-try: flip-inline flip-block;
|
||||
color: inherit; // [popover] gets a `canvastext` color value from useragent.
|
||||
background: var(--menuColor);
|
||||
position-try : flip-inline flip-block;
|
||||
contain : content;
|
||||
color : inherit; // [popover] gets a `canvastext` color value from useragent.
|
||||
background : var(--menuColor);
|
||||
li > .menu-list {
|
||||
margin: 0 0px;
|
||||
top : anchor(top);
|
||||
left : anchor(right);
|
||||
position-try: flip-inline;
|
||||
}
|
||||
top : anchor(top);
|
||||
left : anchor(right);
|
||||
margin : 0 0px;
|
||||
position-try : flip-inline;
|
||||
}
|
||||
}
|
||||
.menu-wrapper:has(:popover-open) > button { // if menu is open...
|
||||
background-color: var(--activeTriggerColor, hsl(from var(--menuColor) h s calc(l * .85))); // tint menu triggers based on menu color
|
||||
background-color : var(--activeTriggerColor, hsl(from var(--menuColor) h s calc(l * 0.85))); // tint menu triggers based on menu color
|
||||
}
|
||||
|
||||
.darkMode .menu-wrapper:has(:popover-open) > button { // if menu is open...
|
||||
--activeTriggerColor : #444; // tint menu triggers based on menu color
|
||||
--activeTriggerColor : #444444; // tint menu triggers based on menu color
|
||||
}
|
||||
@@ -103,8 +103,8 @@ const BrewRenderer = (props)=>{
|
||||
const pagesRef = useRef(null);
|
||||
|
||||
const [visiblePages, setVisiblePages] = useState([]);
|
||||
const [centerPage , setCenterPage ] = useState(1);
|
||||
const [headerState , setHeaderState ] = useState(false);
|
||||
const [centerPage, setCenterPage] = useState(1);
|
||||
const [headerState, setHeaderState] = useState(false);
|
||||
|
||||
const [state, setState] = useState({
|
||||
isMounted : false,
|
||||
@@ -158,10 +158,9 @@ const BrewRenderer = (props)=>{
|
||||
return false;
|
||||
};
|
||||
|
||||
const renderDummyPage = (index)=>
|
||||
<div className='phb page' id={`p${index + 1}`} key={index}>
|
||||
<i className='fas fa-spinner fa-spin' />
|
||||
</div>;
|
||||
const renderDummyPage = (index)=><div className='phb page' id={`p${index + 1}`} key={index}>
|
||||
<i className='fas fa-spinner fa-spin' />
|
||||
</div>;
|
||||
|
||||
const renderStyle = ()=>{
|
||||
const themeStyles = props.themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
|
||||
|
||||
@@ -61,9 +61,7 @@
|
||||
}
|
||||
|
||||
@supports (break-after:always) {
|
||||
.columnSplit {
|
||||
margin-bottom: 100vh;
|
||||
}
|
||||
.columnSplit { margin-bottom : 100vh; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +74,11 @@
|
||||
height : 100%;
|
||||
padding : unset;
|
||||
overflow-y : unset;
|
||||
&:has(.facing, .flow) {
|
||||
padding : unset;
|
||||
}
|
||||
&:has(.facing, .flow) { padding : unset; }
|
||||
.pages {
|
||||
display : block;
|
||||
margin : 0px;
|
||||
zoom : 100% !important;
|
||||
display : block;
|
||||
& > .page { box-shadow : unset; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,13 +99,13 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
|
||||
return (
|
||||
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
|
||||
<div className='toggleButton'>
|
||||
<button data-tooltip-right={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`}
|
||||
aria-label={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`}
|
||||
<button data-tooltip-right={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`}
|
||||
aria-label={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`}
|
||||
onClick={()=>{ setToolsVisible(!toolsVisible); localStorage.setItem(TOOLBAR_VISIBILITY, !toolsVisible); }}>
|
||||
<i aria-hidden='true' className='fas fa-glasses' />
|
||||
</button>
|
||||
<button data-tooltip-right={`${headerState ? 'Hide' : 'Show'} Header Navigation`}
|
||||
aria-label={`${headerState ? 'Hide' : 'Show'} Header Navigation`}
|
||||
<button data-tooltip-right={`${headerState ? 'Hide' : 'Show'} Header Navigation`}
|
||||
aria-label={`${headerState ? 'Hide' : 'Show'} Header Navigation`}
|
||||
onClick={()=>{setHeaderState(!headerState);}}>
|
||||
<i aria-hidden='true' className='fas fa-rectangle-list' />
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import (less) './client/icons/customIcons.less';
|
||||
@import (less) '../../../icons/customIcons.less';
|
||||
|
||||
.toolBar {
|
||||
position : absolute;
|
||||
@@ -176,9 +176,7 @@
|
||||
transition : all 0.2s ease;
|
||||
}
|
||||
|
||||
.toggleButton button i {
|
||||
filter: drop-shadow(0 0 2px black) drop-shadow(0 0 1px black);
|
||||
}
|
||||
.toggleButton button i { filter : drop-shadow(0 0 2px black) drop-shadow(0 0 1px black); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ const Editor = forwardRef(
|
||||
view={view}
|
||||
style={{ display: 'none' }}
|
||||
editorTheme={editorSettings.editorTheme}
|
||||
onThemeChange={setIsDark}
|
||||
onThemeChange={setIsDark}
|
||||
settings={editorSettings}
|
||||
/>
|
||||
<SettingsEditor
|
||||
|
||||
@@ -131,7 +131,7 @@ const SettingsEditor = ({ settings, updateSettings = ()=>{}, EditorThemeNameList
|
||||
onChange={(e)=>handleFieldChange('fontSize', e)}
|
||||
/>
|
||||
<datalist id='font-sizes'>
|
||||
<option value='1' />
|
||||
<option value='1' />
|
||||
</datalist>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
@import (less) './client/icons/customIcons.less';
|
||||
@import (less) '../../../icons/customIcons.less';
|
||||
@import (less) '@themes/fonts/5e/fonts.less';
|
||||
|
||||
.snippetBar {
|
||||
--menuColor : #DDDDDD;
|
||||
--textColor : black;
|
||||
--hoverMenuColor : #999;
|
||||
--hoverMenuColor : #999999;
|
||||
|
||||
@menuHeight : 25px;
|
||||
position : relative;
|
||||
display : flex;
|
||||
flex-wrap : wrap-reverse;
|
||||
reading-flow : flex-visual;
|
||||
justify-content : space-between;
|
||||
height : auto;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-size : 0.65rem;
|
||||
font-weight : 800;
|
||||
color : var(--textColor);
|
||||
text-transform : uppercase;
|
||||
background-color : var(--menuColor);
|
||||
font-size : .65rem;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-weight: 800;
|
||||
reading-flow : flex-visual;
|
||||
|
||||
.editors {
|
||||
display : flex;
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
&.editorTool:not(.active) { cursor : not-allowed; }
|
||||
|
||||
&:hover,&.selected { background-color : var(--hoverMenuColor) }
|
||||
&:hover,&.selected { background-color : var(--hoverMenuColor); }
|
||||
&.text {
|
||||
.tooltipLeft('Brew Editor');
|
||||
}
|
||||
@@ -199,9 +199,7 @@
|
||||
background : grey;
|
||||
border-radius : 12px;
|
||||
}
|
||||
&:hover {
|
||||
background-color : var(--hoverMenuColor);
|
||||
}
|
||||
&:hover { background-color : var(--hoverMenuColor); }
|
||||
&:disabled {
|
||||
color : gray;
|
||||
cursor : not-allowed;
|
||||
@@ -227,7 +225,7 @@
|
||||
}
|
||||
|
||||
.editor.darkMode .snippetBar {
|
||||
--menuColor : #666;
|
||||
--textColor : #eee;
|
||||
--hoverMenuColor : #444;
|
||||
--menuColor : #666666;
|
||||
--textColor : #EEEEEE;
|
||||
--hoverMenuColor : #444444;
|
||||
}
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
.tags {
|
||||
|
||||
.tagInputWrap {
|
||||
display:grid;
|
||||
grid-template-columns: 200px 3fr;
|
||||
gap:10px;
|
||||
display : grid;
|
||||
grid-template-columns : 200px 3fr;
|
||||
gap : 10px;
|
||||
}
|
||||
|
||||
.list input {
|
||||
border-radius: 5px;
|
||||
}
|
||||
.list input { border-radius : 5px; }
|
||||
|
||||
.tagInput-dropdown {
|
||||
.dropdown-options {
|
||||
.item {
|
||||
&.type {
|
||||
background-color: #00800035;
|
||||
}
|
||||
&.group {
|
||||
background-color: #50505035;
|
||||
}
|
||||
&.meta {
|
||||
background-color: #00008035;
|
||||
}
|
||||
&.system {
|
||||
background-color: #80000035;
|
||||
}
|
||||
&.type { background-color : #00800035; }
|
||||
&.group { background-color : #50505035; }
|
||||
&.meta { background-color : #00008035; }
|
||||
&.system { background-color : #80000035; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -105,9 +105,7 @@
|
||||
&::placeholder { color : var(--input-placeholder-clr); }
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
color: var(--input-text-clr);
|
||||
}
|
||||
input[type='range'] { color : var(--input-text-clr); }
|
||||
|
||||
&.description {
|
||||
flex : 1;
|
||||
@@ -151,9 +149,7 @@
|
||||
max-width : 150px;
|
||||
}
|
||||
&.themes {
|
||||
.value {
|
||||
overflow : visible;
|
||||
}
|
||||
.value { overflow : visible; }
|
||||
button {
|
||||
padding-right : 5px;
|
||||
padding-left : 5px;
|
||||
@@ -165,7 +161,7 @@
|
||||
color : dimgray;
|
||||
background-color : darkgray;
|
||||
}
|
||||
.item.dropdown-input { border:none;}
|
||||
.item.dropdown-input { border : none;}
|
||||
.item {
|
||||
position : relative;
|
||||
overflow : visible;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import '@sharedStyles/core.less';
|
||||
.homebrew {
|
||||
height : 100%;
|
||||
background-color:@steel;
|
||||
height : 100%;
|
||||
background-color : @steel;
|
||||
&.deployment { background-color : darkred; }
|
||||
|
||||
.sitePage {
|
||||
|
||||
@@ -39,9 +39,7 @@
|
||||
flex-grow : 1;
|
||||
min-width : 300px;
|
||||
}
|
||||
>.brewTitle {
|
||||
cursor:auto;
|
||||
}
|
||||
>.brewTitle { cursor : auto; }
|
||||
}
|
||||
// "NaturalCrit" logo
|
||||
.navLogo {
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function(){
|
||||
return ()=>{
|
||||
document.removeEventListener('print:startprep', handlePrintStartPrep);
|
||||
document.removeEventListener('print:finishedprep', handlePrintPrepFinished);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handlePrintStartPrep = ()=>{ setPrinting(true); };
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
isolation : isolate;
|
||||
break-inside : avoid;
|
||||
-webkit-column-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
.thumbnail {
|
||||
position : absolute;
|
||||
top : 0;
|
||||
@@ -28,9 +29,18 @@
|
||||
}
|
||||
}
|
||||
.info {
|
||||
font-size : 0.9em;
|
||||
|
||||
& > span { line-height : 1.5em; }
|
||||
a { color : inherit; }
|
||||
position : initial;
|
||||
bottom : 2px;
|
||||
font-size : 0.9em;
|
||||
& > span {
|
||||
margin-right : 12px;
|
||||
line-height : 1.5em;
|
||||
|
||||
a { color : inherit; }
|
||||
}
|
||||
}
|
||||
.brewTags span {
|
||||
display : inline-block;
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Alata&display=swap');
|
||||
@import '@sharedStyles/colors.less';
|
||||
|
||||
.noColumns() {
|
||||
-webkit-column-width : auto;
|
||||
-moz-column-width : auto;
|
||||
column-width : auto;
|
||||
-webkit-column-count : auto;
|
||||
-moz-column-count : auto;
|
||||
column-count : auto;
|
||||
column-fill : auto;
|
||||
-webkit-column-gap : normal;
|
||||
-moz-column-gap : normal;
|
||||
column-gap : normal;
|
||||
height : auto;
|
||||
min-height : 279.4mm;
|
||||
margin : 20px auto;
|
||||
contain : unset;
|
||||
}
|
||||
.listPage {
|
||||
.content {
|
||||
padding : clamp(20px, 5vh, 200px) clamp(20px, 20vw, 200px);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable max-lines */
|
||||
import './editPage.less';
|
||||
|
||||
// Common imports
|
||||
@@ -9,32 +8,15 @@ import _ from 'lodash';
|
||||
|
||||
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||
|
||||
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx'
|
||||
|
||||
import SplitPane from '@components/splitPane/splitPane.jsx';
|
||||
import Editor from '../../editor/editor.jsx';
|
||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx';
|
||||
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import NewBrewItem from '@navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '@navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '@navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
|
||||
// Page specific imports
|
||||
import Headtags from '../../../../vitreum/headtags.js';
|
||||
const Meta = Headtags.Meta;
|
||||
import { md5 } from 'hash-wasm';
|
||||
import { gzipSync, strToU8 } from 'fflate';
|
||||
import { makePatches, stringifyPatches } from '@sanity/diff-match-patch';
|
||||
|
||||
import ShareNavItem from '@navbar/share.navitem.jsx';
|
||||
import LockNotification from './lockNotification/lockNotification.jsx';
|
||||
import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js';
|
||||
import googleDriveIcon from '../../googleDrive.svg';
|
||||
|
||||
@@ -43,8 +25,11 @@ const STYLEKEY = 'HB_newPage_style';
|
||||
const SNIPKEY = 'HB_newPage_snippets';
|
||||
const METAKEY = 'HB_newPage_meta';
|
||||
|
||||
const useLocalStorage = false;
|
||||
const sandbox = false;
|
||||
const useLocalStorage = false;
|
||||
const sandbox = false;
|
||||
const showFloatingButtons = false;
|
||||
const showEditorButtons = true;
|
||||
const pageName = "editPage";
|
||||
|
||||
const EditPage = (props)=>{
|
||||
props = {
|
||||
@@ -55,26 +40,13 @@ const EditPage = (props)=>{
|
||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||
const [saveGoogle, setSaveGoogle] = useState(!!props.brew.googleId);
|
||||
const [error, setError] = useState(null);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
const [alertTrashedGoogleBrew, setAlertTrashedGoogleBrew] = useState(props.brew.trashed);
|
||||
const [alertNoGoogleToTransfer, setAlertNoGoogleToTransfer] = useState(false);
|
||||
const [alertOwnershipToTransfer, setAlertOwnershipToTransfer] = useState(false);
|
||||
const [confirmGoogleTransfer, setConfirmGoogleTransfer] = useState(false);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
|
||||
|
||||
const updateBrew = (newData)=>setCurrentBrew((prevBrew)=>({
|
||||
...prevBrew,
|
||||
style : newData.style,
|
||||
text : newData.text,
|
||||
snippets : newData.snippets
|
||||
}));
|
||||
|
||||
const handleGoogleClick = ()=>{
|
||||
if(currentBrew.authors.length > 0 && global.account?.username !== currentBrew.authors[0]) {
|
||||
setAlertOwnershipToTransfer(true);
|
||||
@@ -106,8 +78,6 @@ const EditPage = (props)=>{
|
||||
};
|
||||
|
||||
const save = async (brew, saveToGoogle)=>{
|
||||
setHTMLErrors(hbfm.validate(brew.text));
|
||||
|
||||
await updateHistory(brew).catch(console.error);
|
||||
await versionHistoryGarbageCollection().catch(console.error);
|
||||
|
||||
@@ -163,7 +133,7 @@ const EditPage = (props)=>{
|
||||
|
||||
{alertOwnershipToTransfer && (
|
||||
<div className='errorContainer'>
|
||||
You must be the Owner to transfer between the Homebrewery and Google Drive!
|
||||
You must be the Owner to transfer between the Homebrewery and Google Drive!
|
||||
The owner of this file is {currentBrew.authors[0]}.
|
||||
<br></br>
|
||||
<div className='confirm' onClick={closeAlerts}> Okay </div>
|
||||
@@ -201,51 +171,13 @@ const EditPage = (props)=>{
|
||||
</Nav.item>
|
||||
);
|
||||
|
||||
const renderAutoSaveButton = ()=>(
|
||||
<Nav.item onClick={toggleAutoSave}>
|
||||
Autosave <i className={autoSaveEnabled ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
|
||||
</Nav.item>
|
||||
);
|
||||
|
||||
const renderNavbar = ()=>{
|
||||
return <Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{currentBrew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{renderGoogleDriveIcon()}
|
||||
{error
|
||||
? <ErrorNavItem error={error} clearError={clearError} />
|
||||
: <Nav.dropdown className='save-menu'>
|
||||
{renderSaveButton()}
|
||||
{renderAutoSaveButton()}
|
||||
</Nav.dropdown>}
|
||||
<NewBrewItem />
|
||||
<PrintNavItem />
|
||||
<HelpNavItem />
|
||||
<VaultNavItem />
|
||||
<ShareNavItem brew={currentBrew} currentPage={currentBrewRendererPageNum} />
|
||||
<RecentNavItem brew={currentBrew} storageKey='edit' />
|
||||
<AccountNavItem/>
|
||||
</Nav.section>
|
||||
</Navbar>;
|
||||
};
|
||||
|
||||
const {
|
||||
handleSplitMove,
|
||||
handleBrewChange,
|
||||
toggleAutoSave,
|
||||
clearError,
|
||||
renderSaveButton,
|
||||
autoSaveEnabled,
|
||||
trySave
|
||||
renderPanels,
|
||||
trySave,
|
||||
} = useCommonEditPageFunctions({
|
||||
saveGoogle,
|
||||
error,
|
||||
setError,
|
||||
setThemeBundle,
|
||||
HTMLErrors,
|
||||
setHTMLErrors,
|
||||
currentBrew,
|
||||
setCurrentBrew,
|
||||
useLocalStorage,
|
||||
@@ -255,51 +187,16 @@ const EditPage = (props)=>{
|
||||
METAKEY,
|
||||
hbfm,
|
||||
sandbox,
|
||||
showFloatingButtons,
|
||||
lastSavedBrew,
|
||||
editorRef,
|
||||
save,
|
||||
pageName,
|
||||
showEditorButtons,
|
||||
renderGoogleDriveIcon,
|
||||
userThemes: props.userThemes
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='editPage sitePage'>
|
||||
<Meta name='robots' content='noindex, nofollow' />
|
||||
|
||||
{renderNavbar()}
|
||||
|
||||
{currentBrew.lock && <LockNotification shareId={currentBrew.shareId} message={currentBrew.lock.editMessage} reviewRequested={currentBrew.lock.reviewRequested}/>}
|
||||
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={handleSplitMove}>
|
||||
<Editor
|
||||
ref={editorRef}
|
||||
brew={currentBrew}
|
||||
onBrewChange={handleBrewChange}
|
||||
reportError={setError}
|
||||
renderer={currentBrew.renderer}
|
||||
userThemes={props.userThemes}
|
||||
themeBundle={themeBundle}
|
||||
updateBrew={updateBrew}
|
||||
onCursorPageChange={setCurrentEditorCursorPageNum}
|
||||
onViewPageChange={setCurrentEditorViewPageNum}
|
||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||
/>
|
||||
<BrewRenderer
|
||||
text={currentBrew.text}
|
||||
style={currentBrew.style}
|
||||
renderer={currentBrew.renderer}
|
||||
themeBundle={themeBundle}
|
||||
errors={HTMLErrors}
|
||||
lang={currentBrew.lang}
|
||||
onPageChange={setCurrentBrewRendererPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
allowPrint={true}
|
||||
/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return renderPanels();
|
||||
};
|
||||
|
||||
export default EditPage;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import './homePage.less';
|
||||
|
||||
// Common imports
|
||||
@@ -9,35 +8,19 @@ import _ from 'lodash';
|
||||
|
||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||
|
||||
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx'
|
||||
|
||||
import SplitPane from '@components/splitPane/splitPane.jsx';
|
||||
import Editor from '../../editor/editor.jsx';
|
||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import NewBrewItem from '@navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '@navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '@navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
|
||||
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx';
|
||||
|
||||
// Page specific imports
|
||||
import Headtags from '@vitreum/headtags.js';
|
||||
const Meta = Headtags.Meta;
|
||||
|
||||
const BREWKEY = 'HB_newPage_content';
|
||||
const STYLEKEY = 'HB_newPage_style';
|
||||
const SNIPKEY = 'HB_newPage_snippets';
|
||||
const METAKEY = 'HB_newPage_meta';
|
||||
|
||||
const useLocalStorage = false;
|
||||
const sandbox = true;
|
||||
const useLocalStorage = false;
|
||||
const sandbox = true;
|
||||
const showFloatingButtons = true;
|
||||
const showEditorButtons = false;
|
||||
const pageName = "homePage";
|
||||
|
||||
const HomePage =(props)=>{
|
||||
props = {
|
||||
@@ -48,13 +31,7 @@ const HomePage =(props)=>{
|
||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||
const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false);
|
||||
const [error, setError] = useState(undefined);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
|
||||
|
||||
const save = async (brew, saveToGoogle)=>{
|
||||
@@ -72,35 +49,12 @@ const HomePage =(props)=>{
|
||||
window.location = `/edit/${saved.editId}`;
|
||||
};
|
||||
|
||||
const renderNavbar = ()=>{
|
||||
return <Navbar ver={props.ver}>
|
||||
<Nav.section>
|
||||
{error
|
||||
? <ErrorNavItem error={error} clearError={clearError} />
|
||||
: renderSaveButton()}
|
||||
<NewBrewItem />
|
||||
<PrintNavItem />
|
||||
<HelpNavItem />
|
||||
<VaultNavItem />
|
||||
<RecentNavItem />
|
||||
<AccountNavItem />
|
||||
</Nav.section>
|
||||
</Navbar>;
|
||||
};
|
||||
|
||||
const {
|
||||
handleSplitMove,
|
||||
handleBrewChange,
|
||||
clearError,
|
||||
renderSaveButton,
|
||||
unsavedChanges,
|
||||
trySave
|
||||
renderPanels
|
||||
} = useCommonEditPageFunctions({
|
||||
saveGoogle,
|
||||
error,
|
||||
setError,
|
||||
setThemeBundle,
|
||||
HTMLErrors,
|
||||
setHTMLErrors,
|
||||
currentBrew,
|
||||
setCurrentBrew,
|
||||
useLocalStorage,
|
||||
@@ -110,49 +64,15 @@ const HomePage =(props)=>{
|
||||
METAKEY,
|
||||
hbfm,
|
||||
sandbox,
|
||||
showFloatingButtons,
|
||||
lastSavedBrew,
|
||||
editorRef,
|
||||
save,
|
||||
pageName,
|
||||
showEditorButtons,
|
||||
userThemes: {}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='homePage sitePage'>
|
||||
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
|
||||
{renderNavbar()}
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={handleSplitMove}>
|
||||
<Editor
|
||||
ref={editorRef}
|
||||
brew={currentBrew}
|
||||
onBrewChange={handleBrewChange}
|
||||
renderer={currentBrew.renderer}
|
||||
showEditButtons={false}
|
||||
themeBundle={themeBundle}
|
||||
onCursorPageChange={setCurrentEditorCursorPageNum}
|
||||
onViewPageChange={setCurrentEditorViewPageNum}
|
||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||
/>
|
||||
<BrewRenderer
|
||||
text={currentBrew.text}
|
||||
style={currentBrew.style}
|
||||
renderer={currentBrew.renderer}
|
||||
themeBundle={themeBundle}
|
||||
onPageChange={setCurrentBrewRendererPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
<div className={`floatingSaveButton${unsavedChanges ? ' show' : ''}`} onClick={()=>trySave(true, true, saveGoogle)}>
|
||||
Save current <i className='fas fa-save' />
|
||||
</div>
|
||||
|
||||
<a href='/new' className='floatingNewButton'>
|
||||
Create your own <i className='fas fa-magic' />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
return renderPanels();
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
|
||||
.navItem.save {
|
||||
background-color : @orange;
|
||||
transition:all 0.2s;
|
||||
transition : all 0.2s;
|
||||
&:hover { background-color : @green; }
|
||||
|
||||
&.neverSaved {
|
||||
translate:-100%;
|
||||
opacity: 0;
|
||||
background-color :#333;
|
||||
cursor:auto;
|
||||
cursor : auto;
|
||||
background-color : #333333;
|
||||
opacity : 0;
|
||||
translate : -100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable max-lines */
|
||||
import './newPage.less';
|
||||
|
||||
// Common imports
|
||||
@@ -8,24 +7,8 @@ import { hbfm } from 'marked-hbfm';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '@shared/helpers.js';
|
||||
|
||||
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx'
|
||||
|
||||
import SplitPane from '@components/splitPane/splitPane.jsx';
|
||||
import Editor from '../../editor/editor.jsx';
|
||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import NewBrewItem from '@navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '@navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '@navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx';
|
||||
|
||||
// Page specific imports
|
||||
const BREWKEY = 'HB_newPage_content';
|
||||
@@ -35,8 +18,11 @@ const METAKEY = 'HB_newPage_meta';
|
||||
|
||||
const SAVEKEYPREFIX = 'HB_editor_defaultSave_';
|
||||
|
||||
const useLocalStorage = true;
|
||||
const sandbox = true;
|
||||
const useLocalStorage = true;
|
||||
const sandbox = true;
|
||||
const showFloatingButtons = false;
|
||||
const showEditorButtons = true;
|
||||
const pageName = "newPage";
|
||||
|
||||
const NewPage = (props)=>{
|
||||
props = {
|
||||
@@ -47,13 +33,7 @@ const NewPage = (props)=>{
|
||||
const [currentBrew, setCurrentBrew] = useState(props.brew);
|
||||
const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false);
|
||||
const [error, setError] = useState(null);
|
||||
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
|
||||
|
||||
useEffect(()=>{
|
||||
@@ -116,37 +96,12 @@ const NewPage = (props)=>{
|
||||
window.location = `/edit/${savedBrew.editId}`;
|
||||
};
|
||||
|
||||
const renderNavbar = ()=>(
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{currentBrew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{error
|
||||
? <ErrorNavItem error={error} clearError={clearError} />
|
||||
: renderSaveButton()}
|
||||
<NewBrewItem />
|
||||
<PrintNavItem />
|
||||
<HelpNavItem />
|
||||
<VaultNavItem />
|
||||
<RecentNavItem />
|
||||
<AccountNavItem />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
const {
|
||||
handleSplitMove,
|
||||
handleBrewChange,
|
||||
clearError,
|
||||
renderSaveButton
|
||||
renderPanels
|
||||
} = useCommonEditPageFunctions({
|
||||
saveGoogle,
|
||||
error,
|
||||
setError,
|
||||
setThemeBundle,
|
||||
HTMLErrors,
|
||||
setHTMLErrors,
|
||||
currentBrew,
|
||||
setCurrentBrew,
|
||||
useLocalStorage,
|
||||
@@ -156,44 +111,15 @@ const NewPage = (props)=>{
|
||||
METAKEY,
|
||||
hbfm,
|
||||
sandbox,
|
||||
showFloatingButtons,
|
||||
lastSavedBrew,
|
||||
editorRef,
|
||||
save,
|
||||
pageName,
|
||||
showEditorButtons,
|
||||
userThemes: props.userThemes
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='newPage sitePage'>
|
||||
{renderNavbar()}
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={handleSplitMove}>
|
||||
<Editor
|
||||
ref={editorRef}
|
||||
brew={currentBrew}
|
||||
onBrewChange={handleBrewChange}
|
||||
renderer={currentBrew.renderer}
|
||||
userThemes={props.userThemes}
|
||||
themeBundle={themeBundle}
|
||||
onCursorPageChange={setCurrentEditorCursorPageNum}
|
||||
onViewPageChange={setCurrentEditorViewPageNum}
|
||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||
/>
|
||||
<BrewRenderer
|
||||
text={currentBrew.text}
|
||||
style={currentBrew.style}
|
||||
renderer={currentBrew.renderer}
|
||||
themeBundle={themeBundle}
|
||||
errors={HTMLErrors}
|
||||
lang={currentBrew.lang}
|
||||
onPageChange={setCurrentBrewRendererPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
allowPrint={true}
|
||||
/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return renderPanels();
|
||||
};
|
||||
|
||||
export default NewPage;
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
.newPage {
|
||||
.navItem.save {
|
||||
background-color : @orange;
|
||||
transition:all 0.2s;
|
||||
transition : all 0.2s;
|
||||
&:hover { background-color : @green; }
|
||||
|
||||
&.neverSaved {
|
||||
translate:-100%;
|
||||
opacity: 0;
|
||||
background-color :#333;
|
||||
cursor:auto;
|
||||
cursor : auto;
|
||||
background-color : #333333;
|
||||
opacity : 0;
|
||||
translate : -100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
*:not(input) { user-select : none; }
|
||||
|
||||
.form {
|
||||
background:white;
|
||||
}
|
||||
.form { background : white; }
|
||||
|
||||
:where(.content .dataGroup) {
|
||||
width : 100%;
|
||||
@@ -174,8 +172,8 @@
|
||||
height : 100%;
|
||||
max-height : 100%;
|
||||
padding : 70px 50px;
|
||||
overflow-y : scroll;
|
||||
container-type : inline-size;
|
||||
overflow-y : scroll;
|
||||
|
||||
h3 { font-size : 25px; }
|
||||
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
import React, { useState, useEffect, useEffectEvent, useRef } from 'react';
|
||||
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
||||
import _ from 'lodash';
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
|
||||
import Headtags from '@vitreum/headtags.js';
|
||||
import SplitPane from '@components/splitPane/splitPane.jsx';
|
||||
import Editor from '../editor/editor.jsx';
|
||||
import BrewRenderer from '../brewRenderer/brewRenderer.jsx';
|
||||
import LockNotification from '../pages/editPage/lockNotification/lockNotification.jsx';
|
||||
const Meta = Headtags.Meta;
|
||||
|
||||
//===---- Navbar
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import NewBrewItem from '@navbar/newbrew.navitem.jsx';
|
||||
import AccountNavItem from '@navbar/account.navitem.jsx';
|
||||
import ErrorNavItem from '@navbar/error-navitem.jsx';
|
||||
import HelpNavItem from '@navbar/help.navitem.jsx';
|
||||
import VaultNavItem from '@navbar/vault.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import ShareNavItem from '@navbar/share.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
|
||||
const AUTOSAVE_KEY = 'HB_editor_autoSaveOn';
|
||||
|
||||
@@ -12,10 +31,8 @@ const UNSAVED_WARNING_POPUP_TIMEOUT = 4000; //Show the warning for 4 seconds
|
||||
export default function useCommonEditPageFunctions(dependencies) {
|
||||
const {
|
||||
saveGoogle,
|
||||
error,
|
||||
setError,
|
||||
setThemeBundle,
|
||||
HTMLErrors,
|
||||
setHTMLErrors,
|
||||
currentBrew,
|
||||
setCurrentBrew,
|
||||
useLocalStorage,
|
||||
@@ -25,27 +42,37 @@ export default function useCommonEditPageFunctions(dependencies) {
|
||||
METAKEY,
|
||||
hbfm,
|
||||
sandbox,
|
||||
showFloatingButtons,
|
||||
lastSavedBrew,
|
||||
editorRef,
|
||||
save,
|
||||
pageName,
|
||||
renderGoogleDriveIcon = ()=>{},
|
||||
showEditorButtons,
|
||||
userThemes = {}
|
||||
} = dependencies;
|
||||
|
||||
const [isSaving , setIsSaving] = useState(false);
|
||||
const [lastSavedTime , setLastSavedTime] = useState(new Date());
|
||||
const [autoSaveEnabled , setAutoSaveEnabled] = useState(!sandbox);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [lastSavedTime, setLastSavedTime] = useState(new Date());
|
||||
const [autoSaveEnabled, setAutoSaveEnabled] = useState(!sandbox);
|
||||
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true);
|
||||
const [unsavedChanges , setUnsavedChanges] = useState(false);
|
||||
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(currentBrew.text));
|
||||
|
||||
const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1);
|
||||
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
|
||||
const unsavedChangesRef = useRef(unsavedChanges); // onBeforeUnload lives outside React and needs ref to unsavedChanges
|
||||
const warnUnsavedTimeout = useRef(null); // timers live outside React and need ref to consistently track time
|
||||
const saveTimeout = useRef(null);
|
||||
const editorRef = useRef(null);
|
||||
|
||||
//==--------- Page setup ----------==//
|
||||
useEffect(()=>{
|
||||
const autoSavePref = !sandbox && JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
|
||||
setAutoSaveEnabled(autoSavePref);
|
||||
setWarnUnsavedChanges(!autoSavePref);
|
||||
setHTMLErrors(hbfm.validate(currentBrew.text));
|
||||
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
|
||||
|
||||
const handleControlKeys = (e)=>{
|
||||
@@ -64,7 +91,7 @@ export default function useCommonEditPageFunctions(dependencies) {
|
||||
};
|
||||
return ()=>{
|
||||
document.removeEventListener('keydown', handleControlKeys);
|
||||
window.onBeforeUnload = null;
|
||||
window.onbeforeunload = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -105,7 +132,7 @@ export default function useCommonEditPageFunctions(dependencies) {
|
||||
if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({
|
||||
renderer : value.renderer,
|
||||
theme : value.theme,
|
||||
lang : value.lang
|
||||
lang : value.lang
|
||||
}));
|
||||
}
|
||||
};
|
||||
@@ -132,10 +159,9 @@ export default function useCommonEditPageFunctions(dependencies) {
|
||||
saveTimeout.current = setTimeout(async ()=>{
|
||||
setIsSaving(true);
|
||||
setError(null);
|
||||
setHTMLErrors(hbfm.validate(currentBrew.text));
|
||||
await save(currentBrew, saveToGoogle)
|
||||
.catch((err)=>{
|
||||
setError(err);
|
||||
});
|
||||
.catch((err)=>{setError(err);});
|
||||
setIsSaving(false);
|
||||
setLastSavedTime(new Date());
|
||||
if(!autoSaveEnabled) resetWarnUnsavedTimer();
|
||||
@@ -155,7 +181,7 @@ export default function useCommonEditPageFunctions(dependencies) {
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
|
||||
Reminder...
|
||||
<div className='errorContainer'>{text}</div>
|
||||
<div className='errorContainer'>{text}</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
@@ -171,14 +197,98 @@ export default function useCommonEditPageFunctions(dependencies) {
|
||||
return <Nav.item className='save saved'>saved</Nav.item>;
|
||||
};
|
||||
|
||||
//TODO: Candidate for refactor/rename; used with history tool to load previous verion; may overlap with snippet injection or handleBrewChange
|
||||
const updateBrew = (newData)=>setCurrentBrew((prevBrew)=>({
|
||||
...prevBrew,
|
||||
style : newData.style,
|
||||
text : newData.text,
|
||||
snippets : newData.snippets
|
||||
}));
|
||||
|
||||
const renderNavbar = ()=>(
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{currentBrew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
<Nav.section>
|
||||
{renderGoogleDriveIcon()}
|
||||
{error
|
||||
? <ErrorNavItem error={error} clearError={clearError} />
|
||||
: <Nav.dropdown className='save-menu'>
|
||||
{renderSaveButton()}
|
||||
{pageName == "editPage" && renderAutoSaveButton()}
|
||||
</Nav.dropdown>}
|
||||
<NewBrewItem />
|
||||
<PrintNavItem />
|
||||
<HelpNavItem />
|
||||
<VaultNavItem />
|
||||
{(pageName == "editPage") && <ShareNavItem brew={currentBrew} currentPage={currentBrewRendererPageNum} />}
|
||||
<RecentNavItem brew={currentBrew} storageKey={(pageName == "editPage") ? 'edit' : undefined} />
|
||||
<AccountNavItem />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
const renderAutoSaveButton = ()=>(
|
||||
<Nav.item onClick={toggleAutoSave}>
|
||||
Autosave <i className={autoSaveEnabled ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
|
||||
</Nav.item>
|
||||
);
|
||||
|
||||
const renderFloatingSaveButtons = ()=>(
|
||||
<>
|
||||
<div className={`floatingSaveButton${unsavedChanges ? ' show' : ''}`} onClick={()=>trySave(true, true, saveGoogle)}>
|
||||
Save current <i className='fas fa-save' />
|
||||
</div>
|
||||
<a href='/new' className='floatingNewButton'>
|
||||
Create your own <i className='fas fa-magic' />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
|
||||
const renderPanels = ()=>(
|
||||
<div className= {`${pageName} sitePage`}>
|
||||
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
|
||||
{(pageName == 'editPage') && <Meta name='robots' content='noindex, nofollow' />}
|
||||
{renderNavbar()}
|
||||
{currentBrew.lock && <LockNotification shareId={currentBrew.shareId} message={currentBrew.lock.editMessage} reviewRequested={currentBrew.lock.reviewRequested}/>}
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={handleSplitMove}>
|
||||
<Editor
|
||||
ref={editorRef}
|
||||
brew={currentBrew}
|
||||
onBrewChange={handleBrewChange}
|
||||
reportError={setError}
|
||||
renderer={currentBrew.renderer}
|
||||
userThemes={userThemes}
|
||||
showEditButtons={showEditorButtons}
|
||||
themeBundle={themeBundle}
|
||||
updateBrew={updateBrew}
|
||||
onCursorPageChange={setCurrentEditorCursorPageNum}
|
||||
onViewPageChange={setCurrentEditorViewPageNum}
|
||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||
/>
|
||||
<BrewRenderer
|
||||
lang={currentBrew.lang}
|
||||
text={currentBrew.text}
|
||||
style={currentBrew.style}
|
||||
renderer={currentBrew.renderer}
|
||||
themeBundle={themeBundle}
|
||||
errors={HTMLErrors}
|
||||
onPageChange={setCurrentBrewRendererPageNum}
|
||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||
allowPrint={true} //TODO: candidate for cleanup in brewRenderer. Homepage only place where allowPrint = false. Vote OK to allow print everywhere
|
||||
/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
{showFloatingButtons && renderFloatingSaveButtons()}
|
||||
</div>
|
||||
);
|
||||
|
||||
return {
|
||||
handleSplitMove,
|
||||
handleBrewChange,
|
||||
toggleAutoSave,
|
||||
clearError,
|
||||
trySave,
|
||||
renderSaveButton,
|
||||
autoSaveEnabled,
|
||||
unsavedChanges,
|
||||
}
|
||||
renderPanels
|
||||
};
|
||||
}
|
||||
+14
-4
@@ -21,6 +21,15 @@
|
||||
|
||||
let prefix = '';
|
||||
|
||||
// Default page title
|
||||
let result ='The Homebrewery';
|
||||
|
||||
// Add brew title, if it exists
|
||||
if (title) {
|
||||
result = `${title} - ${result}`;
|
||||
}
|
||||
|
||||
// Add prefix for Stage/PR Deployment/Local install
|
||||
if (url && url?.includes('://homebrewery-stage.')) {
|
||||
prefix = `Stage `;
|
||||
} else if (url?.includes('://homebrewery-pr-')) {
|
||||
@@ -30,12 +39,13 @@
|
||||
prefix = 'Local ';
|
||||
}
|
||||
|
||||
if (title) {
|
||||
document.title = `${prefix} - ${title} - The Homebrewery`;
|
||||
} else if (prefix) {
|
||||
document.title = `${prefix} - The Homebrewery`;
|
||||
if (prefix) {
|
||||
result = `${prefix} - ${result}`;
|
||||
}
|
||||
|
||||
// Set page title
|
||||
document.title = result;
|
||||
|
||||
if (window.location.pathname.startsWith('/admin')) {
|
||||
import('/client/admin/main.jsx');
|
||||
} else {
|
||||
|
||||
Generated
+1888
-354
File diff suppressed because it is too large
Load Diff
+15
-28
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "3.23.0",
|
||||
"version": "3.24.1",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"npm": ">=10.8 <12",
|
||||
@@ -94,20 +94,18 @@
|
||||
"@babel/runtime": "^8.0.5",
|
||||
"@codemirror/autocomplete": "^6.20.3",
|
||||
"@codemirror/commands": "^6.11.1",
|
||||
"@codemirror/highlight": "^0.19.8",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-javascript": "^6.2.5",
|
||||
"@codemirror/lang-html": "^6.4.12",
|
||||
"@codemirror/lang-markdown": "^6.5.2",
|
||||
"@codemirror/language": "^6.12.2",
|
||||
"@codemirror/language-data": "^6.5.2",
|
||||
"@codemirror/search": "^6.6.0",
|
||||
"@codemirror/state": "^6.7.5",
|
||||
"@codemirror/view": "^6.43.12",
|
||||
"@dmsnell/diff-match-patch": "^1.1.0",
|
||||
"@googleapis/drive": "^26.0.0",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@codemirror/state": "^6.7.6",
|
||||
"@codemirror/view": "^6.43.13",
|
||||
"@googleapis/drive": "^26.0.1",
|
||||
"@lezer/highlight": "^1.2.4",
|
||||
"@oddbird/css-anchor-positioning": "^0.10.2",
|
||||
"@sanity/diff-match-patch": "^3.2.0",
|
||||
"@sanity/diff-match-patch": "^3.2.1",
|
||||
"@vitejs/plugin-react": "^6.0.5",
|
||||
"body-parser": "^2.3.0",
|
||||
"classnames": "^2.5.1",
|
||||
@@ -121,38 +119,27 @@
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-static-gzip": "3.0.2",
|
||||
"fflate": "^0.8.3",
|
||||
"fs-extra": "^11.3.5",
|
||||
"fs-extra": "^11.4.1",
|
||||
"hash-wasm": "^4.12.0",
|
||||
"idb-keyval": "^6.2.5",
|
||||
"js-yaml": "^5.4.2",
|
||||
"jwt-simple": "^0.5.6",
|
||||
"less": "^4.9.1",
|
||||
"lodash": "^4.18.1",
|
||||
"marked": "15.0.12",
|
||||
"marked-alignment-paragraphs": "^1.0.0",
|
||||
"marked-definition-lists": "^1.0.1",
|
||||
"marked-diagrams-markdeep": "^1.0.1",
|
||||
"marked-emoji": "^3.0.0",
|
||||
"marked-extended-tables": "^2.0.1",
|
||||
"marked-gfm-heading-id": "^4.1.4",
|
||||
"marked-hbfm": "^1.0.1",
|
||||
"marked-nonbreaking-spaces": "^1.0.1",
|
||||
"marked-smartypants-lite": "^1.0.3",
|
||||
"marked-subsuper-text": "^1.0.4",
|
||||
"marked-variables": "^1.0.5",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"moment": "^2.31.0",
|
||||
"mongoose": "^9.10.1",
|
||||
"mongoose": "^9.10.2",
|
||||
"nanoid": "6.0.1",
|
||||
"nconf": "^0.13.0",
|
||||
"node": "^26.9.0",
|
||||
"prettier": "^3.9.8",
|
||||
"node": "^26.10.0",
|
||||
"prettier": "^3.9.9",
|
||||
"react": "^19.3.0",
|
||||
"react-dom": "^19.3.0",
|
||||
"react-frame-component": "^5.3.2",
|
||||
"react-router": "^8.4.0",
|
||||
"sanitize-filename": "1.6.4",
|
||||
"superagent": "^10.2.1"
|
||||
"superagent": "^10.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/stylelint-plugin": "^5.3.0",
|
||||
@@ -164,13 +151,13 @@
|
||||
"globals": "^16.4.0",
|
||||
"jest": "^30.5.2",
|
||||
"jest-expect-message": "^1.1.3",
|
||||
"jsdom": "^30.1.0",
|
||||
"jsdom": "^30.1.1",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"postcss-less": "^6.0.0",
|
||||
"stylelint": "^17.15.0",
|
||||
"stylelint-config-recess-order": "^7.7.0",
|
||||
"stylelint-config-recommended": "^18.0.0",
|
||||
"supertest": "^7.1.4",
|
||||
"vite": "^8.3.0"
|
||||
"supertest": "^7.3.0",
|
||||
"vite": "^8.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -2,3 +2,17 @@
|
||||
User-agent: *
|
||||
Disallow: /edit/
|
||||
|
||||
# AI / LLM training crawlers
|
||||
|
||||
User-agent: GPTBot
|
||||
User-agent: ClaudeBot
|
||||
User-agent: CCBot
|
||||
User-agent: Google-Extended
|
||||
User-agent: Bytespider
|
||||
User-agent: Applebot-Extended
|
||||
User-agent: Meta-ExternalAgent
|
||||
User-agent: Amazonbot
|
||||
Disallow: /share/
|
||||
Disallow: /download/
|
||||
Disallow: /print/
|
||||
Disallow: /edit/
|
||||
|
||||
+9
-12
@@ -13,7 +13,6 @@ const isProd = nodeEnv === 'production';
|
||||
|
||||
import HomebrewAPI from './homebrew.api.js';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
|
||||
|
||||
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
||||
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3';
|
||||
@@ -38,14 +37,14 @@ export default function createAdminApi(vite) {
|
||||
throw { HBErrorCode: '52', code: 401, message: 'Access denied' };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Search for up to 300 brews that have not been viewed or updated in 30 days and are shorter than 140 bytes
|
||||
const junkBrewsPipeline = [
|
||||
{ $match : {
|
||||
updatedAt : { $lt: Moment().subtract(30, 'days').toDate() },
|
||||
lastViewed : { $lt: Moment().subtract(30, 'days').toDate() }
|
||||
} },
|
||||
{ $project: { _id: 1, textBinSize: { $binarySize: '$textBin' }, updatedAt: 1, lastViewed: 1} },
|
||||
{ $project: { _id: 1, textBinSize: { $binarySize: '$textBin' }, updatedAt: 1, lastViewed: 1 } },
|
||||
{ $match: { textBinSize: { $lt: 140 } } },
|
||||
{ $limit: 300 }
|
||||
];
|
||||
@@ -53,14 +52,14 @@ export default function createAdminApi(vite) {
|
||||
// Search for up to 500 unauthored brews that have not been viewed or updated in two years
|
||||
const lostBrewsPipeline = [
|
||||
{
|
||||
$match: {
|
||||
authors: [],
|
||||
updatedAt: { $lt: Moment().subtract(2, 'years').toDate() },
|
||||
lastViewed: { $lt: Moment().subtract(2, 'years').toDate() }
|
||||
$match : {
|
||||
authors : [],
|
||||
updatedAt : { $lt: Moment().subtract(2, 'years').toDate() },
|
||||
lastViewed : { $lt: Moment().subtract(2, 'years').toDate() }
|
||||
}
|
||||
},
|
||||
{
|
||||
$limit: 500
|
||||
$limit : 500
|
||||
}
|
||||
];
|
||||
|
||||
@@ -71,7 +70,7 @@ export default function createAdminApi(vite) {
|
||||
|
||||
router.get('/admin/cleanupJunk', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(junkBrewsPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((objs)=>res.json({ count: objs.length, brewCollection : objs }))
|
||||
.then((objs)=>res.json({ count: objs.length, brewCollection: objs }))
|
||||
.catch((error)=>{
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
@@ -94,7 +93,7 @@ export default function createAdminApi(vite) {
|
||||
|
||||
router.get('/admin/cleanupLost', mw.adminOnly, (req, res)=>{
|
||||
HomebrewModel.aggregate(lostBrewsPipeline).option({ maxTimeMS: 60000 })
|
||||
.then((objs)=>res.json({ count: objs.length, brewCollection : objs }))
|
||||
.then((objs)=>res.json({ count: objs.length, brewCollection: objs }))
|
||||
.catch((error)=>{
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
@@ -148,8 +147,6 @@ export default function createAdminApi(vite) {
|
||||
brew[property] = cleanText(brew[property]);
|
||||
});
|
||||
|
||||
splitTextStyleAndMetadata(brew);
|
||||
|
||||
req.body = brew;
|
||||
|
||||
// Remove Account from request to prevent Admin user from being added to brew as an Author
|
||||
|
||||
+9
-7
@@ -137,7 +137,7 @@ export default async function createApp(vite) {
|
||||
return res.sendFile(`robots.txt`, { root: process.cwd() });
|
||||
});
|
||||
//serve brew for sharepage rerender
|
||||
app.get('/api/fetch/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res) => {
|
||||
app.get('/api/fetch/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res)=>{
|
||||
const { brew } = req;
|
||||
brew.authors.includes(req.account?.username)
|
||||
? sanitizeBrew(brew, 'shareAuthor')
|
||||
@@ -204,7 +204,7 @@ export default async function createApp(vite) {
|
||||
});
|
||||
|
||||
Stream.on('sendUpdate', (event, data)=>{
|
||||
console.log('Event:', event, '\nData:', data);
|
||||
if(isLocalEnvironment) { console.log('Event:', event, '\nData:', data); }
|
||||
res.write(`data: ${JSON.stringify({ ...data, eventType: event })}\n\n`);
|
||||
});
|
||||
});
|
||||
@@ -283,11 +283,13 @@ export default async function createApp(vite) {
|
||||
const safeProps = JSON.stringify(props).replace(/<(?=\/?script)/ig, '\\u003c');
|
||||
html = html.replace(
|
||||
'<head>',
|
||||
`<head>\n`
|
||||
+ `<script id="props">`
|
||||
+ `window.__INITIAL_PROPS__ = ` + safeProps
|
||||
+ `</script>\n`
|
||||
+ ogMetaTags
|
||||
()=>{ //Replace must use a function instead of string, else brews with `$` in contents are treated as replacement patterns
|
||||
return `<head>\n`
|
||||
+ `<script id="props">`
|
||||
+ `window.__INITIAL_PROPS__ = ${safeProps
|
||||
}</script>\n${
|
||||
ogMetaTags}`;
|
||||
}
|
||||
);
|
||||
|
||||
return html;
|
||||
|
||||
@@ -193,13 +193,13 @@ const api = {
|
||||
|
||||
const fixedStub = migrateSystemsToTags(stub);
|
||||
req.brew = fixedStub;
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
next();
|
||||
};
|
||||
},
|
||||
getCSS : async (req, res)=>{
|
||||
const { brew } = req;
|
||||
if(!brew) return res.status(404).send('');
|
||||
splitTextStyleAndMetadata(brew);
|
||||
if(!brew.style) return res.status(404).send('');
|
||||
|
||||
res.set({
|
||||
@@ -345,7 +345,6 @@ const api = {
|
||||
});
|
||||
|
||||
currentTheme = req.brew;
|
||||
splitTextStyleAndMetadata(currentTheme);
|
||||
if(!currentTheme.tags.some((tag)=>tag === 'meta:theme' || tag === 'meta:Theme'))
|
||||
throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', status: 422, HBErrorCode: '10' };
|
||||
themeName ??= currentTheme.title;
|
||||
@@ -384,7 +383,6 @@ const api = {
|
||||
// Initialize brew from request and body, destructure query params, and set the initial value for the after-save method
|
||||
const brewFromClient = api.excludePropsFromUpdate(req.body);
|
||||
const brewFromServer = req.brew;
|
||||
splitTextStyleAndMetadata(brewFromServer);
|
||||
|
||||
if(brewFromServer?.version !== brewFromClient?.version){
|
||||
console.log(`Version mismatch on brew ${brewFromClient.editId}`);
|
||||
|
||||
+111
-111
@@ -360,25 +360,25 @@ describe('Tests for api', ()=>{
|
||||
trashed : false,
|
||||
updatedAt : undefined,
|
||||
views : 0,
|
||||
bleedSize: {
|
||||
top: '.125in',
|
||||
bottom: '.125in',
|
||||
inner: '.125in',
|
||||
outer: '.125in',
|
||||
bleedSize : {
|
||||
top : '.125in',
|
||||
bottom : '.125in',
|
||||
inner : '.125in',
|
||||
outer : '.125in',
|
||||
},
|
||||
columns: '2',
|
||||
columnGutter: '.125in',
|
||||
legalAuthors: '',
|
||||
license: 'None',
|
||||
safetySpace: {
|
||||
top: '.25in',
|
||||
bottom: '.25in',
|
||||
outer: '.25in',
|
||||
inner: '.5in',
|
||||
columns : '2',
|
||||
columnGutter : '.125in',
|
||||
legalAuthors : '',
|
||||
license : 'None',
|
||||
safetySpace : {
|
||||
top : '.25in',
|
||||
bottom : '.25in',
|
||||
outer : '.25in',
|
||||
inner : '.5in',
|
||||
},
|
||||
trimSize: {
|
||||
width: '8.5in',
|
||||
height: '11in',
|
||||
trimSize : {
|
||||
width : '8.5in',
|
||||
height : '11in',
|
||||
},
|
||||
});
|
||||
expect(next).toHaveBeenCalled();
|
||||
@@ -471,25 +471,25 @@ describe('Tests for api', ()=>{
|
||||
renderer : 'v3',
|
||||
theme : 'phb',
|
||||
googleId : '12345',
|
||||
bleedSize: {
|
||||
top: '.125in',
|
||||
bottom: '.125in',
|
||||
inner: '.125in',
|
||||
outer: '.125in',
|
||||
bleedSize : {
|
||||
top : '.125in',
|
||||
bottom : '.125in',
|
||||
inner : '.125in',
|
||||
outer : '.125in',
|
||||
},
|
||||
columns: '2',
|
||||
columnGutter: '.125in',
|
||||
legalAuthors: '',
|
||||
license: 'None',
|
||||
safetySpace: {
|
||||
top: '.25in',
|
||||
bottom: '.25in',
|
||||
outer: '.25in',
|
||||
inner: '.5in',
|
||||
columns : '2',
|
||||
columnGutter : '.125in',
|
||||
legalAuthors : '',
|
||||
license : 'None',
|
||||
safetySpace : {
|
||||
top : '.25in',
|
||||
bottom : '.25in',
|
||||
outer : '.25in',
|
||||
inner : '.5in',
|
||||
},
|
||||
trimSize: {
|
||||
width: '8.5in',
|
||||
height: '11in',
|
||||
trimSize : {
|
||||
width : '8.5in',
|
||||
height : '11in',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -534,25 +534,25 @@ brew`);
|
||||
renderer : 'v3',
|
||||
theme : 'phb',
|
||||
googleId : '12345',
|
||||
bleedSize: {
|
||||
top: '.125in',
|
||||
bottom: '.125in',
|
||||
inner: '.125in',
|
||||
outer: '.125in',
|
||||
bleedSize : {
|
||||
top : '.125in',
|
||||
bottom : '.125in',
|
||||
inner : '.125in',
|
||||
outer : '.125in',
|
||||
},
|
||||
columns: '2',
|
||||
columnGutter: '.125in',
|
||||
legalAuthors: '',
|
||||
license: 'None',
|
||||
safetySpace: {
|
||||
top: '.25in',
|
||||
bottom: '.25in',
|
||||
outer: '.25in',
|
||||
inner: '.5in',
|
||||
columns : '2',
|
||||
columnGutter : '.125in',
|
||||
legalAuthors : '',
|
||||
license : 'None',
|
||||
safetySpace : {
|
||||
top : '.25in',
|
||||
bottom : '.25in',
|
||||
outer : '.25in',
|
||||
inner : '.5in',
|
||||
},
|
||||
trimSize: {
|
||||
width: '8.5in',
|
||||
height: '11in',
|
||||
trimSize : {
|
||||
width : '8.5in',
|
||||
height : '11in',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -687,16 +687,16 @@ brew`);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(res.send).toHaveBeenCalledWith({
|
||||
_id : '1',
|
||||
authors : ['test user'],
|
||||
createdAt : undefined,
|
||||
description : '',
|
||||
editId : expect.any(String),
|
||||
gDrive : false,
|
||||
pageCount : 1,
|
||||
published : false,
|
||||
renderer : 'V3',
|
||||
lang : 'en',
|
||||
_id : '1',
|
||||
authors : ['test user'],
|
||||
createdAt : undefined,
|
||||
description : '',
|
||||
editId : expect.any(String),
|
||||
gDrive : false,
|
||||
pageCount : 1,
|
||||
published : false,
|
||||
renderer : 'V3',
|
||||
lang : 'en',
|
||||
shareId : expect.any(String),
|
||||
style : undefined,
|
||||
tags : [],
|
||||
@@ -708,25 +708,25 @@ brew`);
|
||||
trashed : false,
|
||||
updatedAt : undefined,
|
||||
views : 0,
|
||||
bleedSize: {
|
||||
top: '.125in',
|
||||
bottom: '.125in',
|
||||
inner: '.125in',
|
||||
outer: '.125in',
|
||||
bleedSize : {
|
||||
top : '.125in',
|
||||
bottom : '.125in',
|
||||
inner : '.125in',
|
||||
outer : '.125in',
|
||||
},
|
||||
columns: '2',
|
||||
columnGutter: '.125in',
|
||||
legalAuthors: '',
|
||||
license: 'None',
|
||||
safetySpace: {
|
||||
top: '.25in',
|
||||
bottom: '.25in',
|
||||
outer: '.25in',
|
||||
inner: '.5in',
|
||||
columns : '2',
|
||||
columnGutter : '.125in',
|
||||
legalAuthors : '',
|
||||
license : 'None',
|
||||
safetySpace : {
|
||||
top : '.25in',
|
||||
bottom : '.25in',
|
||||
outer : '.25in',
|
||||
inner : '.5in',
|
||||
},
|
||||
trimSize: {
|
||||
width: '8.5in',
|
||||
height: '11in',
|
||||
trimSize : {
|
||||
width : '8.5in',
|
||||
height : '11in',
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -765,16 +765,16 @@ brew`);
|
||||
expect(google.newGoogleBrew).toHaveBeenCalled();
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(res.send).toHaveBeenCalledWith({
|
||||
_id : '1',
|
||||
authors : ['test user'],
|
||||
bleedSize: {
|
||||
top: '.125in',
|
||||
bottom: '.125in',
|
||||
inner: '.125in',
|
||||
outer: '.125in',
|
||||
_id : '1',
|
||||
authors : ['test user'],
|
||||
bleedSize : {
|
||||
top : '.125in',
|
||||
bottom : '.125in',
|
||||
inner : '.125in',
|
||||
outer : '.125in',
|
||||
},
|
||||
columns: '2',
|
||||
columnGutter: '.125in',
|
||||
columns : '2',
|
||||
columnGutter : '.125in',
|
||||
createdAt : undefined,
|
||||
description : '',
|
||||
editId : expect.any(String),
|
||||
@@ -784,29 +784,29 @@ brew`);
|
||||
renderer : 'V3',
|
||||
lang : 'en',
|
||||
shareId : expect.any(String),
|
||||
legalAuthors: '',
|
||||
license: 'None',
|
||||
safetySpace: {
|
||||
top: '.25in',
|
||||
bottom: '.25in',
|
||||
outer: '.25in',
|
||||
inner: '.5in',
|
||||
legalAuthors : '',
|
||||
license : 'None',
|
||||
safetySpace : {
|
||||
top : '.25in',
|
||||
bottom : '.25in',
|
||||
outer : '.25in',
|
||||
inner : '.5in',
|
||||
},
|
||||
googleId : expect.any(String),
|
||||
style : undefined,
|
||||
tags : [],
|
||||
text : undefined,
|
||||
textBin : undefined,
|
||||
theme : '5ePHB',
|
||||
thumbnail : '',
|
||||
title : 'asdf',
|
||||
trashed : false,
|
||||
trimSize: {
|
||||
width: '8.5in',
|
||||
height: '11in',
|
||||
googleId : expect.any(String),
|
||||
style : undefined,
|
||||
tags : [],
|
||||
text : undefined,
|
||||
textBin : undefined,
|
||||
theme : '5ePHB',
|
||||
thumbnail : '',
|
||||
title : 'asdf',
|
||||
trashed : false,
|
||||
trimSize : {
|
||||
width : '8.5in',
|
||||
height : '11in',
|
||||
},
|
||||
updatedAt : undefined,
|
||||
views : 0
|
||||
updatedAt : undefined,
|
||||
views : 0
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1031,7 +1031,7 @@ brew`);
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('deleteGoogleBrew', ()=>{
|
||||
it('should check auth and delete brew', async ()=>{
|
||||
const result = await api.deleteGoogleBrew({ username: 'test user' }, 'id', 'editId', res);
|
||||
@@ -1261,7 +1261,7 @@ brew`);
|
||||
expect(saved.googleId).toEqual(brew.googleId);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Split Text, Style, and Metadata', ()=>{
|
||||
|
||||
it('basic splitting', async ()=>{
|
||||
@@ -1372,5 +1372,5 @@ brew`);
|
||||
expect(testBrew.text).toEqual('text\n');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -244,7 +244,6 @@ export default function pageRoutes({
|
||||
};
|
||||
|
||||
sanitizeBrew(req.brew, 'edit');
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save.
|
||||
return next();
|
||||
}));
|
||||
@@ -252,7 +251,6 @@ export default function pageRoutes({
|
||||
//New Page from ID
|
||||
app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{
|
||||
sanitizeBrew(req.brew, 'share');
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
const brew = {
|
||||
shareId : req.brew.shareId,
|
||||
title : `CLONE - ${req.brew.title}`,
|
||||
@@ -303,14 +301,13 @@ export default function pageRoutes({
|
||||
const googleId = brew.googleId;
|
||||
const shareId = brew.shareId;
|
||||
await GoogleActions.increaseView(googleId, shareId, 'share', brew)
|
||||
.catch((err)=>{next(err);});
|
||||
.catch((err)=>{next(err);});
|
||||
} else {
|
||||
await HomebrewModel.increaseView({ shareId: brew.shareId });
|
||||
}
|
||||
};
|
||||
|
||||
brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share');
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
return next();
|
||||
}));
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
transition-timing-function : @easing;
|
||||
}
|
||||
|
||||
|
||||
.transition (@prop, @duration: @defaultDuration) {
|
||||
-webkit-transition : @prop @duration, -webkit-transform @duration;
|
||||
-moz-transition : @prop @duration, -moz-transform @duration;
|
||||
@@ -58,13 +57,13 @@
|
||||
transform : @transform;
|
||||
}
|
||||
|
||||
|
||||
.delay(@delay) {
|
||||
-webkit-transition-delay : @delay;
|
||||
transition-delay : @delay;
|
||||
-webkit-animation-delay : @delay;
|
||||
animation-delay : @delay;
|
||||
}
|
||||
|
||||
.keep() {
|
||||
-webkit-animation-fill-mode : forwards;
|
||||
-moz-animation-fill-mode : forwards;
|
||||
@@ -73,7 +72,6 @@
|
||||
animation-fill-mode : forwards;
|
||||
}
|
||||
|
||||
|
||||
.sequentialDelay(@delayInc : 0.2s, @initialDelay : 0s) {
|
||||
&:nth-child(1) {.delay(0*@delayInc + @initialDelay); }
|
||||
&:nth-child(2) {.delay(1*@delayInc + @initialDelay); }
|
||||
@@ -97,14 +95,12 @@
|
||||
&:nth-child(20) {.delay(19*@delayInc + @initialDelay); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
.createFrames(@name, @from, @to) {
|
||||
@frames: {
|
||||
from { @from(); }
|
||||
to { @to(); }
|
||||
};
|
||||
@-webkit-keyframes @name {@frames();}
|
||||
@-webkit-keyframes @name {@frames();}
|
||||
@-moz-keyframes @name {@frames();}
|
||||
@-ms-keyframes @name {@frames();}
|
||||
@-o-keyframes @name {@frames();}
|
||||
@@ -126,8 +122,6 @@
|
||||
animation-timing-function : @easing;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************
|
||||
Standard Animations
|
||||
****************************/
|
||||
@@ -136,7 +130,7 @@
|
||||
.createAnimation(fadeIn; @duration; @easing);
|
||||
.createFrames(fadeIn,
|
||||
{ opacity : 0; },
|
||||
{ opacity : 1; }
|
||||
{ opacity : 1; }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -144,7 +138,7 @@
|
||||
.createAnimation(fadeInDown; @duration; @easing);
|
||||
.createFrames(fadeInDown,
|
||||
{ opacity : 0; .transform(translateY(20px));},
|
||||
{ opacity : 1; .transform(translateY(0px));}
|
||||
{ opacity : 1; .transform(translateY(0px));}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,7 +146,7 @@
|
||||
.createAnimation(fadeInTop; @duration; @easing);
|
||||
.createFrames(fadeInTop,
|
||||
{ opacity : 0; .transform(translateY(-20px)); },
|
||||
{ opacity : 1; .transform(translateY(0px));}
|
||||
{ opacity : 1; .transform(translateY(0px));}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -160,7 +154,7 @@
|
||||
.createAnimation(fadeInLeft; @duration; @easing);
|
||||
.createFrames(fadeInLeft,
|
||||
{ opacity: 0; .transform(translateX(-20px));},
|
||||
{ opacity: 1; .transform(translateX(0));}
|
||||
{ opacity: 1; .transform(translateX(0));}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -168,7 +162,7 @@
|
||||
.createAnimation(fadeInRight; @duration; @easing);
|
||||
.createFrames(fadeInRight,
|
||||
{ opacity: 0; .transform(translateX(20px));},
|
||||
{ opacity: 1; .transform(translateX(0));}
|
||||
{ opacity: 1; .transform(translateX(0));}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,7 +170,7 @@
|
||||
.createAnimation(fadeOut; @duration; @easing);
|
||||
.createFrames(fadeOut,
|
||||
{ opacity : 1; },
|
||||
{ opacity : 0; }
|
||||
{ opacity : 0; }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -184,7 +178,7 @@
|
||||
.createAnimation(fadeOutDown; @duration; @easing);
|
||||
.createFrames(fadeOutDown,
|
||||
{ opacity : 1; .transform(translateY(0)); visibility: visible;},
|
||||
{ opacity : 0; .transform(translateY(20px)); visibility: hidden;}
|
||||
{ opacity : 0; .transform(translateY(20px)); visibility: hidden;}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,7 +186,7 @@
|
||||
.createAnimation(fadeOutTop; @duration; @easing);
|
||||
.createFrames(fadeOutTop,
|
||||
{ opacity : 1; .transform(translateY(0)); },
|
||||
{ opacity : 0; .transform(translateY(-20px)); }
|
||||
{ opacity : 0; .transform(translateY(-20px)); }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -200,7 +194,7 @@
|
||||
.createAnimation(fadeOutLeft; @duration; @easing);
|
||||
.createFrames(fadeOutLeft,
|
||||
{ opacity : 1; .transform(translateX(0));},
|
||||
{ opacity : 0; .transform(translateX(-20px));}
|
||||
{ opacity : 0; .transform(translateX(-20px));}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -208,13 +202,10 @@
|
||||
.createAnimation(fadeOutRight; @duration; @easing);
|
||||
.createFrames(fadeOutRight,
|
||||
{ opacity : 1; .transform(translateX(0));},
|
||||
{ opacity : 0; .transform(translateX(20px));}
|
||||
{ opacity : 0; .transform(translateX(20px));}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***************************
|
||||
Fun Animations
|
||||
****************************/
|
||||
|
||||
@@ -40,7 +40,7 @@ p {
|
||||
color : @copyGrey;
|
||||
}
|
||||
code {
|
||||
font-family : 'Courier', "mono";
|
||||
font-family : 'Courier', 'mono';
|
||||
color : black;
|
||||
white-space : pre;
|
||||
background-color : #F8F8F8;
|
||||
@@ -54,7 +54,7 @@ button {
|
||||
.animate(background-color);
|
||||
display : inline-block;
|
||||
padding : 0.6em 1.2em;
|
||||
font-family : 'Lato', "Helvetica", "Arial", sans-serif;
|
||||
font-family : 'Lato', 'Helvetica', 'Arial', sans-serif;
|
||||
font-size : 15px;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
background-color : unset;
|
||||
}
|
||||
|
||||
:where(i){ text-box-trim: trim-both }
|
||||
:where(i) { text-box-trim : trim-both; }
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
@arrowSize : 6px;
|
||||
@arrowPosition : 18px;
|
||||
[data-tooltip] {
|
||||
position:relative;
|
||||
position : relative;
|
||||
.tooltip(attr(data-tooltip));
|
||||
}
|
||||
[data-tooltip-top] {
|
||||
position:relative;
|
||||
position : relative;
|
||||
.tooltipTop(attr(data-tooltip-top));
|
||||
}
|
||||
[data-tooltip-bottom] {
|
||||
position:relative;
|
||||
position : relative;
|
||||
.tooltipBottom(attr(data-tooltip-bottom));
|
||||
}
|
||||
[data-tooltip-left] {
|
||||
position:relative;
|
||||
position : relative;
|
||||
.tooltipLeft(attr(data-tooltip-left));
|
||||
}
|
||||
[data-tooltip-right] {
|
||||
position:relative;
|
||||
position : relative;
|
||||
.tooltipRight(attr(data-tooltip-right));
|
||||
}
|
||||
.tooltip(@content) {
|
||||
@@ -33,9 +33,9 @@
|
||||
}
|
||||
&::after { margin-left : -18px; }
|
||||
&::before, &::after {
|
||||
bottom : 100%;
|
||||
left : 50%;
|
||||
translate: -50% 0;
|
||||
bottom : 100%;
|
||||
left : 50%;
|
||||
translate : -50% 0;
|
||||
}
|
||||
&:hover::after, &:hover::before, &:focus::after, &:focus::before {
|
||||
.transform(translateY(-(@arrowSize + 2)));
|
||||
@@ -49,9 +49,9 @@
|
||||
}
|
||||
&::after { margin-left : -18px; }
|
||||
&::before, &::after {
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
translate: -50% 0;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
translate : -50% 0;
|
||||
}
|
||||
&:hover::after, &:hover::before, &:focus::after, &:focus::before {
|
||||
.transform(translateY(@arrowSize + 2));
|
||||
@@ -65,8 +65,8 @@
|
||||
border-left-color : @tooltipColor;
|
||||
}
|
||||
&::after {
|
||||
max-width : 50ch;
|
||||
margin-bottom : -14px;
|
||||
max-width : 50ch;
|
||||
}
|
||||
&::before, &::after {
|
||||
right : 100%;
|
||||
@@ -84,13 +84,13 @@
|
||||
border-right-color : @tooltipColor;
|
||||
}
|
||||
&::after {
|
||||
max-width : 50ch;
|
||||
margin-bottom : -14px;
|
||||
max-width : 50ch;
|
||||
}
|
||||
&::before, &::after {
|
||||
top : 50%;
|
||||
left : 100%;
|
||||
translate:0 -50%;
|
||||
top : 50%;
|
||||
left : 100%;
|
||||
translate : 0 -50%;
|
||||
}
|
||||
&:hover::after, &:hover::before, &:focus::after, &:focus::before {
|
||||
.transform(translateX(@arrowSize + 2));
|
||||
@@ -115,17 +115,17 @@
|
||||
}
|
||||
//Box
|
||||
&::after {
|
||||
visibility : hidden;
|
||||
padding : 8px 10px;
|
||||
font-size : 12px;
|
||||
line-height : 12px;
|
||||
color : white;
|
||||
content : @content;
|
||||
background : @tooltipColor;
|
||||
max-width : 60ch;
|
||||
width :max-content;
|
||||
word-break : break-word;
|
||||
visibility : hidden;
|
||||
width : max-content;
|
||||
max-width : 60ch;
|
||||
padding : 8px 10px;
|
||||
font-size : 12px;
|
||||
line-height : 12px;
|
||||
color : white;
|
||||
word-break : break-word;
|
||||
overflow-wrap : break-word;
|
||||
content : @content;
|
||||
background : @tooltipColor;
|
||||
}
|
||||
&:hover::before, &:hover::after {
|
||||
visibility : visible;
|
||||
|
||||
+21
-21
@@ -47,19 +47,19 @@ describe(`brewSnippetsToJSON`, ()=>{
|
||||
gen : 'I am the first brew snippet!\n',
|
||||
name : 'First Brew Snippet'
|
||||
}, {
|
||||
gen : 'I am the second brew Snippet!',
|
||||
name: 'Second Brew Snippet'
|
||||
gen : 'I am the second brew Snippet!',
|
||||
name : 'Second Brew Snippet'
|
||||
}
|
||||
]}]
|
||||
] }]
|
||||
};
|
||||
const rendered = brewSnippetsToJSON(`Test Snippets JSON without theme snippets`, brewSnippetsBrewTest, null, true);
|
||||
expect(rendered).toStrictEqual(testMenuObject);
|
||||
});
|
||||
|
||||
it('converts raw brew snippets with theme snippets to JSON', function() {
|
||||
it('converts raw brew snippets with theme snippets to JSON', function() {
|
||||
const testMenuObject = {
|
||||
groupName : 'Brew Snippets',
|
||||
icon : 'fas fa-th-list',
|
||||
icon : 'fas fa-th-list',
|
||||
view : 'text',
|
||||
snippets : [{
|
||||
gen : '',
|
||||
@@ -76,20 +76,20 @@ describe(`brewSnippetsToJSON`, ()=>{
|
||||
icon : '',
|
||||
name : 'Second Theme Snippet',
|
||||
},
|
||||
]},
|
||||
{
|
||||
name : 'Test Snippets JSON with theme snippets',
|
||||
subsnippets : [
|
||||
{
|
||||
gen : 'I am the first brew snippet!\n',
|
||||
name : 'First Brew Snippet'
|
||||
},
|
||||
{
|
||||
gen : 'I am the second brew Snippet!',
|
||||
name: 'Second Brew Snippet'
|
||||
}
|
||||
]
|
||||
}]};
|
||||
] },
|
||||
{
|
||||
name : 'Test Snippets JSON with theme snippets',
|
||||
subsnippets : [
|
||||
{
|
||||
gen : 'I am the first brew snippet!\n',
|
||||
name : 'First Brew Snippet'
|
||||
},
|
||||
{
|
||||
gen : 'I am the second brew Snippet!',
|
||||
name : 'Second Brew Snippet'
|
||||
}
|
||||
]
|
||||
}] };
|
||||
const rendered = brewSnippetsToJSON(`Test Snippets JSON with theme snippets`, brewSnippetsBrewTest, brewSnippetsThemeTest, true);
|
||||
expect(rendered).toStrictEqual(testMenuObject);
|
||||
});
|
||||
@@ -103,8 +103,8 @@ describe(`YAMLSnippetsToText`, ()=>{
|
||||
gen : 'I am the first brew snippet!\n',
|
||||
name : 'First Brew Snippet'
|
||||
}, {
|
||||
gen : 'I am the second brew Snippet!',
|
||||
name: 'Second Brew Snippet'
|
||||
gen : 'I am the second brew Snippet!',
|
||||
name : 'Second Brew Snippet'
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
import {hbfm} from 'marked-hbfm';
|
||||
import { hbfm } from 'marked-hbfm';
|
||||
|
||||
describe('Non-Breaking Spaces Interactions', ()=>{
|
||||
test('I am actually a single-line definition list!', function() {
|
||||
|
||||
@@ -25,16 +25,16 @@ body { counter-reset : phb-page-numbers; }
|
||||
}
|
||||
}
|
||||
.useColumns(@multiplier : 1) {
|
||||
column-count : 2;
|
||||
column-fill : auto;
|
||||
column-gap : 1cm;
|
||||
-webkit-column-width : 8cm * @multiplier;
|
||||
-moz-column-width : 8cm * @multiplier;
|
||||
column-width : 8cm * @multiplier;
|
||||
-webkit-column-count : 2;
|
||||
-moz-column-count : 2;
|
||||
-webkit-column-width : 8cm * @multiplier;
|
||||
-moz-column-width : 8cm * @multiplier;
|
||||
column-count : 2;
|
||||
column-fill : auto;
|
||||
-webkit-column-gap : 1cm;
|
||||
-moz-column-gap : 1cm;
|
||||
column-gap : 1cm;
|
||||
}
|
||||
.phb, .page {
|
||||
.useColumns();
|
||||
@@ -43,8 +43,11 @@ body { counter-reset : phb-page-numbers; }
|
||||
box-sizing : border-box;
|
||||
width : 215.9mm;
|
||||
height : 279.4mm;
|
||||
contain-intrinsic-size : auto none;
|
||||
padding : 1.0cm 1.7cm;
|
||||
padding-bottom : 1.5cm;
|
||||
contain : strict;
|
||||
content-visibility : auto;
|
||||
overflow : hidden;
|
||||
font-family : 'BookSanity';
|
||||
font-size : 0.317cm;
|
||||
@@ -52,11 +55,8 @@ body { counter-reset : phb-page-numbers; }
|
||||
background-color : @background;
|
||||
background-image : @backgroundImage;
|
||||
text-rendering : optimizeLegibility;
|
||||
page-break-before : always;
|
||||
page-break-after : always;
|
||||
contain : strict;
|
||||
content-visibility : auto;
|
||||
contain-intrinsic-size : auto none;
|
||||
break-before : page;
|
||||
break-after : page;
|
||||
}
|
||||
|
||||
.phb {
|
||||
@@ -111,10 +111,10 @@ body { counter-reset : phb-page-numbers; }
|
||||
color : @headerText;
|
||||
}
|
||||
h1 {
|
||||
column-span : all;
|
||||
font-size : 0.987cm;
|
||||
-webkit-column-span : all;
|
||||
-moz-column-span : all;
|
||||
column-span : all;
|
||||
font-size : 0.987cm;
|
||||
& + p::first-letter {
|
||||
float : left;
|
||||
font-family : 'Solberry';
|
||||
@@ -308,7 +308,7 @@ body { counter-reset : phb-page-numbers; }
|
||||
p,blockquote,table {
|
||||
z-index : 15;
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
}
|
||||
//Better spacing for spell blocks
|
||||
@@ -322,7 +322,7 @@ body { counter-reset : phb-page-numbers; }
|
||||
}
|
||||
li {
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ body { counter-reset : phb-page-numbers; }
|
||||
list-style-type : none;
|
||||
break-inside : auto;
|
||||
-webkit-column-break-inside : auto;
|
||||
page-break-inside : auto;
|
||||
break-inside : auto;
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
@@ -436,7 +436,7 @@ body { counter-reset : phb-page-numbers; }
|
||||
// *****************************/
|
||||
.phb .toc {
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
a {
|
||||
color : black;
|
||||
|
||||
+26
-26
@@ -27,16 +27,16 @@
|
||||
h5 + * { margin-top : 0.1cm; }
|
||||
}
|
||||
.useColumns(@multiplier : 1, @fillMode: auto) {
|
||||
column-count : 2;
|
||||
column-fill : @fillMode;
|
||||
column-gap : 0.9cm;
|
||||
-webkit-column-width : 8cm * @multiplier;
|
||||
-moz-column-width : 8cm * @multiplier;
|
||||
column-width : 8cm * @multiplier;
|
||||
-webkit-column-count : 2;
|
||||
-moz-column-count : 2;
|
||||
-webkit-column-width : 8cm * @multiplier;
|
||||
-moz-column-width : 8cm * @multiplier;
|
||||
column-count : 2;
|
||||
column-fill : @fillMode;
|
||||
-webkit-column-gap : 0.9cm;
|
||||
-moz-column-gap : 0.9cm;
|
||||
column-gap : 0.9cm;
|
||||
}
|
||||
.page {
|
||||
.useColumns();
|
||||
@@ -79,12 +79,12 @@
|
||||
color : var(--HB_Color_HeaderText);
|
||||
}
|
||||
h1 {
|
||||
margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE
|
||||
column-span : all;
|
||||
font-size : 0.89cm;
|
||||
line-height : 1em;
|
||||
-webkit-column-span : all;
|
||||
-moz-column-span : all;
|
||||
column-span : all;
|
||||
margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE
|
||||
font-size : 0.89cm;
|
||||
line-height : 1em;
|
||||
& + p::first-line { font-variant : small-caps; }
|
||||
}
|
||||
p.first-letter::first-letter,
|
||||
@@ -363,13 +363,13 @@
|
||||
|
||||
// Monster Ability table
|
||||
hr + table:first-of-type {
|
||||
margin : 0;
|
||||
-webkit-column-span : none;
|
||||
column-span : none;
|
||||
margin : 0;
|
||||
color : var(--HB_Color_HeaderText);
|
||||
background-color : transparent;
|
||||
border-style : none;
|
||||
border-image : none;
|
||||
-webkit-column-span : none;
|
||||
tr { background-color : transparent; }
|
||||
td,th { padding : 0px; }
|
||||
}
|
||||
@@ -420,15 +420,15 @@
|
||||
color : var(--HB_Color_Footnotes);
|
||||
}
|
||||
.footnote {
|
||||
text-transform: uppercase;
|
||||
position : absolute;
|
||||
right : 80px;
|
||||
bottom : 32px;
|
||||
z-index : 150;
|
||||
width : 200px;
|
||||
font-size : 0.8em;
|
||||
color : var(--HB_Color_Footnotes);
|
||||
text-align : right;
|
||||
position : absolute;
|
||||
right : 80px;
|
||||
bottom : 32px;
|
||||
z-index : 150;
|
||||
width : 200px;
|
||||
font-size : 0.8em;
|
||||
color : var(--HB_Color_Footnotes);
|
||||
text-align : right;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
// ************************************
|
||||
// * CODE BLOCKS
|
||||
@@ -488,7 +488,7 @@
|
||||
text-indent : -1em;
|
||||
list-style-type : none;
|
||||
-webkit-column-break-inside : auto;
|
||||
page-break-inside : auto;
|
||||
break-inside : auto;
|
||||
break-inside : auto;
|
||||
}
|
||||
&.wide { column-count : 4; }
|
||||
@@ -615,9 +615,9 @@
|
||||
font-size : 0.496cm;
|
||||
color : white;
|
||||
text-align : center;
|
||||
-webkit-text-stroke : 0.1cm black;
|
||||
text-transform : none;
|
||||
paint-order : stroke;
|
||||
text-transform : none;
|
||||
-webkit-text-stroke : 0.1cm black;
|
||||
}
|
||||
.logo {
|
||||
position : absolute;
|
||||
@@ -675,10 +675,10 @@
|
||||
// * BACK COVER
|
||||
// *****************************/
|
||||
.page:has(.backCover) {
|
||||
columns : 1;
|
||||
padding : 2.25cm 1.3cm 2cm 1.3cm;
|
||||
line-height : 1.4em;
|
||||
color : #FFFFFF;
|
||||
columns : 1;
|
||||
&::after { display : none; }
|
||||
.columnWrapper { width : 7.6cm; }
|
||||
.backCover {
|
||||
@@ -756,9 +756,9 @@
|
||||
// * PART COVER
|
||||
// *****************************/
|
||||
.page:has(.partCover) {
|
||||
columns : 1;
|
||||
padding-top : 0;
|
||||
text-align : center;
|
||||
columns : 1;
|
||||
|
||||
.partCover {
|
||||
position : absolute;
|
||||
@@ -807,7 +807,7 @@
|
||||
&:has(.toc)::after { display : none; }
|
||||
.toc {
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
h1 {
|
||||
margin-bottom : 0.3cm;
|
||||
|
||||
@@ -329,7 +329,7 @@ export default [
|
||||
},
|
||||
{
|
||||
name : 'DTRPG Community Content',
|
||||
icon : null,
|
||||
icon : null,
|
||||
subsnippets : [
|
||||
{
|
||||
name : 'Chronicle System Guild Colophon',
|
||||
|
||||
+45
-57
@@ -34,15 +34,15 @@ body { counter-reset : page-numbers 0; }
|
||||
}
|
||||
|
||||
.useColumns(@multiplier : 1, @fillMode: auto) {
|
||||
column-fill : @fillMode;
|
||||
column-count : 2;
|
||||
column-fill : @fillMode;
|
||||
}
|
||||
.columnWrapper {
|
||||
columns : inherit;
|
||||
column-span : all;
|
||||
column-fill : inherit;
|
||||
column-gap : inherit;
|
||||
max-height : 100%;
|
||||
column-span : all;
|
||||
columns : inherit;
|
||||
column-fill : inherit;
|
||||
}
|
||||
.page {
|
||||
.useColumns();
|
||||
@@ -51,13 +51,13 @@ body { counter-reset : page-numbers 0; }
|
||||
box-sizing : border-box;
|
||||
width : 215.9mm;
|
||||
height : 279.4mm;
|
||||
contain-intrinsic-size : auto none;
|
||||
padding : 1.4cm 1.9cm 1.7cm;
|
||||
contain : strict;
|
||||
content-visibility : auto;
|
||||
overflow : clip;
|
||||
background-color : var(--HB_Color_Background);
|
||||
text-rendering : optimizeLegibility;
|
||||
contain : strict;
|
||||
content-visibility : auto;
|
||||
contain-intrinsic-size : auto none;
|
||||
}
|
||||
//*****************************
|
||||
// * BASE
|
||||
@@ -145,7 +145,7 @@ body { counter-reset : page-numbers 0; }
|
||||
blockquote,table {
|
||||
z-index : 15;
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
}
|
||||
// Nested lists
|
||||
@@ -155,20 +155,20 @@ body { counter-reset : page-numbers 0; }
|
||||
}
|
||||
li {
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
}
|
||||
|
||||
/* Wrap Text */
|
||||
.wrapLeft {
|
||||
shape-outside : var(--HB_src);
|
||||
float : right;
|
||||
shape-outside : var(--HB_src);
|
||||
shape-margin : 0.2cm;
|
||||
}
|
||||
|
||||
.wrapRight {
|
||||
shape-outside : var(--HB_src);
|
||||
float : left;
|
||||
shape-outside : var(--HB_src);
|
||||
shape-margin : 0.2cm;
|
||||
}
|
||||
|
||||
@@ -433,8 +433,8 @@ body { counter-reset : page-numbers 0; }
|
||||
// *****************************/
|
||||
.page {
|
||||
.wide {
|
||||
column-span : all;
|
||||
display : block;
|
||||
column-span : all;
|
||||
margin-bottom : 1em;
|
||||
& + * { margin-top : 0; }
|
||||
}
|
||||
@@ -502,66 +502,54 @@ body { counter-reset : page-numbers 0; }
|
||||
// * Ordered List Type Overrides
|
||||
// *****************************/
|
||||
@counter-style lower-alpha-trailing-paren {
|
||||
system: extends lower-alpha;
|
||||
prefix: "";
|
||||
suffix: ") ";
|
||||
}
|
||||
system : extends lower-alpha;
|
||||
prefix : '';
|
||||
suffix : ') ';
|
||||
}
|
||||
|
||||
.page{
|
||||
.ol-ccby ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
.page {
|
||||
.ol-ccby ol { list-style-type : lower-alpha; }
|
||||
|
||||
.ol-ccby ol ol{
|
||||
padding-left: 0px !important;
|
||||
list-style-type: greek;
|
||||
.ol-ccby ol ol {
|
||||
padding-left : 0px !important;
|
||||
list-style-type : greek;
|
||||
}
|
||||
|
||||
.ol-ccby ol ol ol {
|
||||
padding-left: 0px !important;
|
||||
list-style-type: upper-alpha;
|
||||
padding-left : 0px !important;
|
||||
list-style-type : upper-alpha;
|
||||
}
|
||||
|
||||
.ol-ccby ol ol ol ol {
|
||||
padding-left: 0px !important;
|
||||
list-style-type: lower-roman;
|
||||
padding-left : 0px !important;
|
||||
list-style-type : lower-roman;
|
||||
}
|
||||
|
||||
.ol-ccby p {
|
||||
padding-left: 25px;
|
||||
text-indent: 0em !important;
|
||||
padding-left : 25px;
|
||||
text-indent : 0em !important;
|
||||
}
|
||||
|
||||
// Non-indented, numerical lists with slight gap between paragraphs.
|
||||
.ol-lowerLetters ol { list-style-type : lower-alpha-trailing-paren; }
|
||||
|
||||
.ol-lowerLetters ol li p { display : inline; }
|
||||
|
||||
.ol-lowerLetters ol {
|
||||
list-style-type: lower-alpha-trailing-paren;
|
||||
padding : 0px;
|
||||
list-style-position : inside;
|
||||
}
|
||||
|
||||
.ol-lowerLetters ol li p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.ol-lowerLetters ol {
|
||||
list-style-position: inside;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.ol-lowerLetters ol li:not(:last-child) {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.ol-lowerLetters ol li:not(:last-child) { margin-bottom : 0.5em; }
|
||||
|
||||
.ol-noIndent ol {
|
||||
list-style-position: inside;
|
||||
padding: 0px;
|
||||
padding : 0px;
|
||||
list-style-position : inside;
|
||||
}
|
||||
|
||||
.ol-noIndent ol li p {
|
||||
display: inline;
|
||||
}
|
||||
.ol-noIndent ol li p { display : inline; }
|
||||
|
||||
.ol-noIndent ol li:not(:last-child) {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.ol-noIndent ol li:not(:last-child) { margin-bottom : 0.5em; }
|
||||
}
|
||||
|
||||
// *****************************
|
||||
@@ -617,7 +605,7 @@ h6,
|
||||
&:has(.toc)::after { display : none; }
|
||||
.toc {
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
h1 {
|
||||
margin-bottom : 0.3cm;
|
||||
@@ -684,10 +672,10 @@ Firefox endruns
|
||||
**********************************/
|
||||
|
||||
@supports (-moz-user-select: none) { // This section will only apply to Firefox; it's the only browser that supports `-mos-xyz...`
|
||||
.page {
|
||||
blockquote, table {
|
||||
page-break-inside: auto;
|
||||
break-inside: auto;
|
||||
}
|
||||
}
|
||||
.page {
|
||||
blockquote, table {
|
||||
break-inside : auto;
|
||||
break-inside : auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,12 +275,12 @@
|
||||
|
||||
// Monster Ability table
|
||||
hr + table:first-of-type {
|
||||
column-span : none;
|
||||
margin : 0;
|
||||
color : inherit;
|
||||
background-image : none;
|
||||
border-style : none;
|
||||
border-image : none;
|
||||
column-span : none;
|
||||
tr { background-image : none; }
|
||||
td,th { padding : 0px; }
|
||||
}
|
||||
@@ -372,8 +372,8 @@
|
||||
|
||||
.page .spellList {
|
||||
.useSansSerif();
|
||||
font-family : 'PermanentMarker';
|
||||
column-count : 2;
|
||||
font-family : 'PermanentMarker';
|
||||
ul + h5 { margin-top : 15px; }
|
||||
ul {
|
||||
padding-left : 1em;
|
||||
@@ -382,7 +382,7 @@
|
||||
list-style-type : none;
|
||||
break-inside : auto;
|
||||
-webkit-column-break-inside : auto;
|
||||
page-break-inside : auto;
|
||||
break-inside : auto;
|
||||
}
|
||||
&.wide { column-count : 4; }
|
||||
}
|
||||
@@ -399,7 +399,7 @@
|
||||
// *****************************/
|
||||
.page .toc {
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
h1 {
|
||||
margin-bottom : 0.3cm;
|
||||
|
||||
@@ -8,31 +8,23 @@
|
||||
}
|
||||
|
||||
.page {
|
||||
font-family: Cambria,Georgia,serif;
|
||||
font-size: 14px;
|
||||
font-family : "Cambria","Georgia",serif;
|
||||
font-size : 14px;
|
||||
h1, h2, h3, h4 {
|
||||
font-variant: small-caps;
|
||||
font-weight: normal;
|
||||
font-weight : normal;
|
||||
font-variant : small-caps;
|
||||
}
|
||||
h1 {
|
||||
column-span: all;
|
||||
-webkit-column-span: all;
|
||||
font-size: 40px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 26px;
|
||||
-webkit-column-span : all;
|
||||
column-span : all;
|
||||
font-size : 40px;
|
||||
}
|
||||
h2 { font-size : 26px; }
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
}
|
||||
h6 {
|
||||
font-size: 14px;
|
||||
font-size : 20px;
|
||||
border-bottom : 2px solid black;
|
||||
}
|
||||
h4 { font-size : 18px; }
|
||||
h5 { font-size : 16px; }
|
||||
h6 { font-size : 14px; }
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
/* Main Font, serif */
|
||||
@font-face {
|
||||
font-family : "BookSanity";
|
||||
font-family : 'BookSanity';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e legacy/Bookinsanity.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "BookSanity";
|
||||
font-family : 'BookSanity';
|
||||
font-style : normal;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e legacy/Bookinsanity Bold.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "BookSanity";
|
||||
font-family : 'BookSanity';
|
||||
font-style : italic;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e legacy/Bookinsanity Italic.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "BookSanity";
|
||||
font-family : 'BookSanity';
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e legacy/Bookinsanity Bold Italic.woff2');
|
||||
@@ -26,19 +26,19 @@
|
||||
|
||||
/* Notes and Tables, sans-serif */
|
||||
@font-face {
|
||||
font-family : "ScalySans";
|
||||
font-family : 'ScalySans';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e legacy/Scaly Sans.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "ScalySansSmallCaps";
|
||||
font-family : 'ScalySansSmallCaps';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e legacy/Scaly Sans Caps.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "WalterTurncoat";
|
||||
font-family : 'WalterTurncoat';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e legacy/WalterTurncoat-Regular.woff2');
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
/* Headers */
|
||||
@font-face {
|
||||
font-family : "MrJeeves";
|
||||
font-family : 'MrJeeves';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e legacy/Mr Eaves Small Caps.woff2');
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
/* Fancy Drop Cap */
|
||||
@font-face {
|
||||
font-family : "Solberry";
|
||||
font-family : 'Solberry';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e legacy/Solbera Imitation.woff2');
|
||||
|
||||
+21
-21
@@ -1,24 +1,24 @@
|
||||
/* Main Font, serif */
|
||||
@font-face {
|
||||
font-family : "BookInsanityRemake";
|
||||
font-family : 'BookInsanityRemake';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Bookinsanity.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "BookInsanityRemake";
|
||||
font-family : 'BookInsanityRemake';
|
||||
font-style : normal;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Bookinsanity Bold.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "BookInsanityRemake";
|
||||
font-family : 'BookInsanityRemake';
|
||||
font-style : italic;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Bookinsanity Italic.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "BookInsanityRemake";
|
||||
font-family : 'BookInsanityRemake';
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Bookinsanity Bold Italic.woff2');
|
||||
@@ -26,37 +26,37 @@
|
||||
|
||||
/* Notes and Tables, sans-serif */
|
||||
@font-face {
|
||||
font-family : "ScalySansRemake";
|
||||
font-family : 'ScalySansRemake';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Scaly Sans.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "ScalySansRemake";
|
||||
font-family : 'ScalySansRemake';
|
||||
font-style : normal;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Scaly Sans Bold.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "ScalySansRemake";
|
||||
font-family : 'ScalySansRemake';
|
||||
font-style : italic;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Scaly Sans Italic.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "ScalySansRemake";
|
||||
font-family : 'ScalySansRemake';
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Scaly Sans Bold Italic.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "ScalySansSmallCapsRemake";
|
||||
font-family : 'ScalySansSmallCapsRemake';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Scaly Sans Caps.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "WalterTurncoat";
|
||||
font-family : 'WalterTurncoat';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
/* Headers */
|
||||
@font-face {
|
||||
font-family : "MrEavesRemake";
|
||||
font-family : 'MrEavesRemake';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Mr Eaves Small Caps.woff2');
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
/* Fancy Drop Cap */
|
||||
@font-face {
|
||||
font-family : "SolberaImitationRemake"; //Tweaked 5e version
|
||||
font-family : 'SolberaImitationRemake'; //Tweaked 5e version
|
||||
font-style : normal;
|
||||
font-style : italic;
|
||||
font-weight : 100 1000;
|
||||
@@ -81,62 +81,62 @@
|
||||
|
||||
/* Cover Page */
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : normal;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : italic;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "NodestoCapsWide";
|
||||
font-family : 'NodestoCapsWide';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Wide.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "Overpass";
|
||||
font-family : 'Overpass';
|
||||
font-style : normal;
|
||||
font-weight : 500;
|
||||
src : url('../../../fonts/5e/Overpass Medium.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "Davek";
|
||||
font-family : 'Davek';
|
||||
font-style : normal;
|
||||
font-weight : 500;
|
||||
src : url('../../../fonts/5e/Davek.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "Iokharic";
|
||||
font-family : 'Iokharic';
|
||||
font-style : normal;
|
||||
font-weight : 500;
|
||||
src : url('../../../fonts/5e/Iokharic.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "Rellanic";
|
||||
font-family : 'Rellanic';
|
||||
font-style : normal;
|
||||
font-weight : 500;
|
||||
src : url('../../../fonts/5e/Rellanic.woff2');
|
||||
|
||||
@@ -18,28 +18,28 @@ License:
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family : "Pagella";
|
||||
font-family : 'Pagella';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/Blank/texgyrepagella-regular.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "Pagella";
|
||||
font-family : 'Pagella';
|
||||
font-style : normal;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/Blank/texgyrepagella-bold.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "Pagella";
|
||||
font-family : 'Pagella';
|
||||
font-style : italic;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/Blank/texgyrepagella-italic.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "Pagella";
|
||||
font-family : 'Pagella';
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Main Font, serif */
|
||||
@font-face {
|
||||
font-family : "ReenieBeanie";
|
||||
font-family : 'ReenieBeanie';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/Journal/ReenieBeanie-Regular.woff2');
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
/* Notes and Tables, sans-serif */
|
||||
@font-face {
|
||||
font-family : "PermanentMarker";
|
||||
font-family : 'PermanentMarker';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/Journal/PermanentMarker-Regular.woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family : "WalterTurncoat";
|
||||
font-family : 'WalterTurncoat';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2');
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
/* Headers */
|
||||
@font-face {
|
||||
font-family : "FrederickaTheGreat";
|
||||
font-family : 'FrederickaTheGreat';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/Journal/FrederickaTheGreat-Regular.woff2');
|
||||
@@ -30,28 +30,28 @@
|
||||
|
||||
/* Cover Page */
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : normal;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : italic;
|
||||
font-weight : normal;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family : "NodestoCapsCondensed";
|
||||
font-family : 'NodestoCapsCondensed';
|
||||
font-style : italic;
|
||||
font-weight : bold;
|
||||
src : url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
|
||||
|
||||
@@ -25,19 +25,19 @@
|
||||
&.F-minus::before { content : '\f191'; }
|
||||
&.F-plus::before { content : '\f192'; }
|
||||
&.F-zero::before { content : '\f193'; }
|
||||
&.d10::before { content : '\f194'; }
|
||||
&.d10-0::before { content : '\f100'; }
|
||||
&.d10-1:before { content : '\f101'; }
|
||||
&.d10::before { content : '\f194'; }
|
||||
&.d10-0::before { content : '\f100'; }
|
||||
&.d10-1::before { content : '\f101'; }
|
||||
&.d10-10::before { content : '\f102'; }
|
||||
&.d10-2:before { content : '\f103'; }
|
||||
&.d10-3::before { content : '\f104'; }
|
||||
&.d10-4::before { content : '\f105'; }
|
||||
&.d10-5::before { content : '\f106'; }
|
||||
&.d10-6::before { content : '\f107'; }
|
||||
&.d10-7::before { content : '\f108'; }
|
||||
&.d10-8::before { content : '\f109'; }
|
||||
&.d10-9::before { content : '\f10A'; }
|
||||
&.d12::before { content : '\f195'; }
|
||||
&.d10-2::before { content : '\f103'; }
|
||||
&.d10-3::before { content : '\f104'; }
|
||||
&.d10-4::before { content : '\f105'; }
|
||||
&.d10-5::before { content : '\f106'; }
|
||||
&.d10-6::before { content : '\f107'; }
|
||||
&.d10-7::before { content : '\f108'; }
|
||||
&.d10-8::before { content : '\f109'; }
|
||||
&.d10-9::before { content : '\f10A'; }
|
||||
&.d12::before { content : '\f195'; }
|
||||
&.d12-1::before { content : '\f10b'; }
|
||||
&.d12-10::before { content : '\f10c'; }
|
||||
&.d12-11::before { content : '\f10d'; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import (less) "./themes/fonts/iconFonts/fontawesome-free.less";
|
||||
@import (less) "./themes/fonts/iconFonts/fontawesome-solid.less";
|
||||
@import (less) "./themes/fonts/iconFonts/fontawesome-brands.less";
|
||||
@import (less) "./themes/fonts/iconFonts/fontawesome-regular.less";
|
||||
@import (less) './themes/fonts/iconFonts/fontawesome-free.less';
|
||||
@import (less) './themes/fonts/iconFonts/fontawesome-solid.less';
|
||||
@import (less) './themes/fonts/iconFonts/fontawesome-brands.less';
|
||||
@import (less) './themes/fonts/iconFonts/fontawesome-regular.less';
|
||||
|
||||
|
||||
/* Icon Font: Font Awesome */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,16 +4,15 @@
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
||||
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; }
|
||||
--fa-style-family-classic : 'Font Awesome 6 Free';
|
||||
--fa-font-regular : normal 400 1em/1 'Font Awesome 6 Free'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("/fonts/iconFonts/fa-regular-400.woff2") format("woff2"), url("/fonts/iconFonts/fa-regular-400.ttf") format("truetype"); }
|
||||
font-family : 'Font Awesome 6 Free';
|
||||
font-style : normal;
|
||||
font-weight : normal;
|
||||
src : url('/fonts/iconFonts/fa-regular-400.woff2') format('woff2'), url('/fonts/iconFonts/fa-regular-400.ttf') format('truetype');
|
||||
font-display : block; }
|
||||
|
||||
.far,
|
||||
.fa-regular {
|
||||
font-weight: 400; }
|
||||
.fa-regular { font-weight : normal; }
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
|
||||
--fa-style-family-classic : 'Font Awesome 6 Free';
|
||||
--fa-font-solid : normal 900 1em/1 'Font Awesome 6 Free'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: block;
|
||||
src: url("/fonts/iconFonts/fa-solid-900.woff2") format("woff2"), url("/fonts/iconFonts/fa-solid-900.ttf") format("truetype"); }
|
||||
font-family : 'Font Awesome 6 Free';
|
||||
font-style : normal;
|
||||
font-weight : 900;
|
||||
src : url('/fonts/iconFonts/fa-solid-900.woff2') format('woff2'), url('/fonts/iconFonts/fa-solid-900.ttf') format('truetype');
|
||||
font-display : block; }
|
||||
|
||||
.fas,
|
||||
.fa-solid {
|
||||
font-weight: 900; }
|
||||
.fa-solid { font-weight : 900; }
|
||||
|
||||
+6
-6
@@ -8,12 +8,12 @@ export default defineConfig({
|
||||
plugins : [react(), generateAssetsPlugin()],
|
||||
resolve : {
|
||||
alias : {
|
||||
'@vitreum' : path.resolve(__dirname, './vitreum'),
|
||||
'@shared' : path.resolve(__dirname, './shared'),
|
||||
'@sharedStyles' : path.resolve(__dirname, './shared/naturalcrit/styles'),
|
||||
'@navbar' : path.resolve(__dirname, './client/homebrew/navbar'),
|
||||
'@themes' : path.resolve(__dirname, './themes'),
|
||||
'@components' : path.resolve(__dirname, './client/components')
|
||||
'@vitreum' : path.resolve(import.meta.dirname, './vitreum'),
|
||||
'@shared' : path.resolve(import.meta.dirname, './shared'),
|
||||
'@sharedStyles' : path.resolve(import.meta.dirname, './shared/naturalcrit/styles'),
|
||||
'@navbar' : path.resolve(import.meta.dirname, './client/homebrew/navbar'),
|
||||
'@themes' : path.resolve(import.meta.dirname, './themes'),
|
||||
'@components' : path.resolve(import.meta.dirname, './client/components')
|
||||
},
|
||||
},
|
||||
build : {
|
||||
|
||||
@@ -12,6 +12,14 @@ export function generateAssetsPlugin(isDev = false) {
|
||||
// Copy favicon
|
||||
await fs.copy('./client/homebrew/favicon.ico', `${buildDir}/assets/favicon.ico`);
|
||||
|
||||
//Copy this image into multiple paths to avoid server spam where browser requests an image
|
||||
//at the wrong path, gets a 406, then in response requests broken-image which also fails. Error handler
|
||||
//used the wrong path for this image causing an infinite request loop. Should be able to remove
|
||||
//in some time once all users haver refreshed their browser
|
||||
await fs.copy('./client/icons/broken-image.jpg', `${buildDir}/edit/client/icons/broken-image.jpg`);
|
||||
await fs.copy('./client/icons/broken-image.jpg', `${buildDir}/new/client/icons/broken-image.jpg`);
|
||||
await fs.copy('./client/icons/broken-image.jpg', `${buildDir}/client/icons/broken-image.jpg`);
|
||||
|
||||
// Copy shared styles/fonts
|
||||
const assets = fs.readdirSync('./shared/naturalcrit/styles');
|
||||
for (const file of assets) {
|
||||
|
||||
+12
-75
@@ -1,91 +1,28 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
//old vitreum file, still imported in some pages
|
||||
|
||||
const injectTag = (tag, props, children)=>{
|
||||
const injectNode = document.createElement(tag);
|
||||
Object.entries(props).forEach(([key, val])=>injectNode[key] = val);
|
||||
if(children) injectNode.appendChild(document.createTextNode(children));
|
||||
document.getElementsByTagName('head')[0].appendChild(injectNode);
|
||||
};
|
||||
|
||||
const obj2props = (obj)=>Object.entries(obj)
|
||||
.map(([k, v])=>`${k}="${v}"`)
|
||||
const obj2props = (obj) =>
|
||||
Object.entries(obj)
|
||||
.map(([k, v]) => `${k}="${v}"`)
|
||||
.join(' ');
|
||||
const toStr = (chld)=>(Array.isArray(chld) ? chld.join('') : chld);
|
||||
const onServer = typeof window === 'undefined';
|
||||
|
||||
let NamedTags = {};
|
||||
let UnnamedTags = [];
|
||||
|
||||
export const HeadComponents = {
|
||||
Title({ children }) {
|
||||
if(onServer) NamedTags.title = `<title>${toStr(children)}</title>`;
|
||||
useEffect(()=>{
|
||||
document.title = toStr(children);
|
||||
}, [children]);
|
||||
return null;
|
||||
},
|
||||
Favicon({ type = 'image/png', href = '', rel = 'icon', id = 'favicon' }) {
|
||||
if(onServer) NamedTags.favicon = `<link rel='shortcut icon' type="${type}" id="${id}" href="${href}" />`;
|
||||
useEffect(()=>{
|
||||
document.getElementById(id).href = href;
|
||||
}, [id, href]);
|
||||
return null;
|
||||
},
|
||||
Description({ children }) {
|
||||
if(onServer) NamedTags.description = `<meta name='description' content='${toStr(children)}' />`;
|
||||
return null;
|
||||
},
|
||||
Noscript({ children }) {
|
||||
if(onServer) UnnamedTags.push(`<noscript>${toStr(children)}</noscript>`);
|
||||
return null;
|
||||
},
|
||||
Script({ children = [], ...props }) {
|
||||
if(onServer) {
|
||||
UnnamedTags.push(
|
||||
children.length
|
||||
? `<script ${obj2props(props)}>${toStr(children)}</script>`
|
||||
: `<script ${obj2props(props)} />`,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
const HeadComponents = {
|
||||
Meta(props) {
|
||||
const tag = `<meta ${obj2props(props)} />`;
|
||||
props.property || props.name ? (NamedTags[props.property || props.name] = tag) : UnnamedTags.push(tag);
|
||||
useEffect(()=>{
|
||||
|
||||
props.property || props.name
|
||||
? (NamedTags[props.property || props.name] = tag)
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
document
|
||||
.getElementsByTagName('head')[0]
|
||||
.insertAdjacentHTML('beforeend', Object.values(NamedTags).join('\n'));
|
||||
}, [NamedTags]);
|
||||
return null;
|
||||
},
|
||||
Style({ children, type = 'text/css' }) {
|
||||
if(onServer) UnnamedTags.push(`<style type="${type}">${toStr(children)}</style>`);
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
export const Inject = ({ tag, children, ...props })=>{
|
||||
useEffect(()=>{
|
||||
injectTag(tag, props, children);
|
||||
}, []);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const generate = ()=>Object.values(NamedTags).concat(UnnamedTags).join('\n');
|
||||
|
||||
export const flush = ()=>{
|
||||
NamedTags = {};
|
||||
UnnamedTags = [];
|
||||
};
|
||||
|
||||
export const Meta = HeadComponents.Meta;
|
||||
|
||||
export default {
|
||||
Inject,
|
||||
...HeadComponents,
|
||||
generate,
|
||||
flush,
|
||||
};
|
||||
export default HeadComponents;
|
||||
Reference in New Issue
Block a user