Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fit-listpage-into-vault

This commit is contained in:
Víctor Losada Hernández
2026-10-02 18:21:16 +02:00
71 changed files with 5567 additions and 8759 deletions
+4 -4
View File
@@ -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>
}
+2 -2
View File
@@ -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; }
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+39 -44
View File
@@ -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') },
]));
+1 -1
View File
@@ -7,7 +7,7 @@ const Combobox = createReactClass({
displayName : 'Combobox',
getDefaultProps : function() {
return {
id : '',
id : '',
className : '',
trigger : 'hover',
default : '',
+2 -4
View File
@@ -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;
}
}
+2 -2
View File
@@ -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.
+18 -20
View File
@@ -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); }
}
}
+1 -1
View File
@@ -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;
}
+8 -18
View File
@@ -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
+3 -7
View File
@@ -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;
+2 -2
View File
@@ -1,7 +1,7 @@
@import '@sharedStyles/core.less';
.homebrew {
height : 100%;
background-color:@steel;
height : 100%;
background-color : @steel;
&.deployment { background-color : darkred; }
.sitePage {
+1 -3
View File
@@ -39,9 +39,7 @@
flex-grow : 1;
min-width : 300px;
}
>.brewTitle {
cursor:auto;
}
>.brewTitle { cursor : auto; }
}
// "NaturalCrit" logo
.navLogo {
+1 -1
View File
@@ -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);
+16 -119
View File
@@ -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;
+13 -93
View File
@@ -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;
+5 -5
View File
@@ -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%;
}
}
}
+13 -87
View File
@@ -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;
+5 -5
View File
@@ -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; }
+134 -24
View File
@@ -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
};
}