mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-09-25 20:12:57 +00:00
Merge pull request #4970 from naturalcrit/add-settings-editor
Editor settings tab
This commit is contained in:
@@ -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(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -91,6 +106,7 @@ const CodeEditor = forwardRef(
|
|||||||
editorTheme = 'default',
|
editorTheme = 'default',
|
||||||
style,
|
style,
|
||||||
renderer,
|
renderer,
|
||||||
|
settings = {},
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@@ -163,8 +179,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 +193,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 +284,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];
|
||||||
@@ -320,10 +339,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import dedent from 'dedent';
|
|||||||
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 +21,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 +66,6 @@ const Editor = forwardRef(
|
|||||||
onCursorPageChange = ()=>{},
|
onCursorPageChange = ()=>{},
|
||||||
onViewPageChange = ()=>{},
|
onViewPageChange = ()=>{},
|
||||||
|
|
||||||
editorTheme = 'default',
|
|
||||||
renderer = 'legacy',
|
renderer = 'legacy',
|
||||||
|
|
||||||
moveBrew,
|
moveBrew,
|
||||||
@@ -69,9 +84,16 @@ 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 [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 +103,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 +112,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 +138,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 +238,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 +260,10 @@ 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}
|
||||||
renderer={brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||||
|
settings={editorSettings}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -246,9 +279,10 @@ 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}
|
||||||
renderer={brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
|
||||||
|
settings={editorSettings}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -268,9 +302,10 @@ const Editor = forwardRef(
|
|||||||
value={brew.snippets}
|
value={brew.snippets}
|
||||||
onChange={onBrewChange('snippets')}
|
onChange={onBrewChange('snippets')}
|
||||||
enableFolding={true}
|
enableFolding={true}
|
||||||
editorTheme={currentEditorTheme}
|
editorTheme={editorSettings.editorTheme}
|
||||||
renderer={brew.renderer}
|
renderer={brew.renderer}
|
||||||
style={{ height: `calc(100% - 25px)` }}
|
style={{ height: `calc(100% - 25px)` }}
|
||||||
|
settings={editorSettings}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -278,7 +313,7 @@ const Editor = forwardRef(
|
|||||||
if(isMeta()) {
|
if(isMeta()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CodeEditor key='codeEditor' view={view} style={{ display: 'none' }} />
|
<CodeEditor key='codeEditor' tab='brewMetadata' view={view} style={{ display: 'none' }} settings={editorSettings} />
|
||||||
<MetadataEditor
|
<MetadataEditor
|
||||||
metadata={brew}
|
metadata={brew}
|
||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
@@ -289,6 +324,24 @@ 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' }}
|
||||||
|
settings={editorSettings}
|
||||||
|
/>
|
||||||
|
<SettingsEditor
|
||||||
|
settings={editorSettings}
|
||||||
|
EditorThemeNameList={EditorThemeNameList}
|
||||||
|
updateSettings={updateEditorSettings}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const redo = ()=>codeEditor.current?.redo();
|
const redo = ()=>codeEditor.current?.redo();
|
||||||
@@ -308,7 +361,6 @@ const Editor = forwardRef(
|
|||||||
unfoldCode,
|
unfoldCode,
|
||||||
historySize,
|
historySize,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='editor' ref={editor}>
|
<div className='editor' ref={editor}>
|
||||||
<SnippetBar
|
<SnippetBar
|
||||||
@@ -325,7 +377,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';
|
||||||
@@ -355,7 +355,7 @@ const MetadataEditor = createReactClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='metadataEditor'>
|
return <div className='metadataEditor ui-editor'>
|
||||||
<h1>Properties Editor</h1>
|
<h1>Properties Editor</h1>
|
||||||
|
|
||||||
<div className='field title'>
|
<div className='field title'>
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
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 ui-editor'>
|
||||||
|
<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 9px to 30px</small>
|
||||||
|
<input
|
||||||
|
id='fontSize'
|
||||||
|
type='range'
|
||||||
|
min={.6}
|
||||||
|
step={.1}
|
||||||
|
max={2}
|
||||||
|
name='fontSize'
|
||||||
|
value={settings.fontSize}
|
||||||
|
onChange={(e)=>handleFieldChange('fontSize', e)}
|
||||||
|
/>
|
||||||
|
</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);
|
||||||
@@ -66,7 +48,6 @@ const Snippetbar = createReactClass({
|
|||||||
foldCode : ()=>{},
|
foldCode : ()=>{},
|
||||||
unfoldCode : ()=>{},
|
unfoldCode : ()=>{},
|
||||||
formatCode : ()=>{},
|
formatCode : ()=>{},
|
||||||
updateEditorTheme : ()=>{},
|
|
||||||
cursorPos : {},
|
cursorPos : {},
|
||||||
themeBundle : [],
|
themeBundle : [],
|
||||||
updateBrew : ()=>{}
|
updateBrew : ()=>{}
|
||||||
@@ -76,7 +57,6 @@ const Snippetbar = createReactClass({
|
|||||||
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,27 +3,27 @@
|
|||||||
@import (less) '@themes/fonts/5e/fonts.less';
|
@import (less) '@themes/fonts/5e/fonts.less';
|
||||||
|
|
||||||
.snippetBar {
|
.snippetBar {
|
||||||
--activeTriggerColor: inherit;
|
--activeTriggerColor : inherit;
|
||||||
--menuColor : #DDDDDD;
|
--menuColor : #DDDDDD;
|
||||||
@menuHeight : 25px;
|
@menuHeight : 25px;
|
||||||
position : relative;
|
position : relative;
|
||||||
display : flex;
|
display : flex;
|
||||||
flex-wrap : wrap-reverse;
|
flex-wrap : wrap-reverse;
|
||||||
reading-flow : flex-visual;
|
|
||||||
justify-content : space-between;
|
justify-content : space-between;
|
||||||
height : auto;
|
height : auto;
|
||||||
|
font-family : 'Open Sans', sans-serif;
|
||||||
|
font-size : 0.65rem;
|
||||||
|
font-weight : 800;
|
||||||
color : black;
|
color : black;
|
||||||
|
text-transform : uppercase;
|
||||||
background-color : #DDDDDD;
|
background-color : #DDDDDD;
|
||||||
font-size : .65rem;
|
reading-flow : flex-visual;
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 800;
|
|
||||||
|
|
||||||
.editors {
|
.editors {
|
||||||
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;
|
||||||
|
|
||||||
@@ -138,36 +138,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;
|
||||||
|
width : 100%;
|
||||||
min-width : max-content;
|
min-width : max-content;
|
||||||
padding : 5px;
|
padding : 5px;
|
||||||
cursor : pointer;
|
cursor : pointer;
|
||||||
width: 100%;
|
&: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;
|
||||||
@@ -206,13 +202,11 @@
|
|||||||
background : grey;
|
background : grey;
|
||||||
border-radius : 12px;
|
border-radius : 12px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover { background-color : #999999; }
|
||||||
background-color : #999999;
|
|
||||||
}
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: gray;
|
color : gray;
|
||||||
cursor: not-allowed;
|
cursor : not-allowed;
|
||||||
&:hover { background-color: unset; }
|
&:hover { background-color : unset; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,4 +226,3 @@
|
|||||||
.editors > div.history > .dropdown { right : unset; }
|
.editors > div.history > .dropdown { right : unset; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+51
-34
@@ -5,7 +5,7 @@
|
|||||||
padding-left : 10px;
|
padding-left : 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadataEditor {
|
.ui-editor {
|
||||||
position : absolute;
|
position : absolute;
|
||||||
box-sizing : border-box;
|
box-sizing : border-box;
|
||||||
width : 100%;
|
width : 100%;
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin : 0 0 40px;
|
margin : 0 0 40px;
|
||||||
|
font-size : 15px;
|
||||||
font-weight : bold;
|
font-weight : bold;
|
||||||
text-transform : uppercase;
|
text-transform : uppercase;
|
||||||
}
|
}
|
||||||
@@ -64,10 +65,17 @@
|
|||||||
&:invalid { background : #FFB9B9; }
|
&:invalid { background : #FFB9B9; }
|
||||||
small {
|
small {
|
||||||
display : block;
|
display : block;
|
||||||
|
width : fit-content;
|
||||||
|
margin-inline : 5px;
|
||||||
font-size : 0.9em;
|
font-size : 0.9em;
|
||||||
font-style : italic;
|
font-style : italic;
|
||||||
line-height : 1.4em;
|
line-height : 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'], input[type='number'] {
|
||||||
|
float : right;
|
||||||
|
text-align : end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
input[type='text'], textarea {
|
input[type='text'], textarea {
|
||||||
border : 1px solid gray;
|
border : 1px solid gray;
|
||||||
@@ -175,47 +183,41 @@
|
|||||||
}
|
}
|
||||||
.authors.field {
|
.authors.field {
|
||||||
.tag {
|
.tag {
|
||||||
font-weight:300;
|
font-weight : 300;
|
||||||
transition:background-color 0.2s;
|
transition : background-color 0.2s;
|
||||||
|
|
||||||
&.owner {
|
&.owner {
|
||||||
position: relative;
|
position : relative;
|
||||||
background-color:@silverLight;
|
display : grid;
|
||||||
min-width:25px;
|
place-items : center;
|
||||||
display:grid;
|
min-width : 25px;
|
||||||
place-items:center;
|
font-weight : 900;
|
||||||
font-weight: 900;
|
background-color : @silverLight;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "\f521";
|
position : absolute;
|
||||||
font-family: "Font Awesome 6 Free";
|
top : 0;
|
||||||
color:gold;
|
left : 0;
|
||||||
position: absolute;
|
width : 15px;
|
||||||
top: 0;
|
height : 15px;
|
||||||
left: 0;
|
font-family : 'Font Awesome 6 Free';
|
||||||
width:15px;
|
color : gold;
|
||||||
height:15px;
|
content : '\f521';
|
||||||
rotate:-25deg;
|
transform : scaleY(0.7);
|
||||||
translate:-30% -50%;
|
rotate : -25deg;
|
||||||
transform: scaleY(0.7);
|
translate : -30% -50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:has(button) a {
|
&:has(button) a { padding-right : 5px; }
|
||||||
padding-right:5px;
|
&:has(button:hover) { background : #D97D7D; }
|
||||||
}
|
|
||||||
&:has(button:hover) {
|
|
||||||
background:#d97d7d;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button { color : @red; }
|
||||||
color:@red;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color:black;
|
color : black;
|
||||||
text-underline-offset:0.2em;
|
text-underline-offset : 0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,9 +329,7 @@
|
|||||||
|
|
||||||
.icon { #groupedIcon; }
|
.icon { #groupedIcon; }
|
||||||
|
|
||||||
button {
|
button { cursor : pointer; }
|
||||||
cursor : pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
.input-group {
|
||||||
@@ -372,3 +372,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user