mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-09-25 22:22:58 +00:00
Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-codemirror
This commit is contained in:
@@ -82,6 +82,9 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Test - HTML sanitization
|
name: Test - HTML sanitization
|
||||||
command: npm run test:safehtml
|
command: npm run test:safehtml
|
||||||
|
- run:
|
||||||
|
name: Test - Helpers
|
||||||
|
command: npm run test:helpers
|
||||||
- run:
|
- run:
|
||||||
name: Test - Coverage
|
name: Test - Coverage
|
||||||
command: npm run test:coverage
|
command: npm run test:coverage
|
||||||
|
|||||||
+3
-4
@@ -128,7 +128,7 @@ Fixes issue [#4858](https://github.com/naturalcrit/homebrewery/issues/4858)
|
|||||||
Fixes part of issue [#4101](https://github.com/naturalcrit/homebrewery/issues/4101)
|
Fixes part of issue [#4101](https://github.com/naturalcrit/homebrewery/issues/4101)
|
||||||
|
|
||||||
##### G-Ambatte
|
##### G-Ambatte
|
||||||
* [x] Fix editor panel shrinking when openingdev tools
|
* [x] Fix editor panel shrinking when opening dev tools
|
||||||
|
|
||||||
Fixes issue [#4866](https://github.com/naturalcrit/homebrewery/issues/4866)
|
Fixes issue [#4866](https://github.com/naturalcrit/homebrewery/issues/4866)
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ Fixes issue [#4904](https://github.com/naturalcrit/homebrewery/issues/4904)
|
|||||||
##### 5e-Cleric, Gazook89
|
##### 5e-Cleric, Gazook89
|
||||||
* [x] Fix various issues with Codemirror 6
|
* [x] Fix various issues with Codemirror 6
|
||||||
|
|
||||||
Fixes issues [#4771](https://github.com/naturalcrit/homebrewery/issues/4771), [#4583](https://github.com/naturalcrit/homebrewery/issues/4783)
|
Fixes issues [#4771](https://github.com/naturalcrit/homebrewery/issues/4771), [#4783](https://github.com/naturalcrit/homebrewery/issues/4783)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
\page
|
\page
|
||||||
@@ -170,7 +170,6 @@ Fixes issues [#4771](https://github.com/naturalcrit/homebrewery/issues/4771), [#
|
|||||||
|
|
||||||
##### 5e-Cleric
|
##### 5e-Cleric
|
||||||
* [x] Add auto-suggest to tag entry input box
|
* [x] Add auto-suggest to tag entry input box
|
||||||
* [x] Replace all example artwork with
|
|
||||||
* [x] Added tooltips to the {{openSans :fas_circle_info: **Properties**}} menu
|
* [x] Added tooltips to the {{openSans :fas_circle_info: **Properties**}} menu
|
||||||
* [x] Removed {{openSans **SYSTEMS**}} checkboxes from {{openSans :fas_circle_info: **Properties**}} menu; instead {{openSans **TAGS**}} should be used for this purpose
|
* [x] Removed {{openSans **SYSTEMS**}} checkboxes from {{openSans :fas_circle_info: **Properties**}} menu; instead {{openSans **TAGS**}} should be used for this purpose
|
||||||
* [x] Replace all AI-generated art with public domain art
|
* [x] Replace all AI-generated art with public domain art
|
||||||
@@ -222,7 +221,7 @@ Fixes issue [#4559](https://github.com/naturalcrit/homebrewery/issues/4559)
|
|||||||
##### G-Ambatte
|
##### G-Ambatte
|
||||||
* [x] Fix default save location failing on new documents
|
* [x] Fix default save location failing on new documents
|
||||||
|
|
||||||
Fixes issue [#4437](https://github.com/naturalcrit/homebrewery/issues/3175)
|
Fixes issue [#4437](https://github.com/naturalcrit/homebrewery/issues/4437)
|
||||||
* [x] Fix usernames with special symbols unable to open userpage
|
* [x] Fix usernames with special symbols unable to open userpage
|
||||||
|
|
||||||
Fixes issue [#807](https://github.com/naturalcrit/homebrewery/issues/807)
|
Fixes issue [#807](https://github.com/naturalcrit/homebrewery/issues/807)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* eslint max-lines: ["error", { "max": 405 }] */
|
/* eslint max-lines: ["error", { "max": 455 }] */
|
||||||
import './codeEditor.less';
|
import './codeEditor.less';
|
||||||
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ import cm5Themes from 'codemirror-5-themes';
|
|||||||
const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
||||||
const themeCompartment = new Compartment();
|
const themeCompartment = new Compartment();
|
||||||
const highlightCompartment = new Compartment();
|
const highlightCompartment = new Compartment();
|
||||||
|
const settingsCompartment = new Compartment();
|
||||||
|
|
||||||
import { generalKeymap, markdownKeymap, cssKeymap, formatCSS } from './extensions/customKeyMaps.js';
|
import { generalKeymap, markdownKeymap, cssKeymap, formatCSS } from './extensions/customKeyMaps.js';
|
||||||
import foldOnPages from './extensions/customFolding.js';
|
import foldOnPages from './extensions/customFolding.js';
|
||||||
@@ -78,6 +79,20 @@ const programmaticCursorLineField = StateField.define({
|
|||||||
provide : (decorationSet)=>EditorView.decorations.from(decorationSet)
|
provide : (decorationSet)=>EditorView.decorations.from(decorationSet)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createSettingsExtensions = (settings)=>[
|
||||||
|
...(settings.autoCloseBrackets ? [autoCloseBrackets] : []),
|
||||||
|
...(settings.lineNumbers ? [lineNumbers()] : []),
|
||||||
|
...(settings.activeLineShading ? [highlightActiveLine(),
|
||||||
|
highlightActiveLineGutter()] : []),
|
||||||
|
...(settings.fontSize
|
||||||
|
? [EditorView.theme({
|
||||||
|
'&, .cm-content' : {
|
||||||
|
fontSize : `${settings.fontSize || 1}em`,
|
||||||
|
},
|
||||||
|
})]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
const CodeEditor = forwardRef(
|
const CodeEditor = forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -88,9 +103,11 @@ const CodeEditor = forwardRef(
|
|||||||
onChange = ()=>{},
|
onChange = ()=>{},
|
||||||
onCursorChange = ()=>{},
|
onCursorChange = ()=>{},
|
||||||
onViewChange = ()=>{},
|
onViewChange = ()=>{},
|
||||||
|
onThemeChange = ()=>{},
|
||||||
editorTheme = 'default',
|
editorTheme = 'default',
|
||||||
style,
|
style,
|
||||||
renderer,
|
renderer,
|
||||||
|
settings = {},
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@@ -163,8 +180,7 @@ const CodeEditor = forwardRef(
|
|||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
setEventListeners,
|
setEventListeners,
|
||||||
languageExtension,
|
languageExtension,
|
||||||
autoCloseBrackets,
|
settingsCompartment.of(createSettingsExtensions(settings)),
|
||||||
lineNumbers(),
|
|
||||||
scrollPastEnd(),
|
scrollPastEnd(),
|
||||||
search(),
|
search(),
|
||||||
history(), //allows for undo and redo
|
history(), //allows for undo and redo
|
||||||
@@ -178,10 +194,8 @@ const CodeEditor = forwardRef(
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
//highlights
|
//highlights
|
||||||
highlightCompartment.of([customHighlightPlugin(renderer, tab), highlightExtension]),
|
highlightCompartment.of([customHighlightPlugin(renderer, tab, settings), highlightExtension]),
|
||||||
themeCompartment.of(themeExtension),
|
themeCompartment.of(themeExtension),
|
||||||
highlightActiveLine(),
|
|
||||||
highlightActiveLineGutter(),
|
|
||||||
|
|
||||||
//keyboard shortcut
|
//keyboard shortcut
|
||||||
keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]),
|
keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]),
|
||||||
@@ -271,6 +285,12 @@ const CodeEditor = forwardRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
view.setState(nextState);
|
view.setState(nextState);
|
||||||
|
view.dispatch({
|
||||||
|
effects : settingsCompartment.reconfigure(
|
||||||
|
createSettingsExtensions(settings)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
restoreFolds(view, foldsRef.current[tab]);
|
restoreFolds(view, foldsRef.current[tab]);
|
||||||
|
|
||||||
const savedScroll = scrollRef.current[tab];
|
const savedScroll = scrollRef.current[tab];
|
||||||
@@ -308,6 +328,9 @@ const CodeEditor = forwardRef(
|
|||||||
view.dispatch({
|
view.dispatch({
|
||||||
effects : themeCompartment.reconfigure(themeExtension),
|
effects : themeCompartment.reconfigure(themeExtension),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isDark = view.state.facet(EditorView.darkTheme);
|
||||||
|
onThemeChange(isDark);
|
||||||
}, [editorTheme, tab]);
|
}, [editorTheme, tab]);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
@@ -320,10 +343,21 @@ const CodeEditor = forwardRef(
|
|||||||
: syntaxHighlighting(legacyCustomHighlightStyle);
|
: syntaxHighlighting(legacyCustomHighlightStyle);
|
||||||
|
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
effects : highlightCompartment.reconfigure([customHighlightPlugin(renderer, tab), highlightExtension]),
|
effects : highlightCompartment.reconfigure([customHighlightPlugin(renderer, tab, settings), highlightExtension])
|
||||||
});
|
});
|
||||||
}, [renderer, tab]);
|
}, [renderer, tab]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
const view = viewRef.current;
|
||||||
|
if(!view) return;
|
||||||
|
|
||||||
|
view.dispatch({
|
||||||
|
effects : settingsCompartment.reconfigure(
|
||||||
|
createSettingsExtensions(settings)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
useImperativeHandle(ref, ()=>({
|
useImperativeHandle(ref, ()=>({
|
||||||
|
|
||||||
injectText : (text)=>{
|
injectText : (text)=>{
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ class ImageWidget extends WidgetType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function customHighlightPlugin(renderer, tab) {
|
export function customHighlightPlugin(renderer, tab, settings) {
|
||||||
//this function takes the custom tokens created in the tokenize function in customhighlight files
|
//this function takes the custom tokens created in the tokenize function in customhighlight files
|
||||||
//takes the tokens defined by that function and assigns classes to them
|
//takes the tokens defined by that function and assigns classes to them
|
||||||
//it also creates page number and snippet number widgets
|
//it also creates page number and snippet number widgets
|
||||||
@@ -398,7 +398,7 @@ export function customHighlightPlugin(renderer, tab) {
|
|||||||
const tree = ensureSyntaxTree(view.state, view.state.doc.length, 50) || syntaxTree(view.state);
|
const tree = ensureSyntaxTree(view.state, view.state.doc.length, 50) || syntaxTree(view.state);
|
||||||
tree.iterate({
|
tree.iterate({
|
||||||
enter : (node)=>{
|
enter : (node)=>{
|
||||||
if(node.name === 'Image') {
|
if(node.name === 'Image' && settings.showImagePreviews) {
|
||||||
const url = getUrl(node, view.state.doc);
|
const url = getUrl(node, view.state.doc);
|
||||||
|
|
||||||
const widgetPosition = node.node.lastChild.from;
|
const widgetPosition = node.node.lastChild.from;
|
||||||
@@ -431,7 +431,7 @@ export function customHighlightPlugin(renderer, tab) {
|
|||||||
const to = line.from + token.to;
|
const to = line.from + token.to;
|
||||||
|
|
||||||
const attrs = {};
|
const attrs = {};
|
||||||
if(token.type === 'Image' && token.url) {
|
if(token.type === 'Image' && token.url && settings.showImagePreviews) {
|
||||||
|
|
||||||
attrs['data-url'] = token.url;
|
attrs['data-url'] = token.url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const Combobox = createReactClass({
|
|||||||
displayName : 'Combobox',
|
displayName : 'Combobox',
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
|
id : '',
|
||||||
className : '',
|
className : '',
|
||||||
trigger : 'hover',
|
trigger : 'hover',
|
||||||
default : '',
|
default : '',
|
||||||
@@ -75,6 +76,7 @@ const Combobox = createReactClass({
|
|||||||
onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}
|
onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}
|
||||||
{...(this.props.tooltip ? { 'data-tooltip-right': this.props.tooltip } : {})}>
|
{...(this.props.tooltip ? { 'data-tooltip-right': this.props.tooltip } : {})}>
|
||||||
<input
|
<input
|
||||||
|
id={this.props.id}
|
||||||
type='text'
|
type='text'
|
||||||
onChange={(e)=>this.handleInput(e)}
|
onChange={(e)=>this.handleInput(e)}
|
||||||
value={this.state.value || ''}
|
value={this.state.value || ''}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
.item i {
|
.item i {
|
||||||
position : absolute;
|
position : absolute;
|
||||||
right : 10px;
|
right : 10px;
|
||||||
color : black;
|
color : inherit;
|
||||||
}
|
}
|
||||||
.dropdown-options {
|
.dropdown-options {
|
||||||
position : absolute;
|
position : absolute;
|
||||||
@@ -32,14 +32,13 @@
|
|||||||
font-size : 11px;
|
font-size : 11px;
|
||||||
cursor : default;
|
cursor : default;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color : rgb(163, 163, 163);
|
background-color : #ddd;
|
||||||
filter : brightness(120%);
|
|
||||||
}
|
}
|
||||||
.detail {
|
.detail {
|
||||||
width : 100%;
|
width : 100%;
|
||||||
font-size : 9px;
|
font-size : 9px;
|
||||||
font-style : italic;
|
font-style : italic;
|
||||||
color : rgb(124, 124, 124);
|
color : #7c7c7c;
|
||||||
text-align : left;
|
text-align : left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@property --activeTriggerColor {
|
@property --activeTriggerColor {
|
||||||
syntax: '<color>';
|
syntax: '<color>';
|
||||||
inherits: true;
|
inherits: true;
|
||||||
initial-value: #DDD;
|
initial-value: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root{
|
:root{
|
||||||
@@ -30,4 +30,8 @@
|
|||||||
}
|
}
|
||||||
.menu-wrapper:has(:popover-open) > button { // if menu is open...
|
.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 * .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
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
import ErrorBar from './errorBar/errorBar.jsx';
|
import ErrorBar from './errorBar/errorBar.jsx';
|
||||||
import ToolBar from './toolBar/toolBar.jsx';
|
import ToolBar from './toolBar/toolBar.jsx';
|
||||||
|
|
||||||
@@ -41,7 +41,6 @@ const BrewPage = (props)=>{
|
|||||||
props = {
|
props = {
|
||||||
contents : '',
|
contents : '',
|
||||||
index : 0,
|
index : 0,
|
||||||
hoisted : false,
|
|
||||||
...props
|
...props
|
||||||
};
|
};
|
||||||
const pageRef = useRef(null);
|
const pageRef = useRef(null);
|
||||||
@@ -54,7 +53,7 @@ const BrewPage = (props)=>{
|
|||||||
// Observer for tracking which pages are at least 30% visible in the iframe
|
// Observer for tracking which pages are at least 30% visible in the iframe
|
||||||
const visibleObserver = new IntersectionObserver(
|
const visibleObserver = new IntersectionObserver(
|
||||||
(entries)=>entries.forEach((entry)=>{
|
(entries)=>entries.forEach((entry)=>{
|
||||||
props.onVisibilityChange(pageNum, entry.isIntersecting, false); // add page to array of visible pages.
|
props.onVisibilityChange(pageNum, entry.isIntersecting, false); // add/remove page from array of visible pages.
|
||||||
}),
|
}),
|
||||||
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
|
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
|
||||||
);
|
);
|
||||||
@@ -96,14 +95,16 @@ const BrewRenderer = (props)=>{
|
|||||||
lang : '',
|
lang : '',
|
||||||
errors : [],
|
errors : [],
|
||||||
currentEditorCursorPageNum : 1,
|
currentEditorCursorPageNum : 1,
|
||||||
currentBrewRendererPageNum : 1,
|
|
||||||
themeBundle : {},
|
themeBundle : {},
|
||||||
onPageChange : ()=>{},
|
onPageChange : ()=>{},
|
||||||
...props
|
...props
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pagesRef = useRef(null);
|
||||||
|
|
||||||
const [visiblePages, setVisiblePages] = useState([]);
|
const [visiblePages, setVisiblePages] = useState([]);
|
||||||
const [centerPage , setCenterPage ] = useState(1);
|
const [centerPage , setCenterPage ] = useState(1);
|
||||||
|
const [headerState , setHeaderState ] = useState(false);
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
isMounted : false,
|
isMounted : false,
|
||||||
@@ -125,10 +126,6 @@ const BrewRenderer = (props)=>{
|
|||||||
toolbarState && setDisplayOptions(toolbarState);
|
toolbarState && setDisplayOptions(toolbarState);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [headerState, setHeaderState] = useState(false);
|
|
||||||
|
|
||||||
const pagesRef = useRef(null);
|
|
||||||
|
|
||||||
if(props.renderer == 'legacy') {
|
if(props.renderer == 'legacy') {
|
||||||
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
|
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
|
||||||
} else {
|
} else {
|
||||||
@@ -155,17 +152,16 @@ const BrewRenderer = (props)=>{
|
|||||||
if(index == props.currentEditorCursorPageNum - 1) //Already rendered before this step
|
if(index == props.currentEditorCursorPageNum - 1) //Already rendered before this step
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(Math.abs(index - props.currentBrewRendererPageNum - 1) <= 3)
|
if(Math.abs(index - centerPage - 1) <= 3)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDummyPage = (index)=>{
|
const renderDummyPage = (index)=>
|
||||||
return <div className='phb page' id={`p${index + 1}`} key={index}>
|
<div className='phb page' id={`p${index + 1}`} key={index}>
|
||||||
<i className='fas fa-spinner fa-spin' />
|
<i className='fas fa-spinner fa-spin' />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
|
||||||
|
|
||||||
const renderStyle = ()=>{
|
const renderStyle = ()=>{
|
||||||
const themeStyles = props.themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
|
const themeStyles = props.themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
|
||||||
@@ -223,9 +219,8 @@ const BrewRenderer = (props)=>{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPages = (checkHoists = false)=>{
|
const renderPages = ()=>{
|
||||||
|
if(props.errors?.length)
|
||||||
if(props.errors && props.errors.length)
|
|
||||||
return renderedPages;
|
return renderedPages;
|
||||||
|
|
||||||
if(rawPages.length != renderedPages.length) { // Re-render all pages when page count changes
|
if(rawPages.length != renderedPages.length) { // Re-render all pages when page count changes
|
||||||
@@ -238,16 +233,10 @@ const BrewRenderer = (props)=>{
|
|||||||
renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1);
|
renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1);
|
||||||
|
|
||||||
_.forEach(rawPages, (page, index)=>{
|
_.forEach(rawPages, (page, index)=>{
|
||||||
const varsOnPageRegex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g; // Find out if there are any vars on the page.
|
if((isInView(index) || !renderedPages[index]) && typeof window !== 'undefined'){
|
||||||
const forceRender = checkHoists &&
|
|
||||||
!props.hoisted &&
|
|
||||||
(page.match(varsOnPageRegex)); // forceRender forces pages outside of the PPR range to render if true.
|
|
||||||
// This is necessary on the first load to fully populate the variable table.
|
|
||||||
if((isInView(index) || !renderedPages[index] || forceRender) && typeof window !== 'undefined'){
|
|
||||||
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
|
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(!props.hoisted) { props.hoisted = true; } // Only fully hoist once.
|
|
||||||
return renderedPages;
|
return renderedPages;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,8 +275,8 @@ const BrewRenderer = (props)=>{
|
|||||||
|
|
||||||
window.addEventListener('hashchange', ()=>scrollToHash(window.location.hash));
|
window.addEventListener('hashchange', ()=>scrollToHash(window.location.hash));
|
||||||
|
|
||||||
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
|
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
|
||||||
renderPages(true); //Make sure page is renderable before showing
|
renderPages(); //Make sure page is renderable before showing
|
||||||
setState((prevState)=>({
|
setState((prevState)=>({
|
||||||
...prevState,
|
...prevState,
|
||||||
isMounted : true,
|
isMounted : true,
|
||||||
@@ -313,7 +302,7 @@ const BrewRenderer = (props)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
|
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
|
||||||
renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]);
|
renderedPages = useMemo(()=>renderPages(), [props.text, centerPage, displayOptions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -338,7 +327,7 @@ const BrewRenderer = (props)=>{
|
|||||||
<Frame id='BrewRenderer' title='Rendered Brew Content' initialContent={INITIAL_CONTENT}
|
<Frame id='BrewRenderer' title='Rendered Brew Content' initialContent={INITIAL_CONTENT}
|
||||||
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||||
contentDidMount={frameDidMount}
|
contentDidMount={frameDidMount}
|
||||||
onClick={()=>{emitClick();}}
|
onClick={emitClick}
|
||||||
sandbox='allow-same-origin allow-modals allow-top-navigation'
|
sandbox='allow-same-origin allow-modals allow-top-navigation'
|
||||||
>
|
>
|
||||||
<div className='brewRenderer'
|
<div className='brewRenderer'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import './notificationPopup.less';
|
import './notificationPopup.less';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
import Dialog from '@components/dialog.jsx';
|
import Dialog from '@components/dialog.jsx';
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
import './editor.less';
|
import './editor.less';
|
||||||
import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
|
||||||
import CodeEditor from '@components/codeEditor/codeEditor.jsx';
|
import CodeEditor from '@components/codeEditor/codeEditor.jsx';
|
||||||
import SnippetBar from './snippetbar/snippetbar.jsx';
|
import SnippetBar from './snippetbar/snippetbar.jsx';
|
||||||
import MetadataEditor from './metadataEditor/metadataEditor.jsx';
|
import MetadataEditor from './metadataEditor/metadataEditor.jsx';
|
||||||
|
import SettingsEditor from './settingsEditor/settingsEditor.jsx';
|
||||||
|
|
||||||
const EDITOR_THEME_KEY = 'HB_editor_theme';
|
const EDITOR_THEME_KEY = 'HB_editor_theme';
|
||||||
|
const EDITOR_SETTINGS_KEY = 'HB_edit_settings';
|
||||||
|
|
||||||
import defaultCM5Theme from '@themes/codeMirror/default.js';
|
import defaultCM5Theme from '@themes/codeMirror/default.js';
|
||||||
import darkbrewery from '@themes/codeMirror/darkbrewery.js';
|
import darkbrewery from '@themes/codeMirror/darkbrewery.js';
|
||||||
@@ -19,6 +22,20 @@ const EditorThemes = Object.entries(themes)
|
|||||||
.filter(([name, value])=>Array.isArray(value) && !name.endsWith('Init') && !name.endsWith('Style'))
|
.filter(([name, value])=>Array.isArray(value) && !name.endsWith('Init') && !name.endsWith('Style'))
|
||||||
.map(([name])=>name);
|
.map(([name])=>name);
|
||||||
|
|
||||||
|
const themeNames = Object.entries(themes)
|
||||||
|
.filter(([name, value])=>Array.isArray(value) &&
|
||||||
|
!name.endsWith('Init') &&
|
||||||
|
!name.endsWith('Style')
|
||||||
|
)
|
||||||
|
.map(([name])=>name);
|
||||||
|
|
||||||
|
const EditorThemeNameList = [
|
||||||
|
'default',
|
||||||
|
...themeNames
|
||||||
|
.filter((name)=>name !== 'default')
|
||||||
|
.sort((a, b)=>a.localeCompare(b))
|
||||||
|
];
|
||||||
|
|
||||||
//const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
//const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
//const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
//const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
|
||||||
const DEFAULT_STYLE_TEXT = dedent`
|
const DEFAULT_STYLE_TEXT = dedent`
|
||||||
@@ -50,7 +67,6 @@ const Editor = forwardRef(
|
|||||||
onCursorPageChange = ()=>{},
|
onCursorPageChange = ()=>{},
|
||||||
onViewPageChange = ()=>{},
|
onViewPageChange = ()=>{},
|
||||||
|
|
||||||
editorTheme = 'default',
|
|
||||||
renderer = 'legacy',
|
renderer = 'legacy',
|
||||||
|
|
||||||
moveBrew,
|
moveBrew,
|
||||||
@@ -69,9 +85,17 @@ const Editor = forwardRef(
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
)=>{
|
)=>{
|
||||||
const [currentEditorTheme, setEditorTheme] = useState(editorTheme);
|
|
||||||
const [view, setView] = useState('text'); // 'text', 'style', 'meta', 'snippet'
|
const [view, setView] = useState('text'); // 'text', 'style', 'meta', 'snippet'
|
||||||
const [snippetBarHeight, setSnippetBarHeight] = useState(26);
|
const [snippetBarHeight, setSnippetBarHeight] = useState(26);
|
||||||
|
const [isDark, setIsDark] = useState(false);
|
||||||
|
const [editorSettings, setEditorSettings] = useState({
|
||||||
|
autoCloseBrackets : true,
|
||||||
|
showImagePreviews : true,
|
||||||
|
activeLineShading : true,
|
||||||
|
lineNumbers : true,
|
||||||
|
fontSize : 1,
|
||||||
|
editorTheme : 'default',
|
||||||
|
});
|
||||||
|
|
||||||
const editor = useRef(null);
|
const editor = useRef(null);
|
||||||
const codeEditor = useRef(null);
|
const codeEditor = useRef(null);
|
||||||
@@ -81,6 +105,7 @@ const Editor = forwardRef(
|
|||||||
const isStyle = ()=>isView('style');
|
const isStyle = ()=>isView('style');
|
||||||
const isMeta = ()=>isView('meta');
|
const isMeta = ()=>isView('meta');
|
||||||
const isSnip = ()=>isView('snippet');
|
const isSnip = ()=>isView('snippet');
|
||||||
|
const isSettings = ()=>isView('settings');
|
||||||
|
|
||||||
const isView = (name)=>view === name;
|
const isView = (name)=>view === name;
|
||||||
|
|
||||||
@@ -89,8 +114,12 @@ const Editor = forwardRef(
|
|||||||
brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', handleControlKeys);
|
brewRenderer.onload = ()=>brewRenderer.contentDocument?.addEventListener('keydown', handleControlKeys);
|
||||||
document.addEventListener('keydown', handleControlKeys);
|
document.addEventListener('keydown', handleControlKeys);
|
||||||
|
|
||||||
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
const localEditorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
|
||||||
if(editorTheme && EditorThemes.includes(editorTheme)) setEditorTheme(editorTheme); else setEditorTheme('default');
|
if(localEditorTheme && EditorThemes.includes(localEditorTheme)) {
|
||||||
|
setEditorSettings({ ...editorSettings, editorTheme: localEditorTheme });
|
||||||
|
} else setEditorSettings({ ...editorSettings, editorTheme: 'default' });
|
||||||
|
const localEditorSettings = window.localStorage.getItem(EDITOR_SETTINGS_KEY);
|
||||||
|
if(localEditorSettings) setEditorSettings(JSON.parse(localEditorSettings));
|
||||||
const snippetBar = document.querySelector('.editor > .snippetBar');
|
const snippetBar = document.querySelector('.editor > .snippetBar');
|
||||||
if(!snippetBar) return;
|
if(!snippetBar) return;
|
||||||
|
|
||||||
@@ -111,7 +140,7 @@ const Editor = forwardRef(
|
|||||||
useEffect(()=>{ if(liveScroll) brewJump(currentEditorViewPageNum, false); }, [currentEditorViewPageNum, liveScroll]);
|
useEffect(()=>{ if(liveScroll) brewJump(currentEditorViewPageNum, false); }, [currentEditorViewPageNum, liveScroll]);
|
||||||
useEffect(()=>{ if(liveScroll) brewJump(currentEditorCursorPageNum, false); }, [currentEditorCursorPageNum, liveScroll]);
|
useEffect(()=>{ if(liveScroll) brewJump(currentEditorCursorPageNum, false); }, [currentEditorCursorPageNum, liveScroll]);
|
||||||
|
|
||||||
const handleFormatCode = () => {
|
const handleFormatCode = ()=>{
|
||||||
codeEditor.current?.formatCode();
|
codeEditor.current?.formatCode();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -211,7 +240,12 @@ const Editor = forwardRef(
|
|||||||
|
|
||||||
const updateEditorTheme = (newTheme)=>{
|
const updateEditorTheme = (newTheme)=>{
|
||||||
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
|
||||||
setEditorTheme(newTheme);
|
setEditorSettings({ ...editorSettings, editorTheme: newTheme });
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateEditorSettings = (newEditorSettings)=>{
|
||||||
|
window.localStorage.setItem(EDITOR_SETTINGS_KEY, JSON.stringify(newEditorSettings));
|
||||||
|
setEditorSettings(newEditorSettings);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderEditor = ()=>{
|
const renderEditor = ()=>{
|
||||||
@@ -228,9 +262,11 @@ const Editor = forwardRef(
|
|||||||
onChange={onBrewChange('text')}
|
onChange={onBrewChange('text')}
|
||||||
onCursorChange={(page)=>updateCurrentCursorPage(page)}
|
onCursorChange={(page)=>updateCurrentCursorPage(page)}
|
||||||
onViewChange={(page)=>updateCurrentViewPage(page)}
|
onViewChange={(page)=>updateCurrentViewPage(page)}
|
||||||
editorTheme={currentEditorTheme}
|
editorTheme={editorSettings.editorTheme}
|
||||||
|
onThemeChange={setIsDark}
|
||||||
renderer={brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||||
|
settings={editorSettings}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -246,9 +282,11 @@ const Editor = forwardRef(
|
|||||||
view={view}
|
view={view}
|
||||||
value={brew.style ?? DEFAULT_STYLE_TEXT}
|
value={brew.style ?? DEFAULT_STYLE_TEXT}
|
||||||
onChange={onBrewChange('style')}
|
onChange={onBrewChange('style')}
|
||||||
editorTheme={currentEditorTheme}
|
editorTheme={editorSettings.editorTheme}
|
||||||
|
onThemeChange={setIsDark}
|
||||||
renderer={brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||||
|
settings={editorSettings}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -268,9 +306,11 @@ const Editor = forwardRef(
|
|||||||
value={brew.snippets}
|
value={brew.snippets}
|
||||||
onChange={onBrewChange('snippets')}
|
onChange={onBrewChange('snippets')}
|
||||||
enableFolding={true}
|
enableFolding={true}
|
||||||
editorTheme={currentEditorTheme}
|
editorTheme={editorSettings.editorTheme}
|
||||||
|
onThemeChange={setIsDark}
|
||||||
renderer={brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - 25px)` }}
|
style={{ height: `calc(100% - 25px)` }}
|
||||||
|
settings={editorSettings}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -278,7 +318,7 @@ const Editor = forwardRef(
|
|||||||
if(isMeta()) {
|
if(isMeta()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CodeEditor key='codeEditor' view={view} style={{ display: 'none' }} />
|
<CodeEditor key='codeEditor' tab='brewMetadata' editorTheme={editorSettings.editorTheme} onThemeChange={setIsDark} view={view} style={{ display: 'none' }} settings={editorSettings} />
|
||||||
<MetadataEditor
|
<MetadataEditor
|
||||||
metadata={brew}
|
metadata={brew}
|
||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
@@ -289,6 +329,26 @@ const Editor = forwardRef(
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if(isSettings()){
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CodeEditor
|
||||||
|
key='codeEditor'
|
||||||
|
tab='brewSettings' //necessary or the brew object loses its contents, culprit possibly on the tab dependent useEffect in codeEditor.jsx
|
||||||
|
view={view}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
editorTheme={editorSettings.editorTheme}
|
||||||
|
onThemeChange={setIsDark}
|
||||||
|
settings={editorSettings}
|
||||||
|
/>
|
||||||
|
<SettingsEditor
|
||||||
|
settings={editorSettings}
|
||||||
|
EditorThemeNameList={EditorThemeNameList}
|
||||||
|
updateSettings={updateEditorSettings}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const redo = ()=>codeEditor.current?.redo();
|
const redo = ()=>codeEditor.current?.redo();
|
||||||
@@ -308,9 +368,8 @@ const Editor = forwardRef(
|
|||||||
unfoldCode,
|
unfoldCode,
|
||||||
historySize,
|
historySize,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='editor' ref={editor}>
|
<div className={`editor${isDark ? ' darkMode' : ''}`} ref={editor}>
|
||||||
<SnippetBar
|
<SnippetBar
|
||||||
brew={brew}
|
brew={brew}
|
||||||
view={view}
|
view={view}
|
||||||
@@ -325,7 +384,7 @@ const Editor = forwardRef(
|
|||||||
unfoldCode={unfoldCode}
|
unfoldCode={unfoldCode}
|
||||||
formatCode={isStyle() ? handleFormatCode : null}
|
formatCode={isStyle() ? handleFormatCode : null}
|
||||||
historySize={historySize()}
|
historySize={historySize()}
|
||||||
currentEditorTheme={currentEditorTheme}
|
currentEditorTheme={editorSettings.editorTheme}
|
||||||
updateEditorTheme={updateEditorTheme}
|
updateEditorTheme={updateEditorTheme}
|
||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
cursorPos={codeEditor.current?.getCursorPosition() || {}}
|
cursorPos={codeEditor.current?.getCursorPosition() || {}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
import './metadataEditor.less';
|
import '../uiEditor.less';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -7,7 +7,6 @@ import request from '../../utils/request-middleware.js';
|
|||||||
import Combobox from '@components/combobox.jsx';
|
import Combobox from '@components/combobox.jsx';
|
||||||
import TagInput from '../tagInput/tagInput.jsx';
|
import TagInput from '../tagInput/tagInput.jsx';
|
||||||
|
|
||||||
|
|
||||||
import Themes from '@themes/themes.json';
|
import Themes from '@themes/themes.json';
|
||||||
import validations from './validations.js';
|
import validations from './validations.js';
|
||||||
|
|
||||||
@@ -84,7 +83,6 @@ const MetadataEditor = createReactClass({
|
|||||||
return `- ${err}`;
|
return `- ${err}`;
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
|
|
||||||
|
|
||||||
debouncedReportValidity(e.target, errMessage);
|
debouncedReportValidity(e.target, errMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -156,11 +154,11 @@ const MetadataEditor = createReactClass({
|
|||||||
|
|
||||||
renderPublish : function(){
|
renderPublish : function(){
|
||||||
if(this.props.metadata.published){
|
if(this.props.metadata.published){
|
||||||
return <button className='unpublish' onClick={()=>this.handlePublish(false)}>
|
return <button id='publish-button' className='unpublish' onClick={()=>this.handlePublish(false)}>
|
||||||
<i className='fas fa-ban' aria-hidden='true' /> unpublish
|
<i className='fas fa-ban' aria-hidden='true' /> unpublish
|
||||||
</button>;
|
</button>;
|
||||||
} else {
|
} else {
|
||||||
return <button className='publish' onClick={()=>this.handlePublish(true)}>
|
return <button id='publish-button' className='publish' onClick={()=>this.handlePublish(true)}>
|
||||||
<i className='fas fa-globe' aria-hidden='true' /> publish
|
<i className='fas fa-globe' aria-hidden='true' /> publish
|
||||||
</button>;
|
</button>;
|
||||||
}
|
}
|
||||||
@@ -170,9 +168,9 @@ const MetadataEditor = createReactClass({
|
|||||||
if(!this.props.metadata.editId) return;
|
if(!this.props.metadata.editId) return;
|
||||||
|
|
||||||
return <div className='field delete'>
|
return <div className='field delete'>
|
||||||
<label>delete</label>
|
<label htmlFor='delete-button'>delete</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
<button className='publish' onClick={this.handleDelete}>
|
<button id='delete-button' onClick={this.handleDelete}>
|
||||||
<i className='fas fa-trash-alt' /> delete brew
|
<i className='fas fa-trash-alt' /> delete brew
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,8 +184,8 @@ const MetadataEditor = createReactClass({
|
|||||||
<label>authors</label>
|
<label>authors</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
{authors.length > 0 && (
|
{authors.length > 0 && (
|
||||||
<a href={`/user/${authors[0]}`} className='author-link' target="_blank" title={`Owner - Click to open ${authors[0]}'s profile in a new tab`}>
|
<a href={`/user/${authors[0]}`} className='author-link' target='_blank' title={`Owner - Click to open ${authors[0]}'s profile in a new tab`}>
|
||||||
{authors[0]}{authors.length > 1 && ', '}
|
{authors[0]}{authors.length > 1 && ', '}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{authors.length > 1 && authors.slice(1).map((author, i)=>(
|
{authors.length > 1 && authors.slice(1).map((author, i)=>(
|
||||||
@@ -227,7 +225,6 @@ const MetadataEditor = createReactClass({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderThemeDropdown : function(){
|
renderThemeDropdown : function(){
|
||||||
@@ -264,6 +261,7 @@ const MetadataEditor = createReactClass({
|
|||||||
dropdown =
|
dropdown =
|
||||||
<div className='value' data-tooltip-top='Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew.'>
|
<div className='value' data-tooltip-top='Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew.'>
|
||||||
<Combobox trigger='click'
|
<Combobox trigger='click'
|
||||||
|
id='combobox-themes'
|
||||||
className='themes-dropdown'
|
className='themes-dropdown'
|
||||||
default={currentThemeDisplay}
|
default={currentThemeDisplay}
|
||||||
placeholder='Select from below, or enter the Share URL or ID of a brew with the meta:theme tag'
|
placeholder='Select from below, or enter the Share URL or ID of a brew with the meta:theme tag'
|
||||||
@@ -284,7 +282,7 @@ const MetadataEditor = createReactClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <div className='field themes'>
|
return <div className='field themes'>
|
||||||
<label>theme</label>
|
<label htmlFor='combobox-themes'>theme</label>
|
||||||
{dropdown}
|
{dropdown}
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
@@ -303,9 +301,10 @@ const MetadataEditor = createReactClass({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return <div className='field language'>
|
return <div className='field language'>
|
||||||
<label>language</label>
|
<label htmlFor='combobox-language'>language</label>
|
||||||
<div className='value' data-tooltip-right='Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.'>
|
<div className='value' data-tooltip-right='Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.'>
|
||||||
<Combobox trigger='click'
|
<Combobox trigger='click'
|
||||||
|
id='combobox-language'
|
||||||
className='language-dropdown'
|
className='language-dropdown'
|
||||||
default={this.props.metadata.lang || ''}
|
default={this.props.metadata.lang || ''}
|
||||||
placeholder='en'
|
placeholder='en'
|
||||||
@@ -355,24 +354,24 @@ const MetadataEditor = createReactClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='metadataEditor'>
|
return <div className='metadataEditor uiEditor'>
|
||||||
<h1>Properties Editor</h1>
|
<h1>Properties Editor</h1>
|
||||||
|
|
||||||
<div className='field title'>
|
<div className='field title'>
|
||||||
<label for='title_field'>title</label>
|
<label htmlFor='title_field'>title</label>
|
||||||
<input type='text' id='title_field' className='value'
|
<input type='text' id='title_field' className='value'
|
||||||
defaultValue={this.props.metadata.title}
|
defaultValue={this.props.metadata.title}
|
||||||
onChange={(e)=>this.handleFieldChange('title', e)} />
|
onChange={(e)=>this.handleFieldChange('title', e)} />
|
||||||
</div>
|
</div>
|
||||||
<div className='field-group'>
|
<fieldset className='field-group'>
|
||||||
<div className='field-column'>
|
<div className='field-column'>
|
||||||
<div className='field description'>
|
<div className='field description'>
|
||||||
<label for='description_field'>description</label>
|
<label htmlFor='description_field'>description</label>
|
||||||
<textarea id='description_field' defaultValue={this.props.metadata.description} className='value'
|
<textarea id='description_field' defaultValue={this.props.metadata.description} className='value'
|
||||||
onChange={(e)=>this.handleFieldChange('description', e)} />
|
onChange={(e)=>this.handleFieldChange('description', e)} />
|
||||||
</div>
|
</div>
|
||||||
<div className='field thumbnail'>
|
<div className='field thumbnail'>
|
||||||
<label for='thumbnail_field'>thumbnail</label>
|
<label htmlFor='thumbnail_field'>thumbnail</label>
|
||||||
<input type='text'
|
<input type='text'
|
||||||
id='thumbnail_field'
|
id='thumbnail_field'
|
||||||
defaultValue={this.props.metadata.thumbnail}
|
defaultValue={this.props.metadata.thumbnail}
|
||||||
@@ -380,18 +379,19 @@ const MetadataEditor = createReactClass({
|
|||||||
className='value'
|
className='value'
|
||||||
onChange={(e)=>this.handleFieldChange('thumbnail', e)} />
|
onChange={(e)=>this.handleFieldChange('thumbnail', e)} />
|
||||||
<button className='display' onClick={this.toggleThumbnailDisplay}
|
<button className='display' onClick={this.toggleThumbnailDisplay}
|
||||||
aria-label={`${this.state.showThumbnail ? 'hide thumbnail' : 'show thumbnail'}`}>
|
aria-label={`${this.state.showThumbnail ? 'hide thumbnail' : 'show thumbnail'}`}>
|
||||||
<i className={`fas fa-caret-${this.state.showThumbnail ? 'right' : 'left'}`} />
|
<i className={`fas fa-caret-${this.state.showThumbnail ? 'right' : 'left'}`} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.renderThumbnail()}
|
{this.renderThumbnail()}
|
||||||
</div>
|
</fieldset>
|
||||||
|
|
||||||
<div className='field tags'>
|
<div className='field tags'>
|
||||||
<label>Tags</label>
|
<label htmlFor='combobox-tags'>Tags</label>
|
||||||
<div className='value' >
|
<div className='value' >
|
||||||
<TagInput
|
<TagInput
|
||||||
|
id='combobox-tags'
|
||||||
label='tags'
|
label='tags'
|
||||||
valuePatterns={/^\s*(?:(?:group|meta|system|type)\s*:\s*)?[A-Za-z0-9][A-Za-z0-9 \/\\.&_\-]{0,40}\s*$/}
|
valuePatterns={/^\s*(?:(?:group|meta|system|type)\s*:\s*)?[A-Za-z0-9][A-Za-z0-9 \/\\.&_\-]{0,40}\s*$/}
|
||||||
placeholder='add tag' unique={true}
|
placeholder='add tag' unique={true}
|
||||||
@@ -402,7 +402,6 @@ const MetadataEditor = createReactClass({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{this.renderLanguageDropdown()}
|
{this.renderLanguageDropdown()}
|
||||||
|
|
||||||
{this.renderThemeDropdown()}
|
{this.renderThemeDropdown()}
|
||||||
@@ -414,9 +413,10 @@ const MetadataEditor = createReactClass({
|
|||||||
{this.renderAuthors()}
|
{this.renderAuthors()}
|
||||||
|
|
||||||
<div className='field invitedAuthors'>
|
<div className='field invitedAuthors'>
|
||||||
<label>Invited authors</label>
|
<label htmlFor='combobox-invited-authors'>Invited authors</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
<TagInput
|
<TagInput
|
||||||
|
id='combobox-invited-authors'
|
||||||
label='invited authors'
|
label='invited authors'
|
||||||
valuePatterns={/.+/}
|
valuePatterns={/.+/}
|
||||||
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
|
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
|
||||||
@@ -429,11 +429,10 @@ const MetadataEditor = createReactClass({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<h2>Privacy</h2>
|
<h2>Privacy</h2>
|
||||||
|
|
||||||
<div className='field publish'>
|
<div className='field publish'>
|
||||||
<label>publish</label>
|
<label htmlFor='publish-button'>publish</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
{this.renderPublish()}
|
{this.renderPublish()}
|
||||||
<small>Published brews are searchable in <a href='/vault'>the Vault</a> and visible on your user page. Unpublished brews are not indexed in the Vault or visible on your user page, but can still be shared and indexed by search engines. You can unpublish a brew any time.</small>
|
<small>Published brews are searchable in <a href='/vault'>the Vault</a> and visible on your user page. Unpublished brews are not indexed in the Vault or visible on your user page, but can still be shared and indexed by search engines. You can unpublish a brew any time.</small>
|
||||||
|
|||||||
@@ -1,374 +0,0 @@
|
|||||||
@import '@sharedStyles/core.less';
|
|
||||||
|
|
||||||
.userThemeName {
|
|
||||||
padding-right : 10px;
|
|
||||||
padding-left : 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metadataEditor {
|
|
||||||
position : absolute;
|
|
||||||
box-sizing : border-box;
|
|
||||||
width : 100%;
|
|
||||||
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
|
|
||||||
padding : 25px;
|
|
||||||
overflow-y : auto;
|
|
||||||
font-size : 13px;
|
|
||||||
background-color : #999999;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin : 0 0 40px;
|
|
||||||
font-weight : bold;
|
|
||||||
text-transform : uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin : 20px 0;
|
|
||||||
font-weight : bold;
|
|
||||||
color : #555555;
|
|
||||||
border-bottom : 2px solid gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > div { margin-bottom : 10px; }
|
|
||||||
|
|
||||||
.field-group {
|
|
||||||
display : flex;
|
|
||||||
flex-wrap : wrap;
|
|
||||||
gap : 10px;
|
|
||||||
width : 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-column {
|
|
||||||
display : flex;
|
|
||||||
flex : 5 0 200px;
|
|
||||||
flex-direction : column;
|
|
||||||
gap : 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field {
|
|
||||||
position : relative;
|
|
||||||
display : flex;
|
|
||||||
flex-wrap : wrap;
|
|
||||||
width : 100%;
|
|
||||||
min-width : 200px;
|
|
||||||
& > label {
|
|
||||||
width : 80px;
|
|
||||||
font-size : 0.9em;
|
|
||||||
font-weight : 800;
|
|
||||||
line-height : 1.8em;
|
|
||||||
text-transform : uppercase;
|
|
||||||
}
|
|
||||||
& > .value {
|
|
||||||
flex : 1 1 auto;
|
|
||||||
width : 50px;
|
|
||||||
&[data-tooltip-right] { max-width : 380px; }
|
|
||||||
&:invalid { background : #FFB9B9; }
|
|
||||||
small {
|
|
||||||
display : block;
|
|
||||||
font-size : 0.9em;
|
|
||||||
font-style : italic;
|
|
||||||
line-height : 1.4em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input[type='text'], textarea {
|
|
||||||
border : 1px solid gray;
|
|
||||||
&:focus { outline : 1px solid #444444; }
|
|
||||||
}
|
|
||||||
|
|
||||||
&.description {
|
|
||||||
flex : 1;
|
|
||||||
textarea.value {
|
|
||||||
height : auto;
|
|
||||||
font-family : 'Open Sans', sans-serif;
|
|
||||||
resize : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.thumbnail, &.themes {
|
|
||||||
label { line-height : 2.0em; }
|
|
||||||
.value {
|
|
||||||
overflow : hidden;
|
|
||||||
text-overflow : ellipsis;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
.colorButton();
|
|
||||||
padding : 0px 5px;
|
|
||||||
color : white;
|
|
||||||
background-color : black;
|
|
||||||
border : 1px solid #999999;
|
|
||||||
&:hover { background-color : #777777; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.tags .tagInput-dropdown {
|
|
||||||
z-index : 400;
|
|
||||||
max-width : 200px;
|
|
||||||
}
|
|
||||||
&.language .value {
|
|
||||||
z-index : 300;
|
|
||||||
max-width : 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.themes {
|
|
||||||
.value {
|
|
||||||
overflow : visible;
|
|
||||||
text-overflow : auto;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding-right : 5px;
|
|
||||||
padding-left : 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.invitedAuthors .value {
|
|
||||||
z-index : 100;
|
|
||||||
|
|
||||||
.tagInput-dropdown { max-width : 200px; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail-preview {
|
|
||||||
position : relative;
|
|
||||||
flex : 1 1;
|
|
||||||
justify-self : center;
|
|
||||||
width : 80px;
|
|
||||||
height : min-content;
|
|
||||||
max-height : 115px;
|
|
||||||
aspect-ratio : 1 / 1;
|
|
||||||
object-fit : contain;
|
|
||||||
background-color : #AAAAAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.renderers.field .value {
|
|
||||||
label {
|
|
||||||
display : inline-flex;
|
|
||||||
align-items : center;
|
|
||||||
margin-right : 15px;
|
|
||||||
font-size : 0.9em;
|
|
||||||
font-weight : 800;
|
|
||||||
vertical-align : middle;
|
|
||||||
white-space : nowrap;
|
|
||||||
cursor : pointer;
|
|
||||||
user-select : none;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
margin : 3px;
|
|
||||||
vertical-align : middle;
|
|
||||||
cursor : pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.publish.field .value {
|
|
||||||
position : relative;
|
|
||||||
margin-bottom : 15px;
|
|
||||||
button { width : 100%; }
|
|
||||||
button.publish {
|
|
||||||
.colorButton(@blueLight);
|
|
||||||
}
|
|
||||||
button.unpublish {
|
|
||||||
.colorButton(@silver);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete.field .value {
|
|
||||||
button {
|
|
||||||
.colorButton(@red);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.authors.field {
|
|
||||||
.tag {
|
|
||||||
font-weight:300;
|
|
||||||
transition:background-color 0.2s;
|
|
||||||
|
|
||||||
&.owner {
|
|
||||||
position: relative;
|
|
||||||
background-color:@silverLight;
|
|
||||||
min-width:25px;
|
|
||||||
display:grid;
|
|
||||||
place-items:center;
|
|
||||||
font-weight: 900;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: "\f521";
|
|
||||||
font-family: "Font Awesome 6 Free";
|
|
||||||
color:gold;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width:15px;
|
|
||||||
height:15px;
|
|
||||||
rotate:-25deg;
|
|
||||||
translate:-30% -50%;
|
|
||||||
transform: scaleY(0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:has(button) a {
|
|
||||||
padding-right:5px;
|
|
||||||
}
|
|
||||||
&:has(button:hover) {
|
|
||||||
background:#d97d7d;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
color:@red;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color:black;
|
|
||||||
text-underline-offset:0.2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.themes.field {
|
|
||||||
& .dropdown-container {
|
|
||||||
position : relative;
|
|
||||||
z-index : 200;
|
|
||||||
background-color : white;
|
|
||||||
}
|
|
||||||
& .dropdown-options { overflow-y : visible; }
|
|
||||||
.disabled {
|
|
||||||
font-style : italic;
|
|
||||||
color : dimgray;
|
|
||||||
background-color : darkgray;
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
position : relative;
|
|
||||||
padding : 3px 3px;
|
|
||||||
overflow : visible;
|
|
||||||
background-color : white;
|
|
||||||
border-top : 1px solid rgb(118, 118, 118);
|
|
||||||
.preview {
|
|
||||||
position : absolute;
|
|
||||||
top : 0;
|
|
||||||
right : 0;
|
|
||||||
z-index : 1;
|
|
||||||
display : flex;
|
|
||||||
flex-direction : column;
|
|
||||||
width : 200px;
|
|
||||||
overflow : hidden;
|
|
||||||
color : black;
|
|
||||||
background : #CCCCCC;
|
|
||||||
border-radius : 5px;
|
|
||||||
box-shadow : 0 0 5px black;
|
|
||||||
opacity : 0;
|
|
||||||
transition : opacity 250ms ease;
|
|
||||||
h6 {
|
|
||||||
padding-block : 0.5em;
|
|
||||||
padding-inline : 1em;
|
|
||||||
font-weight : 900;
|
|
||||||
border-bottom : 2px solid hsl(0,0%,40%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.texture-container {
|
|
||||||
position : absolute;
|
|
||||||
top : 0;
|
|
||||||
left : 0;
|
|
||||||
width : 100%;
|
|
||||||
height : 100%;
|
|
||||||
min-height : 100%;
|
|
||||||
overflow : hidden;
|
|
||||||
> img {
|
|
||||||
position : absolute;
|
|
||||||
top : 0;
|
|
||||||
right : 0;
|
|
||||||
width : 50%;
|
|
||||||
min-height : 100%;
|
|
||||||
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
|
|
||||||
mask-image : linear-gradient(90deg, transparent, black 20%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color : white;
|
|
||||||
background-color : @blue;
|
|
||||||
filter : unset;
|
|
||||||
}
|
|
||||||
&:hover > .preview { opacity : 1; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.field .list {
|
|
||||||
display : flex;
|
|
||||||
flex : 1 0;
|
|
||||||
flex-wrap : wrap;
|
|
||||||
|
|
||||||
> * { flex : 0 0 auto; }
|
|
||||||
|
|
||||||
#groupedIcon {
|
|
||||||
#backgroundColors;
|
|
||||||
position : relative;
|
|
||||||
top : -0.3em;
|
|
||||||
right : -0.3em;
|
|
||||||
display : inline-block;
|
|
||||||
min-width : 20px;
|
|
||||||
height : ~'calc(100% + 0.6em)';
|
|
||||||
color : white;
|
|
||||||
text-align : center;
|
|
||||||
cursor : pointer;
|
|
||||||
|
|
||||||
i {
|
|
||||||
position : relative;
|
|
||||||
top : 50%;
|
|
||||||
transform : translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:last-child) { border-right : 1px solid black; }
|
|
||||||
|
|
||||||
&:last-child { border-radius : 0 0.5em 0.5em 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
padding : 0.35em;
|
|
||||||
margin : 2px;
|
|
||||||
font-size : 0.95em;
|
|
||||||
background-color : #DDDDDD;
|
|
||||||
border-radius : 0.5em;
|
|
||||||
|
|
||||||
.icon { #groupedIcon; }
|
|
||||||
|
|
||||||
button {
|
|
||||||
cursor : pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
height : ~'calc(.9em + 4px + .6em)';
|
|
||||||
|
|
||||||
input { border-radius : 0.5em 0 0 0.5em; }
|
|
||||||
|
|
||||||
input:last-child { border-radius : 0.5em; }
|
|
||||||
|
|
||||||
.value {
|
|
||||||
width : 7.5vw;
|
|
||||||
min-width : 75px;
|
|
||||||
height : 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
height : ~'calc(.9em + 4px + .6em)';
|
|
||||||
|
|
||||||
input { border-radius : 0.5em 0 0 0.5em; }
|
|
||||||
|
|
||||||
input:last-child { border-radius : 0.5em; }
|
|
||||||
|
|
||||||
.value {
|
|
||||||
width : 7.5vw;
|
|
||||||
min-width : 75px;
|
|
||||||
height : 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invalid:focus { background-color : pink; }
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
#groupedIcon;
|
|
||||||
top : -0.54em;
|
|
||||||
right : 1px;
|
|
||||||
height : 97%;
|
|
||||||
|
|
||||||
i { font-size : 1.125em; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import '../uiEditor.less';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const SettingsEditor = ({ settings, updateSettings = ()=>{}, EditorThemeNameList })=>{
|
||||||
|
|
||||||
|
const validations = {};
|
||||||
|
|
||||||
|
const handleFieldChange = (setting, e)=>{
|
||||||
|
const value =
|
||||||
|
e.target.type === 'checkbox'
|
||||||
|
? e.target.checked
|
||||||
|
: e.target.value;
|
||||||
|
|
||||||
|
const inputRules = validations[setting] ?? [];
|
||||||
|
|
||||||
|
const validationErrors = inputRules
|
||||||
|
.map((rule)=>rule(value))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if(validationErrors.length > 0) {
|
||||||
|
e.target.setCustomValidity(validationErrors.join('\n'));
|
||||||
|
e.target.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.target.setCustomValidity('');
|
||||||
|
|
||||||
|
const updatedSettings = {
|
||||||
|
...settings,
|
||||||
|
[setting] : e.target.type === 'number'
|
||||||
|
? Number(value)
|
||||||
|
: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSettings(updatedSettings);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='settingsEditor uiEditor'>
|
||||||
|
<h1>Editor Settings</h1>
|
||||||
|
|
||||||
|
<div className='field'>
|
||||||
|
<label htmlFor='changeEditorTheme'>
|
||||||
|
Select your Editor Theme
|
||||||
|
</label>
|
||||||
|
<div className='value'>
|
||||||
|
<select id='changeEditorTheme' value={settings.editorTheme} onChange={(e)=>handleFieldChange('editorTheme', e)} >
|
||||||
|
{EditorThemeNameList.map((theme, key)=>{
|
||||||
|
return <option key={key} value={theme}>{theme}</option>;
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='field'>
|
||||||
|
<label htmlFor='autoCloseBrackets'>
|
||||||
|
Automatically close brackets
|
||||||
|
</label>
|
||||||
|
<div className='value'>
|
||||||
|
<input
|
||||||
|
id='autoCloseBrackets'
|
||||||
|
type='checkbox'
|
||||||
|
name='autoCloseBrackets'
|
||||||
|
checked={settings.autoCloseBrackets}
|
||||||
|
onChange={(e)=>handleFieldChange('autoCloseBrackets', e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='field'>
|
||||||
|
<label htmlFor='showImagePreviews'>
|
||||||
|
Show Image Previews when hovering a link
|
||||||
|
</label>
|
||||||
|
<div className='value'>
|
||||||
|
<input
|
||||||
|
id='showImagePreviews'
|
||||||
|
type='checkbox'
|
||||||
|
name='showImagePreviews'
|
||||||
|
checked={settings.showImagePreviews}
|
||||||
|
onChange={(e)=>handleFieldChange('showImagePreviews', e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='field'>
|
||||||
|
<label htmlFor='activeLineShading'>
|
||||||
|
Background shading of active line
|
||||||
|
</label>
|
||||||
|
<div className='value'>
|
||||||
|
<input
|
||||||
|
id='activeLineShading'
|
||||||
|
type='checkbox'
|
||||||
|
name='activeLineShading'
|
||||||
|
checked={settings.activeLineShading}
|
||||||
|
onChange={(e)=>handleFieldChange('activeLineShading', e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='field'>
|
||||||
|
<label htmlFor='lineNumbers'>Show Line Numbers</label>
|
||||||
|
<div className='value'>
|
||||||
|
<input
|
||||||
|
id='lineNumbers'
|
||||||
|
type='checkbox'
|
||||||
|
name='lineNumbers'
|
||||||
|
checked={settings.lineNumbers}
|
||||||
|
onChange={(e)=>handleFieldChange('lineNumbers', e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='field'>
|
||||||
|
<label htmlFor='fontSize'>
|
||||||
|
Editor Font Size
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className='value'>
|
||||||
|
<small style={{ fontSize: `${settings.fontSize || 1}em` }}>from 7px to 26px</small>
|
||||||
|
<input
|
||||||
|
id='fontSize'
|
||||||
|
type='range'
|
||||||
|
min={.5}
|
||||||
|
step={.1}
|
||||||
|
max={2}
|
||||||
|
list='font-sizes'
|
||||||
|
name='fontSize'
|
||||||
|
title={`${Math.round(settings.fontSize * 13)}px`}
|
||||||
|
value={settings.fontSize || 1}
|
||||||
|
onChange={(e)=>handleFieldChange('fontSize', e)}
|
||||||
|
/>
|
||||||
|
<datalist id='font-sizes'>
|
||||||
|
<option value='1' />
|
||||||
|
</datalist>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsEditor;
|
||||||
@@ -10,6 +10,7 @@ import cx from 'classnames';
|
|||||||
import { loadHistory } from '../../utils/versionHistory.js';
|
import { loadHistory } from '../../utils/versionHistory.js';
|
||||||
import { brewSnippetsToJSON } from '@shared/helpers.js';
|
import { brewSnippetsToJSON } from '@shared/helpers.js';
|
||||||
|
|
||||||
|
/*eslint-disable camelcase*/
|
||||||
import Legacy5ePHB from '@themes/Legacy/5ePHB/snippets.js';
|
import Legacy5ePHB from '@themes/Legacy/5ePHB/snippets.js';
|
||||||
import V3_5ePHB from '@themes/V3/5ePHB/snippets.js';
|
import V3_5ePHB from '@themes/V3/5ePHB/snippets.js';
|
||||||
import V3_5eDMG from '@themes/V3/5eDMG/snippets.js';
|
import V3_5eDMG from '@themes/V3/5eDMG/snippets.js';
|
||||||
@@ -23,26 +24,7 @@ const ThemeSnippets = {
|
|||||||
V3_Journal : V3_Journal,
|
V3_Journal : V3_Journal,
|
||||||
V3_Blank : V3_Blank,
|
V3_Blank : V3_Blank,
|
||||||
};
|
};
|
||||||
|
/*eslint-enable camelcase */
|
||||||
import defaultCM5Theme from '@themes/codeMirror/default.js';
|
|
||||||
import darkbrewery from '@themes/codeMirror/darkbrewery.js';
|
|
||||||
import cm5Themes from 'codemirror-5-themes';
|
|
||||||
|
|
||||||
const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
|
||||||
|
|
||||||
const themeNames = Object.entries(themes)
|
|
||||||
.filter(([name, value])=>Array.isArray(value) &&
|
|
||||||
!name.endsWith('Init') &&
|
|
||||||
!name.endsWith('Style')
|
|
||||||
)
|
|
||||||
.map(([name])=>name);
|
|
||||||
|
|
||||||
const EditorThemes = [
|
|
||||||
'default',
|
|
||||||
...themeNames
|
|
||||||
.filter((name)=>name !== 'default')
|
|
||||||
.sort((a, b)=>a.localeCompare(b))
|
|
||||||
];
|
|
||||||
|
|
||||||
const execute = function(val, props){
|
const execute = function(val, props){
|
||||||
if(_.isFunction(val)) return val(props);
|
if(_.isFunction(val)) return val(props);
|
||||||
@@ -53,30 +35,28 @@ const Snippetbar = createReactClass({
|
|||||||
displayName : 'SnippetBar',
|
displayName : 'SnippetBar',
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
brew : {},
|
brew : {},
|
||||||
view : 'text',
|
view : 'text',
|
||||||
onViewChange : ()=>{},
|
onViewChange : ()=>{},
|
||||||
onInject : ()=>{},
|
onInject : ()=>{},
|
||||||
onToggle : ()=>{},
|
onToggle : ()=>{},
|
||||||
showEditButtons : true,
|
showEditButtons : true,
|
||||||
renderer : 'legacy',
|
renderer : 'legacy',
|
||||||
undo : ()=>{},
|
undo : ()=>{},
|
||||||
redo : ()=>{},
|
redo : ()=>{},
|
||||||
historySize : ()=>{},
|
historySize : ()=>{},
|
||||||
foldCode : ()=>{},
|
foldCode : ()=>{},
|
||||||
unfoldCode : ()=>{},
|
unfoldCode : ()=>{},
|
||||||
formatCode : ()=>{},
|
formatCode : ()=>{},
|
||||||
updateEditorTheme : ()=>{},
|
cursorPos : {},
|
||||||
cursorPos : {},
|
themeBundle : [],
|
||||||
themeBundle : [],
|
updateBrew : ()=>{}
|
||||||
updateBrew : ()=>{}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
renderer : this.props.renderer,
|
renderer : this.props.renderer,
|
||||||
themeSelector : false,
|
|
||||||
snippets : [],
|
snippets : [],
|
||||||
showHistory : false,
|
showHistory : false,
|
||||||
historyExists : false,
|
historyExists : false,
|
||||||
@@ -93,7 +73,6 @@ const Snippetbar = createReactClass({
|
|||||||
|
|
||||||
componentDidUpdate : async function(prevProps, prevState) {
|
componentDidUpdate : async function(prevProps, prevState) {
|
||||||
if(prevProps.renderer != this.props.renderer ||
|
if(prevProps.renderer != this.props.renderer ||
|
||||||
prevProps.theme != this.props.theme ||
|
|
||||||
prevProps.themeBundle != this.props.themeBundle ||
|
prevProps.themeBundle != this.props.themeBundle ||
|
||||||
prevProps.brew.snippets != this.props.brew.snippets) {
|
prevProps.brew.snippets != this.props.brew.snippets) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -158,33 +137,6 @@ const Snippetbar = createReactClass({
|
|||||||
this.props.onInject(injectedText);
|
this.props.onInject(injectedText);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleThemeSelector : function(e){
|
|
||||||
if(e.target.tagName != 'SELECT'){
|
|
||||||
this.setState({
|
|
||||||
themeSelector : !this.state.themeSelector
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
changeTheme : function(e){
|
|
||||||
if(e.target.value == this.props.currentEditorTheme) return;
|
|
||||||
this.props.updateEditorTheme(e.target.value);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
themeSelector : false,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderThemeSelector : function(){
|
|
||||||
return <div className='themeSelector'>
|
|
||||||
<select value={this.props.currentEditorTheme} onChange={this.changeTheme} >
|
|
||||||
{EditorThemes.map((theme, key)=>{
|
|
||||||
return <option key={key} value={theme}>{theme}</option>;
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
</div>;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSnippetGroups : function(){
|
renderSnippetGroups : function(){
|
||||||
const snippets = this.state.snippets.filter((snippetGroup)=>snippetGroup.view === this.props.view);
|
const snippets = this.state.snippets.filter((snippetGroup)=>snippetGroup.view === this.props.view);
|
||||||
if(snippets.length === 0) return null;
|
if(snippets.length === 0) return null;
|
||||||
@@ -246,7 +198,7 @@ const Snippetbar = createReactClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='editors'>
|
<div className='editors'>
|
||||||
{this.props.view !== 'meta' && <><div className='historyTools'>
|
{this.props.view !== 'meta' && this.props.view !== 'settings' && <><div className='historyTools'>
|
||||||
<button className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
|
<button className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
|
||||||
onClick={this.toggleHistoryMenu} >
|
onClick={this.toggleHistoryMenu} >
|
||||||
<i className='fas fa-clock-rotate-left' />
|
<i className='fas fa-clock-rotate-left' />
|
||||||
@@ -274,11 +226,6 @@ const Snippetbar = createReactClass({
|
|||||||
onClick={this.props.formatCode} >
|
onClick={this.props.formatCode} >
|
||||||
<i className='fas fa-wand-magic-sparkles' />
|
<i className='fas fa-wand-magic-sparkles' />
|
||||||
</button>
|
</button>
|
||||||
<button className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
|
|
||||||
onClick={this.toggleThemeSelector} >
|
|
||||||
<i className='fas fa-palette' />
|
|
||||||
{this.state.themeSelector && this.renderThemeSelector()}
|
|
||||||
</button>
|
|
||||||
</div></>}
|
</div></>}
|
||||||
|
|
||||||
<div className='tabs'>
|
<div className='tabs'>
|
||||||
@@ -298,6 +245,10 @@ const Snippetbar = createReactClass({
|
|||||||
onClick={()=>this.props.onViewChange('meta')}>
|
onClick={()=>this.props.onViewChange('meta')}>
|
||||||
<i className='fas fa-info-circle' />
|
<i className='fas fa-info-circle' />
|
||||||
</button>
|
</button>
|
||||||
|
<button className={cx('settings', { selected: this.props.view === 'settings' })}
|
||||||
|
onClick={()=>this.props.onViewChange('settings')}>
|
||||||
|
<i className='fas fa-gear' />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -346,7 +297,7 @@ const SnippetGroup = createReactClass({
|
|||||||
<Dropdown groupName={snippet.name} icon={snippet.icon} key={snippet.name}>
|
<Dropdown groupName={snippet.name} icon={snippet.icon} key={snippet.name}>
|
||||||
{this.renderSnippets(snippet.subsnippets)}
|
{this.renderSnippets(snippet.subsnippets)}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
@import (less) '@themes/fonts/5e/fonts.less';
|
@import (less) '@themes/fonts/5e/fonts.less';
|
||||||
|
|
||||||
.snippetBar {
|
.snippetBar {
|
||||||
--activeTriggerColor: inherit;
|
|
||||||
--menuColor : #DDDDDD;
|
--menuColor : #DDDDDD;
|
||||||
|
--textColor : black;
|
||||||
|
--hoverMenuColor : #999;
|
||||||
|
|
||||||
@menuHeight : 25px;
|
@menuHeight : 25px;
|
||||||
position : relative;
|
position : relative;
|
||||||
display : flex;
|
display : flex;
|
||||||
@@ -12,8 +14,8 @@
|
|||||||
reading-flow : flex-visual;
|
reading-flow : flex-visual;
|
||||||
justify-content : space-between;
|
justify-content : space-between;
|
||||||
height : auto;
|
height : auto;
|
||||||
color : black;
|
color : var(--textColor);
|
||||||
background-color : #DDDDDD;
|
background-color : var(--menuColor);
|
||||||
font-size : .65rem;
|
font-size : .65rem;
|
||||||
font-family: 'Open Sans', sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
display : flex;
|
display : flex;
|
||||||
justify-content : flex-end;
|
justify-content : flex-end;
|
||||||
min-width : 275px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
|
min-width : 275px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
|
||||||
font-size: .85rem;
|
font-size : 0.85rem;
|
||||||
&:only-child {min-width : unset; margin-left : auto;}
|
&:only-child {min-width : unset; margin-left : auto;}
|
||||||
reading-order : 2;
|
reading-order : 2;
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
|
|
||||||
&.editorTool:not(.active) { cursor : not-allowed; }
|
&.editorTool:not(.active) { cursor : not-allowed; }
|
||||||
|
|
||||||
&:hover,&.selected { background-color : #999999; }
|
&:hover,&.selected { background-color : var(--hoverMenuColor) }
|
||||||
&.text {
|
&.text {
|
||||||
.tooltipLeft('Brew Editor');
|
.tooltipLeft('Brew Editor');
|
||||||
}
|
}
|
||||||
@@ -101,11 +103,6 @@
|
|||||||
background-color : #999999;
|
background-color : #999999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.divider {
|
|
||||||
width : 5px;
|
|
||||||
background : linear-gradient(currentColor, currentColor) no-repeat center/1px 100%;
|
|
||||||
&:hover { background-color : inherit; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.themeSelector {
|
.themeSelector {
|
||||||
position : absolute;
|
position : absolute;
|
||||||
@@ -138,36 +135,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// removed caret for top level items, by request (makes buttons too wide).
|
// removed caret for top level items, by request (makes buttons too wide).
|
||||||
.menu-wrapper .menu-item:is(.snippets > .menu-wrapper > .menu-item):first-child .caret { display: none; }
|
.menu-wrapper .menu-item:is(.snippets > .menu-wrapper > .menu-item):first-child .caret { display : none; }
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
position : relative;
|
position : relative;
|
||||||
display : flex;
|
display : flex;
|
||||||
justify-content: space-between;
|
align-items : center;
|
||||||
align-items : center;
|
justify-content : space-between;
|
||||||
min-width : max-content;
|
width : 100%;
|
||||||
padding : 5px;
|
min-width : max-content;
|
||||||
cursor : pointer;
|
padding : 5px;
|
||||||
width: 100%;
|
cursor : pointer;
|
||||||
&:is(.menu-list .menu-item) [class*="name"] {
|
&:is(.menu-list .menu-item) [class*='name'] {
|
||||||
padding-inline: 8px; // additional space between icon and name (helpful in Fonts menu especially).
|
padding-inline : 8px; // additional space between icon and name (helpful in Fonts menu especially).
|
||||||
}
|
}
|
||||||
.menu-name {
|
.menu-name {
|
||||||
flex: 1;
|
flex : 1;
|
||||||
text-align: left;
|
text-align : left;
|
||||||
text-box-trim: trim-end;
|
text-box-trim : trim-end;
|
||||||
}
|
}
|
||||||
i {
|
i {
|
||||||
min-width : 25px;
|
min-width : 25px;
|
||||||
height : .85rem;
|
height : 0.85rem;
|
||||||
font-size : 1.2em;
|
font-size : 1.2em;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
&.caret {
|
&.caret { margin-right : 0; }
|
||||||
margin-right: 0;
|
&.caret:is(.menu-wrapper .menu-wrapper *) { text-align : right; }
|
||||||
}
|
|
||||||
&.caret:is(.menu-wrapper .menu-wrapper * ) {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
/* Fonts */
|
/* Fonts */
|
||||||
&.font {
|
&.font {
|
||||||
height : auto;
|
height : auto;
|
||||||
@@ -207,17 +200,17 @@
|
|||||||
border-radius : 12px;
|
border-radius : 12px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color : #999999;
|
background-color : var(--hoverMenuColor);
|
||||||
}
|
}
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: gray;
|
color : gray;
|
||||||
cursor: not-allowed;
|
cursor : not-allowed;
|
||||||
&:hover { background-color: unset; }
|
&:hover { background-color : unset; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@container editor (width < 841px) {
|
@container editor (width < 841px) {
|
||||||
.snippetBar {
|
.snippetBar {
|
||||||
.editors {
|
.editors {
|
||||||
flex : 1;
|
flex : 1;
|
||||||
justify-content : space-between;
|
justify-content : space-between;
|
||||||
@@ -233,3 +226,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor.darkMode .snippetBar {
|
||||||
|
--menuColor : #666;
|
||||||
|
--textColor : #eee;
|
||||||
|
--hoverMenuColor : #444;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Combobox from '@components/combobox.jsx';
|
|||||||
|
|
||||||
import { tagSuggestionList, canonizationList } from './curatedTagSuggestionList.js';
|
import { tagSuggestionList, canonizationList } from './curatedTagSuggestionList.js';
|
||||||
|
|
||||||
const TagInput = ({ tooltip, label, valuePatterns, values = [], unique = true, placeholder = '', smallText = '', onChange })=>{
|
const TagInput = ({ id, tooltip, label, valuePatterns, values = [], unique = true, placeholder = '', smallText = '', onChange })=>{
|
||||||
const [tagList, setTagList] = useState(
|
const [tagList, setTagList] = useState(
|
||||||
values.map((value)=>({
|
values.map((value)=>({
|
||||||
value,
|
value,
|
||||||
@@ -128,6 +128,7 @@ const TagInput = ({ tooltip, label, valuePatterns, values = [], unique = true, p
|
|||||||
return (
|
return (
|
||||||
<div className='tagInputWrap'>
|
<div className='tagInputWrap'>
|
||||||
<Combobox
|
<Combobox
|
||||||
|
id={id}
|
||||||
trigger='click'
|
trigger='click'
|
||||||
className='tagInput-dropdown'
|
className='tagInput-dropdown'
|
||||||
default=''
|
default=''
|
||||||
@@ -155,6 +156,7 @@ const TagInput = ({ tooltip, label, valuePatterns, values = [], unique = true, p
|
|||||||
<ul className='list'>
|
<ul className='list'>
|
||||||
{tagList.map((t, i)=>t.editing ? (
|
{tagList.map((t, i)=>t.editing ? (
|
||||||
<input
|
<input
|
||||||
|
id={`${id}-${i}`}
|
||||||
key={i}
|
key={i}
|
||||||
type='text'
|
type='text'
|
||||||
value={t.draft} // always use draft
|
value={t.draft} // always use draft
|
||||||
|
|||||||
@@ -0,0 +1,442 @@
|
|||||||
|
@import '@themes/assets/assets.less';
|
||||||
|
@import '@sharedStyles/core.less';
|
||||||
|
|
||||||
|
.userThemeName {
|
||||||
|
padding-right : 10px;
|
||||||
|
padding-left : 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uiEditor {
|
||||||
|
--bg-clr : white;
|
||||||
|
--bg-image : @backgroundImageAlt;
|
||||||
|
--h1-clr : black;
|
||||||
|
--h2-clr : #000077;
|
||||||
|
--label-clr : black;
|
||||||
|
--input-bg : white;
|
||||||
|
--input-bg-hover : #DDDDDD;
|
||||||
|
--input-text-clr : black;
|
||||||
|
--input-placeholder-clr : grey;
|
||||||
|
|
||||||
|
position : absolute;
|
||||||
|
box-sizing : border-box;
|
||||||
|
width : 100%;
|
||||||
|
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
|
||||||
|
padding : 25px;
|
||||||
|
overflow-y : auto;
|
||||||
|
font-size : 13px;
|
||||||
|
color : var(--label-clr);
|
||||||
|
background-color : var(--bg-clr);
|
||||||
|
background-image : var(--bg-image);
|
||||||
|
background-size : cover;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin : 0 0 40px;
|
||||||
|
font-size : 18px;
|
||||||
|
font-weight : bold;
|
||||||
|
color : var(--h1-clr);
|
||||||
|
text-transform : uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin : 16px 0;
|
||||||
|
font-size : 15px;
|
||||||
|
font-weight : bold;
|
||||||
|
color : var(--h2-clr);
|
||||||
|
border-bottom : 2px solid currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div ,& > fieldset { margin-bottom : 10px; }
|
||||||
|
|
||||||
|
.field-group {
|
||||||
|
display : flex;
|
||||||
|
flex-wrap : wrap;
|
||||||
|
gap : 10px;
|
||||||
|
width : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-column {
|
||||||
|
display : flex;
|
||||||
|
flex : 5 0 250px;
|
||||||
|
flex-direction : column;
|
||||||
|
gap : 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
position : relative;
|
||||||
|
display : flex;
|
||||||
|
flex-wrap : wrap;
|
||||||
|
width : 100%;
|
||||||
|
min-width : 250px;
|
||||||
|
padding-left : 10px;
|
||||||
|
|
||||||
|
& > label {
|
||||||
|
width : 100px;
|
||||||
|
font-size : 1em;
|
||||||
|
font-weight : 800;
|
||||||
|
line-height : 1.8em;
|
||||||
|
color : inherit;
|
||||||
|
text-transform : capitalize;
|
||||||
|
}
|
||||||
|
& > .value {
|
||||||
|
flex : 1 1 auto;
|
||||||
|
width : 50px;
|
||||||
|
&[data-tooltip-right] { max-width : 380px; }
|
||||||
|
&:invalid { background : #FFB9B9; }
|
||||||
|
small {
|
||||||
|
display : block;
|
||||||
|
width : fit-content;
|
||||||
|
margin-inline : 5px;
|
||||||
|
font-size : 0.9em;
|
||||||
|
font-style : italic;
|
||||||
|
line-height : 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'], input[type='number'] {
|
||||||
|
float : right;
|
||||||
|
text-align : end;
|
||||||
|
}
|
||||||
|
input[type='text'], textarea, select {
|
||||||
|
color : var(--input-text-clr);
|
||||||
|
background-color : var(--input-bg);
|
||||||
|
border : 1px solid var(--input-placeholder-clr);
|
||||||
|
&:hover, :focus { outline : 1px solid var(--input-placeholder-clr); background-color : var(--input-bg-hover); }
|
||||||
|
&::placeholder { color : var(--input-placeholder-clr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='range'] {
|
||||||
|
color: var(--input-text-clr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.description {
|
||||||
|
flex : 1;
|
||||||
|
textarea.value {
|
||||||
|
height : auto;
|
||||||
|
font-family : 'Open Sans', sans-serif;
|
||||||
|
resize : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.thumbnail, &.themes {
|
||||||
|
label { line-height : 2.0em; }
|
||||||
|
.value {
|
||||||
|
overflow : hidden;
|
||||||
|
text-overflow : ellipsis;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
.colorButton();
|
||||||
|
padding : 0px 5px;
|
||||||
|
color : var(--input-text-clr);
|
||||||
|
background-color : var(--input-bg);
|
||||||
|
border : 1px solid #999999;
|
||||||
|
&:hover { background-color : #777777; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.tags .tagInput-dropdown {
|
||||||
|
z-index : 400;
|
||||||
|
max-width : 200px;
|
||||||
|
|
||||||
|
.dropdown-options {
|
||||||
|
color : var(--input-text-clr);
|
||||||
|
background-color : var(--input-bg);
|
||||||
|
|
||||||
|
.item:hover {
|
||||||
|
color : var(--input-bg);
|
||||||
|
background-color : var(--input-text-clr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.language .value {
|
||||||
|
z-index : 300;
|
||||||
|
max-width : 150px;
|
||||||
|
}
|
||||||
|
&.themes {
|
||||||
|
.value {
|
||||||
|
overflow : visible;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding-right : 5px;
|
||||||
|
padding-left : 5px;
|
||||||
|
}
|
||||||
|
& .dropdown-container { z-index : 200; }
|
||||||
|
& .dropdown-options { overflow-y : visible; }
|
||||||
|
.disabled {
|
||||||
|
font-style : italic;
|
||||||
|
color : dimgray;
|
||||||
|
background-color : darkgray;
|
||||||
|
}
|
||||||
|
.item.dropdown-input { border:none;}
|
||||||
|
.item {
|
||||||
|
position : relative;
|
||||||
|
overflow : visible;
|
||||||
|
color : inherit;
|
||||||
|
background-color : var(--input-bg);
|
||||||
|
border-top : 1px solid #767676;
|
||||||
|
.preview {
|
||||||
|
position : absolute;
|
||||||
|
top : 0;
|
||||||
|
right : 0;
|
||||||
|
z-index : 10;
|
||||||
|
display : flex;
|
||||||
|
flex-direction : column;
|
||||||
|
width : 200px;
|
||||||
|
overflow : hidden;
|
||||||
|
color : var(--input-text-clr);
|
||||||
|
background : var(--input-bg);
|
||||||
|
border-radius : 5px;
|
||||||
|
box-shadow : 0 0 5px var(--input-text-clr);
|
||||||
|
opacity : 0;
|
||||||
|
transition : opacity 250ms ease;
|
||||||
|
h6 {
|
||||||
|
padding-block : 0.5em;
|
||||||
|
padding-inline : 1em;
|
||||||
|
font-weight : 900;
|
||||||
|
border-bottom : 2px solid #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input { padding-right : 25px; }
|
||||||
|
|
||||||
|
.texture-container {
|
||||||
|
position : absolute;
|
||||||
|
top : 0;
|
||||||
|
left : 0;
|
||||||
|
width : 100%;
|
||||||
|
height : 100%;
|
||||||
|
min-height : 100%;
|
||||||
|
overflow : hidden;
|
||||||
|
> img {
|
||||||
|
position : absolute;
|
||||||
|
top : 0;
|
||||||
|
right : 0;
|
||||||
|
width : 50%;
|
||||||
|
min-height : 100%;
|
||||||
|
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||||
|
mask-image : linear-gradient(90deg, transparent, black 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover > .preview { opacity : 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.renderers .value {
|
||||||
|
label {
|
||||||
|
display : inline-flex;
|
||||||
|
align-items : center;
|
||||||
|
margin-right : 15px;
|
||||||
|
font-size : 0.9em;
|
||||||
|
font-weight : 800;
|
||||||
|
vertical-align : middle;
|
||||||
|
white-space : nowrap;
|
||||||
|
cursor : pointer;
|
||||||
|
user-select : none;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin : 3px;
|
||||||
|
vertical-align : middle;
|
||||||
|
cursor : pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.authors {
|
||||||
|
.tag {
|
||||||
|
font-weight : 300;
|
||||||
|
transition : background-color 0.2s;
|
||||||
|
|
||||||
|
&.owner {
|
||||||
|
position : relative;
|
||||||
|
display : grid;
|
||||||
|
place-items : center;
|
||||||
|
min-width : 25px;
|
||||||
|
font-weight : 900;
|
||||||
|
background-color : @silverLight;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position : absolute;
|
||||||
|
top : 0;
|
||||||
|
left : 0;
|
||||||
|
width : 15px;
|
||||||
|
height : 15px;
|
||||||
|
font-family : 'Font Awesome 6 Free';
|
||||||
|
color : gold;
|
||||||
|
content : '\f521';
|
||||||
|
transform : scaleY(0.7);
|
||||||
|
rotate : -25deg;
|
||||||
|
translate : -30% -50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:has(button) a { padding-right : 5px; }
|
||||||
|
&:has(button:hover) { background : #D97D7D; }
|
||||||
|
|
||||||
|
button { color : @red; }
|
||||||
|
|
||||||
|
}
|
||||||
|
a { text-underline-offset : 0.2em; }
|
||||||
|
}
|
||||||
|
&.invitedAuthors .value {
|
||||||
|
z-index : 100;
|
||||||
|
|
||||||
|
.tagInput-dropdown { max-width : 200px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.publish .value {
|
||||||
|
position : relative;
|
||||||
|
margin-bottom : 15px;
|
||||||
|
button { width : 100%; }
|
||||||
|
button.publish {
|
||||||
|
.colorButton(@blueLight);
|
||||||
|
}
|
||||||
|
button.unpublish {
|
||||||
|
.colorButton(@silver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.delete .value {
|
||||||
|
button {
|
||||||
|
.colorButton(@red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display : flex;
|
||||||
|
flex : 1 0;
|
||||||
|
flex-wrap : wrap;
|
||||||
|
|
||||||
|
> * { flex : 0 0 auto; }
|
||||||
|
|
||||||
|
#groupedIcon {
|
||||||
|
#backgroundColors;
|
||||||
|
position : relative;
|
||||||
|
top : -0.3em;
|
||||||
|
right : -0.3em;
|
||||||
|
display : inline-block;
|
||||||
|
min-width : 20px;
|
||||||
|
height : ~'calc(100% + 0.6em)';
|
||||||
|
color : white;
|
||||||
|
text-align : center;
|
||||||
|
cursor : pointer;
|
||||||
|
|
||||||
|
i {
|
||||||
|
position : relative;
|
||||||
|
top : 50%;
|
||||||
|
transform : translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) { border-right : 1px solid black; }
|
||||||
|
|
||||||
|
&:last-child { border-radius : 0 0.5em 0.5em 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
padding : 0.35em;
|
||||||
|
margin : 2px;
|
||||||
|
font-size : 0.95em;
|
||||||
|
border-radius : 0.5em;
|
||||||
|
|
||||||
|
.icon { #groupedIcon; }
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor : pointer;
|
||||||
|
|
||||||
|
&:hover { color : @redLight; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
height : ~'calc(.9em + 4px + .6em)';
|
||||||
|
|
||||||
|
input { border-radius : 0.5em 0 0 0.5em; }
|
||||||
|
|
||||||
|
input:last-child { border-radius : 0.5em; }
|
||||||
|
|
||||||
|
.value {
|
||||||
|
width : 7.5vw;
|
||||||
|
min-width : 75px;
|
||||||
|
height : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
height : ~'calc(.9em + 4px + .6em)';
|
||||||
|
|
||||||
|
input { border-radius : 0.5em 0 0 0.5em; }
|
||||||
|
|
||||||
|
input:last-child { border-radius : 0.5em; }
|
||||||
|
|
||||||
|
.value {
|
||||||
|
width : 7.5vw;
|
||||||
|
min-width : 75px;
|
||||||
|
height : 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid:focus { background-color : pink; }
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
#groupedIcon;
|
||||||
|
top : -0.54em;
|
||||||
|
right : 1px;
|
||||||
|
height : 97%;
|
||||||
|
|
||||||
|
i { font-size : 1.125em; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-preview {
|
||||||
|
position : relative;
|
||||||
|
flex : 1 1;
|
||||||
|
justify-self : center;
|
||||||
|
width : 80px;
|
||||||
|
height : min-content;
|
||||||
|
max-height : 115px;
|
||||||
|
aspect-ratio : 1 / 1;
|
||||||
|
object-fit : contain;
|
||||||
|
background-color : #AAAAAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
color : var(--input-bg);
|
||||||
|
background : var(--input-text-clr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingsEditor .field {
|
||||||
|
padding-bottom : 10px;
|
||||||
|
border-bottom : 1px solid black;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
|
||||||
|
display : flex;
|
||||||
|
justify-content : end;
|
||||||
|
}
|
||||||
|
|
||||||
|
> label {
|
||||||
|
width : fit-content;
|
||||||
|
max-width : calc(100% - 200px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor.darkMode .uiEditor {
|
||||||
|
--bg-clr : #555555;
|
||||||
|
--bg-image : @backgroundImageAltDark;
|
||||||
|
--h1-clr : white;
|
||||||
|
--h2-clr : #AAAAFF;
|
||||||
|
--label-clr : #DDDDDD;
|
||||||
|
--input-bg : #333333;
|
||||||
|
--input-bg-hover : #666666;
|
||||||
|
--input-text-clr : #EEEEEE;
|
||||||
|
--input-placeholder-clr : #AAAAAA;
|
||||||
|
|
||||||
|
.field { border-color : var(--h1-clr); }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color : @blueLight;
|
||||||
|
|
||||||
|
&:visited { color : #CB8AD8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-preview[src='/client/homebrew/thumbnail.png'] { filter : invert(0.8); }
|
||||||
|
button.publish {
|
||||||
|
.colorButton(#3137de) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ const Homebrew = (props)=>{
|
|||||||
global.enablev4 = enablev4;
|
global.enablev4 = enablev4;
|
||||||
|
|
||||||
const backgroundObject = ()=>{
|
const backgroundObject = ()=>{
|
||||||
if(config?.deployment || (config?.local && config?.development)) {
|
if(config?.deployment || config?.developmentStyle) {
|
||||||
const bgText = config?.deployment || 'Local';
|
const bgText = config?.deployment || 'Local';
|
||||||
return {
|
return {
|
||||||
backgroundImage : `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='100px' width='200px'><text x='0' y='15' fill='%23fff7' font-size='20'>${bgText}</text></svg>")`
|
backgroundImage : `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='100px' width='200px'><text x='0' y='15' fill='%23fff7' font-size='20'>${bgText}</text></svg>")`
|
||||||
@@ -60,7 +60,7 @@ const Homebrew = (props)=>{
|
|||||||
if(brew.pureError) {
|
if(brew.pureError) {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
<div className={`homebrew${(config?.deployment || config?.developmentStyle) ? ' deployment' : ''}`} style={backgroundObject()}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={brew.originalUrl} element={<WithRoute el={ErrorPage} brew={brew} />} />
|
<Route path={brew.originalUrl} element={<WithRoute el={ErrorPage} brew={brew} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
@@ -72,7 +72,7 @@ const Homebrew = (props)=>{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
<div className={`homebrew${(config?.deployment || config?.developmentStyle) ? ' deployment' : ''}`} style={backgroundObject()}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
|
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
|
||||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} />} />
|
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} />} />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './editPage.less';
|
|||||||
// Common imports
|
// Common imports
|
||||||
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
|
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||||
@@ -294,7 +294,6 @@ const EditPage = (props)=>{
|
|||||||
lang={currentBrew.lang}
|
lang={currentBrew.lang}
|
||||||
onPageChange={setCurrentBrewRendererPageNum}
|
onPageChange={setCurrentBrewRendererPageNum}
|
||||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import './errorPage.less';
|
import './errorPage.less';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import UIPage from '../basePages/uiPage/uiPage.jsx';
|
import UIPage from '../basePages/uiPage/uiPage.jsx';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
import ErrorIndex from './errors/errorIndex.js';
|
import ErrorIndex from './errors/errorIndex.js';
|
||||||
|
|
||||||
const ErrorPage = ({ brew })=>{
|
const ErrorPage = ({ brew })=>{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './homePage.less';
|
|||||||
// Common imports
|
// Common imports
|
||||||
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
|
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||||
@@ -141,7 +141,6 @@ const HomePage =(props)=>{
|
|||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
onPageChange={setCurrentBrewRendererPageNum}
|
onPageChange={setCurrentBrewRendererPageNum}
|
||||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './newPage.less';
|
|||||||
// Common imports
|
// Common imports
|
||||||
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
|
import React, { useState, useEffect, useRef, useEffectEvent } from 'react';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
|
||||||
@@ -188,7 +188,6 @@ const NewPage = (props)=>{
|
|||||||
lang={currentBrew.lang}
|
lang={currentBrew.lang}
|
||||||
onPageChange={setCurrentBrewRendererPageNum}
|
onPageChange={setCurrentBrewRendererPageNum}
|
||||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
|||||||
@@ -12,12 +12,15 @@ const { both: RecentNavItem } = RecentNavItems;
|
|||||||
import Account from '@navbar/account.navitem.jsx';
|
import Account from '@navbar/account.navitem.jsx';
|
||||||
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
|
||||||
|
|
||||||
|
import request from '../../utils/request-middleware.js';
|
||||||
|
|
||||||
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||||
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
||||||
|
|
||||||
const SharePage = (props)=>{
|
const SharePage = (props)=>{
|
||||||
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
|
const { disableMeta = false } = props;
|
||||||
|
|
||||||
|
const [currentBrew, setCurrentBrew] = useState(props.brew || DEFAULT_BREW_LOAD);
|
||||||
const [themeBundle, setThemeBundle] = useState({});
|
const [themeBundle, setThemeBundle] = useState({});
|
||||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||||
|
|
||||||
@@ -35,18 +38,30 @@ const SharePage = (props)=>{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchUpdatedBrew = async ()=>{
|
||||||
|
const response = await request
|
||||||
|
.get(`/api/fetch/${currentBrew.shareId}`)
|
||||||
|
.catch((error)=>{
|
||||||
|
console.log('error at fetching updated brew: ', error);
|
||||||
|
});
|
||||||
|
if(response.ok && !!response.body.brew) {
|
||||||
|
setCurrentBrew(response.body.brew);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
document.addEventListener('keydown', handleControlKeys);
|
document.addEventListener('keydown', handleControlKeys);
|
||||||
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
|
fetchThemeBundle(undefined, setThemeBundle, currentBrew.renderer, currentBrew.theme);
|
||||||
|
|
||||||
// listen for changes in the brew version
|
// listen for changes in the brew version
|
||||||
const eventSource = new EventSource('/stream');
|
const eventSource = new EventSource('/stream');
|
||||||
eventSource.addEventListener('message', (evt)=>{
|
eventSource.addEventListener('message', (evt)=>{
|
||||||
const messageData = JSON.parse(evt.data);
|
const messageData = JSON.parse(evt.data);
|
||||||
|
|
||||||
if(messageData.eventType == 'brewUpdated'){
|
if(messageData.eventType == 'brewUpdated'){
|
||||||
if(messageData.shareId == brew.shareId && messageData.version != brew.version) {
|
if(messageData.shareId == currentBrew.shareId && messageData.version != currentBrew.version) {
|
||||||
console.log(`brew has been updated, viewing ${brew.version}, new version is ${messageData.version}`);
|
console.log('should fetch brew');
|
||||||
|
fetchUpdatedBrew();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -57,13 +72,13 @@ const SharePage = (props)=>{
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const processShareId = ()=>{
|
const processShareId = ()=>{
|
||||||
return brew.googleId && !brew.stubbed ? brew.googleId + brew.shareId : brew.shareId;
|
return currentBrew.googleId && !currentBrew.stubbed ? currentBrew.googleId + currentBrew.shareId : currentBrew.shareId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderEditLink = ()=>{
|
const renderEditLink = ()=>{
|
||||||
if(!brew.editId) return null;
|
if(!currentBrew.editId) return null;
|
||||||
|
|
||||||
const editLink = brew.googleId && ! brew.stubbed ? brew.googleId + brew.editId : brew.editId;
|
const editLink = currentBrew.googleId && ! currentBrew.stubbed ? currentBrew.googleId + currentBrew.editId : currentBrew.editId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}>
|
<Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}>
|
||||||
@@ -74,7 +89,7 @@ const SharePage = (props)=>{
|
|||||||
|
|
||||||
const titleEl = (
|
const titleEl = (
|
||||||
<Nav.item className='brewTitle' style={disableMeta ? { cursor: 'default' } : {}}>
|
<Nav.item className='brewTitle' style={disableMeta ? { cursor: 'default' } : {}}>
|
||||||
{brew.title}
|
{currentBrew.title}
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -83,11 +98,11 @@ const SharePage = (props)=>{
|
|||||||
<Meta name='robots' content='noindex, nofollow' />
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<Nav.section className='titleSection'>
|
<Nav.section className='titleSection'>
|
||||||
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
|
{disableMeta ? titleEl : <MetadataNav brew={currentBrew}>{titleEl}</MetadataNav>}
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
|
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
{brew.shareId && (
|
{currentBrew.shareId && (
|
||||||
<>
|
<>
|
||||||
<PrintNavItem />
|
<PrintNavItem />
|
||||||
<Nav.dropdown>
|
<Nav.dropdown>
|
||||||
@@ -120,21 +135,20 @@ const SharePage = (props)=>{
|
|||||||
</Nav.dropdown>
|
</Nav.dropdown>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<RecentNavItem brew={brew} storageKey='view' />
|
<RecentNavItem brew={currentBrew} storageKey='view' />
|
||||||
<Account />
|
<Account />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<BrewRenderer
|
<BrewRenderer
|
||||||
text={brew.text}
|
text={currentBrew.text}
|
||||||
style={brew.style}
|
style={currentBrew.style}
|
||||||
lang={brew.lang}
|
lang={currentBrew.lang}
|
||||||
renderer={brew.renderer}
|
renderer={currentBrew.renderer}
|
||||||
theme={brew.theme}
|
theme={currentBrew.theme}
|
||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
onPageChange={handleBrewRendererPageChange}
|
onPageChange={handleBrewRendererPageChange}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"development": true,
|
"development_style": false,
|
||||||
"host" : "homebrewery.local.naturalcrit.com:8000",
|
"host" : "homebrewery.local.naturalcrit.com:8000",
|
||||||
"naturalcrit_url" : "local.naturalcrit.com:8010",
|
"naturalcrit_url" : "local.naturalcrit.com:8010",
|
||||||
"secret" : "secret",
|
"secret" : "secret",
|
||||||
|
|||||||
Generated
+881
-2717
File diff suppressed because it is too large
Load Diff
+27
-26
@@ -39,6 +39,7 @@
|
|||||||
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
|
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
|
||||||
"test:route": "jest tests/routes/static-pages.test.js --verbose",
|
"test:route": "jest tests/routes/static-pages.test.js --verbose",
|
||||||
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
|
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
|
||||||
|
"test:helpers": "jest tests/html/helpers.test.js --verbose",
|
||||||
"phb": "node --experimental-require-module scripts/phb.js",
|
"phb": "node --experimental-require-module scripts/phb.js",
|
||||||
"prod": "set NODE_ENV=production && npm run build",
|
"prod": "set NODE_ENV=production && npm run build",
|
||||||
"postinstall": "npm run build",
|
"postinstall": "npm run build",
|
||||||
@@ -86,13 +87,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^8.0.1",
|
"@babel/core": "^8.0.6",
|
||||||
"@babel/plugin-transform-runtime": "^8.0.1",
|
"@babel/plugin-transform-runtime": "^8.0.6",
|
||||||
"@babel/preset-env": "^8.0.2",
|
"@babel/preset-env": "^8.0.6",
|
||||||
"@babel/preset-react": "^8.0.1",
|
"@babel/preset-react": "^8.0.1",
|
||||||
"@babel/runtime": "^8.0.0",
|
"@babel/runtime": "^8.0.5",
|
||||||
"@codemirror/autocomplete": "^6.20.3",
|
"@codemirror/autocomplete": "^6.20.3",
|
||||||
"@codemirror/commands": "^6.11.0",
|
"@codemirror/commands": "^6.11.1",
|
||||||
"@codemirror/highlight": "^0.19.8",
|
"@codemirror/highlight": "^0.19.8",
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
"@codemirror/lang-javascript": "^6.2.5",
|
"@codemirror/lang-javascript": "^6.2.5",
|
||||||
@@ -100,17 +101,17 @@
|
|||||||
"@codemirror/language": "^6.12.2",
|
"@codemirror/language": "^6.12.2",
|
||||||
"@codemirror/language-data": "^6.5.2",
|
"@codemirror/language-data": "^6.5.2",
|
||||||
"@codemirror/search": "^6.6.0",
|
"@codemirror/search": "^6.6.0",
|
||||||
"@codemirror/state": "^6.6.0",
|
"@codemirror/state": "^6.7.5",
|
||||||
"@codemirror/view": "^6.43.9",
|
"@codemirror/view": "^6.43.12",
|
||||||
"@dmsnell/diff-match-patch": "^1.1.0",
|
"@dmsnell/diff-match-patch": "^1.1.0",
|
||||||
"@googleapis/drive": "^21.0.0",
|
"@googleapis/drive": "^26.0.0",
|
||||||
"@lezer/highlight": "^1.2.3",
|
"@lezer/highlight": "^1.2.3",
|
||||||
"@oddbird/css-anchor-positioning": "^0.10.2",
|
"@oddbird/css-anchor-positioning": "^0.10.2",
|
||||||
"@sanity/diff-match-patch": "^3.2.0",
|
"@sanity/diff-match-patch": "^3.2.0",
|
||||||
"@vitejs/plugin-react": "^6.0.5",
|
"@vitejs/plugin-react": "^6.0.5",
|
||||||
"body-parser": "^2.3.0",
|
"body-parser": "^2.3.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"codemirror-5-themes": "^1.5.1",
|
"codemirror-5-themes": "^1.5.3",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"core-js": "^3.50.0",
|
"core-js": "^3.50.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
@@ -118,58 +119,58 @@
|
|||||||
"dedent": "^1.7.2",
|
"dedent": "^1.7.2",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "3.0.1",
|
"express-static-gzip": "3.0.2",
|
||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
"fs-extra": "^11.3.5",
|
"fs-extra": "^11.3.5",
|
||||||
"hash-wasm": "^4.12.0",
|
"hash-wasm": "^4.12.0",
|
||||||
"hbmarkedwrapper": "^1.0.0",
|
|
||||||
"idb-keyval": "^6.2.5",
|
"idb-keyval": "^6.2.5",
|
||||||
"js-yaml": "^5.3.0",
|
"js-yaml": "^5.4.2",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
"less": "^4.8.1",
|
"less": "^4.9.1",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
"marked": "15.0.12",
|
"marked": "15.0.12",
|
||||||
"marked-alignment-paragraphs": "^1.0.0",
|
"marked-alignment-paragraphs": "^1.0.0",
|
||||||
"marked-definition-lists": "^1.0.1",
|
"marked-definition-lists": "^1.0.1",
|
||||||
"marked-diagrams-markdeep": "^1.0.1",
|
"marked-diagrams-markdeep": "^1.0.1",
|
||||||
"marked-emoji": "^2.0.3",
|
"marked-emoji": "^3.0.0",
|
||||||
"marked-extended-tables": "^2.0.1",
|
"marked-extended-tables": "^2.0.1",
|
||||||
"marked-gfm-heading-id": "^4.1.4",
|
"marked-gfm-heading-id": "^4.1.4",
|
||||||
|
"marked-hbfm": "^1.0.1",
|
||||||
"marked-nonbreaking-spaces": "^1.0.1",
|
"marked-nonbreaking-spaces": "^1.0.1",
|
||||||
"marked-smartypants-lite": "^1.0.3",
|
"marked-smartypants-lite": "^1.0.3",
|
||||||
"marked-subsuper-text": "^1.0.4",
|
"marked-subsuper-text": "^1.0.4",
|
||||||
"marked-variables": "^1.0.5",
|
"marked-variables": "^1.0.5",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.31.0",
|
||||||
"mongoose": "^9.9.3",
|
"mongoose": "^9.10.1",
|
||||||
"nanoid": "6.0.1",
|
"nanoid": "6.0.1",
|
||||||
"nconf": "^0.13.0",
|
"nconf": "^0.13.0",
|
||||||
"node": "^26.7.0",
|
"node": "^26.9.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.9.8",
|
||||||
"react": "^19.2.7",
|
"react": "^19.3.0",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.3.0",
|
||||||
"react-frame-component": "^5.3.2",
|
"react-frame-component": "^5.3.2",
|
||||||
"react-router": "^8.3.0",
|
"react-router": "^8.4.0",
|
||||||
"sanitize-filename": "1.6.4",
|
"sanitize-filename": "1.6.4",
|
||||||
"superagent": "^10.2.1"
|
"superagent": "^10.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^5.3.0",
|
"@stylistic/stylelint-plugin": "^5.3.0",
|
||||||
"babel-jest": "^30.4.1",
|
"babel-jest": "^30.5.2",
|
||||||
"babel-plugin-transform-import-meta": "^3.0.0",
|
"babel-plugin-transform-import-meta": "^3.0.0",
|
||||||
"eslint": "9.7",
|
"eslint": "9.7",
|
||||||
"eslint-plugin-jest": "^29.15.1",
|
"eslint-plugin-jest": "^29.15.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"jest": "^30.4.2",
|
"jest": "^30.5.2",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"jsdom": "^30.0.1",
|
"jsdom": "^30.1.0",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"stylelint": "^17.11.1",
|
"stylelint": "^17.15.0",
|
||||||
"stylelint-config-recess-order": "^7.7.0",
|
"stylelint-config-recess-order": "^7.7.0",
|
||||||
"stylelint-config-recommended": "^18.0.0",
|
"stylelint-config-recommended": "^18.0.0",
|
||||||
"supertest": "^7.1.4",
|
"supertest": "^7.1.4",
|
||||||
"vite": "^8.2.1"
|
"vite": "^8.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-6
@@ -14,6 +14,7 @@ import express from 'express';
|
|||||||
import config from './config.js';
|
import config from './config.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
|
||||||
|
|
||||||
import api from './homebrew.api.js';
|
import api from './homebrew.api.js';
|
||||||
const { homebrewApi, getBrew, getCSS } = api;
|
const { homebrewApi, getBrew, getCSS } = api;
|
||||||
@@ -135,6 +136,15 @@ export default async function createApp(vite) {
|
|||||||
app.get('/robots.txt', (req, res)=>{
|
app.get('/robots.txt', (req, res)=>{
|
||||||
return res.sendFile(`robots.txt`, { root: process.cwd() });
|
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) => {
|
||||||
|
const { brew } = req;
|
||||||
|
brew.authors.includes(req.account?.username)
|
||||||
|
? sanitizeBrew(brew, 'shareAuthor')
|
||||||
|
: sanitizeBrew(brew, 'share');
|
||||||
|
splitTextStyleAndMetadata(brew);
|
||||||
|
res.json({ brew });
|
||||||
|
}));
|
||||||
|
|
||||||
//Serve brew metadata
|
//Serve brew metadata
|
||||||
app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res)=>{
|
app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res)=>{
|
||||||
@@ -235,11 +245,12 @@ export default async function createApp(vite) {
|
|||||||
|
|
||||||
// Create configuration object
|
// Create configuration object
|
||||||
const configuration = {
|
const configuration = {
|
||||||
local : isLocalEnvironment,
|
local : isLocalEnvironment,
|
||||||
publicUrl : config.get('publicUrl') ?? '',
|
publicUrl : config.get('publicUrl') ?? '',
|
||||||
baseUrl : `${req.protocol}://${req.get('host')}`,
|
baseUrl : `${req.protocol}://${req.get('host')}`,
|
||||||
environment : nodeEnv,
|
environment : nodeEnv,
|
||||||
deployment : config.get('heroku_app_name') ?? ''
|
deployment : config.get('heroku_app_name') ?? '',
|
||||||
|
developmentStyle : config.get('development_style')
|
||||||
};
|
};
|
||||||
const props = {
|
const props = {
|
||||||
version : version,
|
version : version,
|
||||||
@@ -269,9 +280,14 @@ export default async function createApp(vite) {
|
|||||||
html = await vite.transformIndexHtml(req.originalUrl, html);
|
html = await vite.transformIndexHtml(req.originalUrl, html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const safeProps = JSON.stringify(props).replace(/<(?=\/?script)/ig, '\\u003c');
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
'<head>',
|
'<head>',
|
||||||
()=>{ return `<head>\n<script id="props" >window.__INITIAL_PROPS__ = ${JSON.stringify(props)}</script>\n${ogMetaTags}`; }
|
`<head>\n`
|
||||||
|
+ `<script id="props">`
|
||||||
|
+ `window.__INITIAL_PROPS__ = ` + safeProps
|
||||||
|
+ `</script>\n`
|
||||||
|
+ ogMetaTags
|
||||||
);
|
);
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { model as HomebrewModel } from './homebrew.model.js';
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import zlib from 'zlib';
|
import zlib from 'zlib';
|
||||||
import GoogleActions from './googleActions.js';
|
import GoogleActions from './googleActions.js';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import asyncHandler from 'express-async-handler';
|
import asyncHandler from 'express-async-handler';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@@ -577,9 +577,9 @@ const api = {
|
|||||||
router.use(dbCheck);
|
router.use(dbCheck);
|
||||||
|
|
||||||
router.post('/api', checkClientVersion, asyncHandler(api.newBrew));
|
router.post('/api', checkClientVersion, asyncHandler(api.newBrew));
|
||||||
router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
|
router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew)); //alt endpoint, unused
|
||||||
router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
|
router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
|
||||||
router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew));
|
router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew)); //alt endpoint, unused
|
||||||
router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew));
|
router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew));
|
||||||
router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));
|
router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -229,5 +229,6 @@ export {
|
|||||||
printCurrentBrew,
|
printCurrentBrew,
|
||||||
fetchThemeBundle,
|
fetchThemeBundle,
|
||||||
brewSnippetsToJSON,
|
brewSnippetsToJSON,
|
||||||
debugTextMismatch
|
debugTextMismatch,
|
||||||
|
yamlSnippetsToText
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
fetchThemeBundle,
|
||||||
|
brewSnippetsToJSON,
|
||||||
|
debugTextMismatch,
|
||||||
|
yamlSnippetsToText,
|
||||||
|
} from '../../shared/helpers.js';
|
||||||
|
|
||||||
|
import dedent from 'dedent';
|
||||||
|
|
||||||
|
// Marked.js adds line returns after closing tags on some default tokens.
|
||||||
|
// This removes those line returns for comparison sake.
|
||||||
|
String.prototype.trimReturns = function(){
|
||||||
|
return this.replace(/\r?\n|\r/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const emoji = 'df_d12_2';
|
||||||
|
|
||||||
|
const brewSnippetsThemeTest = [
|
||||||
|
{
|
||||||
|
name : 'Test Theme',
|
||||||
|
snippets : dedent `
|
||||||
|
\snippet First Theme Snippet
|
||||||
|
I am the first theme snippet!
|
||||||
|
|
||||||
|
\snippet Second Theme Snippet
|
||||||
|
I am the second theme Snippet!`,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const brewSnippetsBrewTest = dedent`
|
||||||
|
\snippet First Brew Snippet
|
||||||
|
I am the first brew snippet!
|
||||||
|
|
||||||
|
\snippet Second Brew Snippet
|
||||||
|
I am the second brew Snippet!`;
|
||||||
|
|
||||||
|
describe(`brewSnippetsToJSON`, ()=>{
|
||||||
|
it('converts raw brew snippets without theme snippets to JSON', function() {
|
||||||
|
const testMenuObject = {
|
||||||
|
groupName : 'Brew Snippets',
|
||||||
|
icon : 'fas fa-th-list',
|
||||||
|
view : 'text',
|
||||||
|
snippets : [{
|
||||||
|
name : 'Test Snippets JSON without 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 without theme snippets`, brewSnippetsBrewTest, null, true);
|
||||||
|
expect(rendered).toStrictEqual(testMenuObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts raw brew snippets with theme snippets to JSON', function() {
|
||||||
|
const testMenuObject = {
|
||||||
|
groupName : 'Brew Snippets',
|
||||||
|
icon : 'fas fa-th-list',
|
||||||
|
view : 'text',
|
||||||
|
snippets : [{
|
||||||
|
gen : '',
|
||||||
|
icon : '',
|
||||||
|
name : 'Test Theme',
|
||||||
|
subsnippets : [
|
||||||
|
{
|
||||||
|
gen : 'I am the first theme snippet!\n',
|
||||||
|
icon : '',
|
||||||
|
name : 'First Theme Snippet',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gen : 'I am the second theme Snippet!',
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]};
|
||||||
|
const rendered = brewSnippetsToJSON(`Test Snippets JSON with theme snippets`, brewSnippetsBrewTest, brewSnippetsThemeTest, true);
|
||||||
|
expect(rendered).toStrictEqual(testMenuObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`YAMLSnippetsToText`, ()=>{
|
||||||
|
it('converts brew snippet YAML to a string ', function() {
|
||||||
|
const brewSnippetsYAML = [{
|
||||||
|
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 = yamlSnippetsToText(brewSnippetsYAML);
|
||||||
|
expect(rendered).toBe(`${brewSnippetsBrewTest}\n`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
|
test('Processes the markdown within an HTML block if its just a class wrapper', function() {
|
||||||
const source = '<div>*Bold text*</div>';
|
const source = '<div>*Bold text*</div>';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
describe('Inline Definition Lists', ()=>{
|
describe('Inline Definition Lists', ()=>{
|
||||||
test('No Term 1 Definition', function() {
|
test('No Term 1 Definition', function() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
|
|
||||||
// Marked.js adds line returns after closing tags on some default tokens.
|
// Marked.js adds line returns after closing tags on some default tokens.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
describe('Hard Breaks', ()=>{
|
describe('Hard Breaks', ()=>{
|
||||||
test('Single Break', function() {
|
test('Single Break', function() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
|
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
// Marked.js adds line returns after closing tags on some default tokens.
|
// Marked.js adds line returns after closing tags on some default tokens.
|
||||||
// This removes those line returns for comparison sake.
|
// This removes those line returns for comparison sake.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import {hbfm} from 'hbmarkedwrapper';
|
import {hbfm} from 'marked-hbfm';
|
||||||
|
|
||||||
describe('Non-Breaking Spaces Interactions', ()=>{
|
describe('Non-Breaking Spaces Interactions', ()=>{
|
||||||
test('I am actually a single-line definition list!', function() {
|
test('I am actually a single-line definition list!', function() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
describe('Justification', ()=>{
|
describe('Justification', ()=>{
|
||||||
test('Left Justify', function() {
|
test('Left Justify', function() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
|
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import { hbfm } from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
// Marked.js adds line returns after closing tags on some default tokens.
|
// Marked.js adds line returns after closing tags on some default tokens.
|
||||||
// This removes those line returns for comparison sake.
|
// This removes those line returns for comparison sake.
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
.note table tbody tr:nth-child(odd) { background : #FFFFFF; }
|
.note table tbody tr:nth-child(odd) { background : #FFFFFF; }
|
||||||
|
|
||||||
/* DROP CAP */
|
/* DROP CAP */
|
||||||
.first-letter, .drop-cap
|
p.first-letter::first-letter,
|
||||||
|
p.drop-cap::first-letter,
|
||||||
h1 + p::first-letter {
|
h1 + p::first-letter {
|
||||||
color : black;
|
color : black;
|
||||||
background-image : unset;
|
background-image : unset;
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export default [
|
|||||||
name : 'Tweak Drop Cap',
|
name : 'Tweak Drop Cap',
|
||||||
icon : 'fas fa-sliders-h',
|
icon : 'fas fa-sliders-h',
|
||||||
gen : dedent`/* Drop Cap settings */
|
gen : dedent`/* Drop Cap settings */
|
||||||
.page .first-letter, .page .drop-cap,
|
.page p.first-letter::first-letter,
|
||||||
|
.page p.drop-cap::first-letter,
|
||||||
.page h1 + p::first-letter {
|
.page h1 + p::first-letter {
|
||||||
font-family: SolberaImitationRemake;
|
font-family: SolberaImitationRemake;
|
||||||
font-size: 3.5cm;
|
font-size: 3.5cm;
|
||||||
|
|||||||
@@ -87,7 +87,8 @@
|
|||||||
-moz-column-span : all;
|
-moz-column-span : all;
|
||||||
& + p::first-line { font-variant : small-caps; }
|
& + p::first-line { font-variant : small-caps; }
|
||||||
}
|
}
|
||||||
.first-letter, .drop-cap,
|
p.first-letter::first-letter,
|
||||||
|
p.drop-cap::first-letter,
|
||||||
h1 + p::first-letter {
|
h1 + p::first-letter {
|
||||||
float : left;
|
float : left;
|
||||||
padding-bottom : 2px;
|
padding-bottom : 2px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import hbfm from 'hbmarkedwrapper';
|
import { hbfm } from 'marked-hbfm';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createFooterFunc : function(headerSize=1){
|
createFooterFunc : function(headerSize=1){
|
||||||
|
|||||||
@@ -82,7 +82,8 @@
|
|||||||
line-height : 1em;
|
line-height : 1em;
|
||||||
& + p::first-line { font-variant : small-caps; }
|
& + p::first-line { font-variant : small-caps; }
|
||||||
}
|
}
|
||||||
.first-letter, .drop-cap,
|
p.first-letter::first-letter,
|
||||||
|
p.drop-cap::first-letter,
|
||||||
h1 + p::first-letter {
|
h1 + p::first-letter {
|
||||||
float : left;
|
float : left;
|
||||||
padding-top : 0.3em;
|
padding-top : 0.3em;
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
@footerAccentImage : url('/assets/PHB_footerAccent.png');
|
@footerAccentImage : url('/assets/PHB_footerAccent.png');
|
||||||
@frameBorderImage : url('/assets/frameBorder.png');
|
@frameBorderImage : url('/assets/frameBorder.png');
|
||||||
@backgroundImage : url('/assets/parchmentBackground.jpg');
|
@backgroundImage : url('/assets/parchmentBackground.jpg');
|
||||||
|
@backgroundImageAlt : url('/assets/fluffy_background.webp');
|
||||||
|
@backgroundImageAltDark : url('/assets/fluffy_background_dark.webp');
|
||||||
@redTriangleImage : url('/assets/redTriangle.png');
|
@redTriangleImage : url('/assets/redTriangle.png');
|
||||||
@monsterBorderImageLegacy : url('/assets/monsterBorderLegacy.png');
|
@monsterBorderImageLegacy : url('/assets/monsterBorderLegacy.png');
|
||||||
@noteBorderImage : url('/assets/noteBorder.png');
|
@noteBorderImage : url('/assets/noteBorder.png');
|
||||||
@descriptiveBoxImage : url('/assets/descriptiveBorder.png');
|
@descriptiveBoxImage : url('/assets/descriptiveBorder.png');
|
||||||
@monsterBlockBackground : url('/assets/parchmentBackgroundGrayscale.jpg');
|
@monsterBlockBackground : url('/assets/parchmentBackgroundGrayscale.jpg');
|
||||||
@monsterBlockOverlay : url('/assets/parchmentBackgroundOverlayed.jpg');
|
@monsterBlockOverlay : url('/assets/parchmentBackgroundOverlayed.jpg');
|
||||||
@monsterBorderImage : url('/assets/monsterBorderFancy.png');
|
@monsterBorderImage : url('/assets/monsterBorderFancy.png');
|
||||||
@codeBorderImage : url('/assets/codeBorder.png');
|
@codeBorderImage : url('/assets/codeBorder.png');
|
||||||
@classTableDecoration : url('/assets/classTableDecoration.png');
|
@classTableDecoration : url('/assets/classTableDecoration.png');
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
Reference in New Issue
Block a user