Merge pull request #4970 from naturalcrit/add-settings-editor

Editor settings tab
This commit is contained in:
Víctor Losada Hernández
2026-09-24 15:09:32 +02:00
committed by GitHub
8 changed files with 361 additions and 179 deletions
+37 -7
View File
@@ -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;
} }
+64 -12
View File
@@ -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);
@@ -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,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;
color : black; font-size : 0.65rem;
background-color : #DDDDDD; font-weight : 800;
font-size : .65rem; color : black;
font-family: 'Open Sans', sans-serif; text-transform : uppercase;
text-transform: uppercase; background-color : #DDDDDD;
font-weight: 800; reading-flow : flex-visual;
.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;
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;
@@ -206,18 +202,16 @@
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; }
} }
} }
} }
@container editor (width < 841px) { @container editor (width < 841px) {
.snippetBar { .snippetBar {
.editors { .editors {
flex : 1; flex : 1;
justify-content : space-between; justify-content : space-between;
@@ -232,4 +226,3 @@
.editors > div.history > .dropdown { right : unset; } .editors > div.history > .dropdown { right : unset; }
} }
} }
@@ -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;
} }
@@ -63,10 +64,17 @@
&[data-tooltip-right] { max-width : 380px; } &[data-tooltip-right] { max-width : 380px; }
&:invalid { background : #FFB9B9; } &:invalid { background : #FFB9B9; }
small { small {
display : block; display : block;
font-size : 0.9em; width : fit-content;
font-style : italic; margin-inline : 5px;
line-height : 1.4em; 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 { input[type='text'], textarea {
@@ -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);
}
}