diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx
index 87d8133da..797a452fb 100644
--- a/client/homebrew/editor/editor.jsx
+++ b/client/homebrew/editor/editor.jsx
@@ -9,6 +9,7 @@ import MetadataEditor from './metadataEditor/metadataEditor.jsx';
import SettingsEditor from './settingsEditor/settingsEditor.jsx';
const EDITOR_THEME_KEY = 'HB_editor_theme';
+const EDITOR_SETTINGS_KEY = 'HB_edit_settings';
import defaultCM5Theme from '@themes/codeMirror/default.js';
import darkbrewery from '@themes/codeMirror/darkbrewery.js';
@@ -73,6 +74,12 @@ const Editor = forwardRef(
const [currentEditorTheme, setEditorTheme] = useState(editorTheme);
const [view, setView] = useState('text'); // 'text', 'style', 'meta', 'snippet'
const [snippetBarHeight, setSnippetBarHeight] = useState(26);
+ const [editorSettings, setEditorSettings] = useState({
+ blockShading: false,
+ activeLineShading: true,
+ lineNumbers: true,
+ fontSize: 14
+ })
const editor = useRef(null);
const codeEditor = useRef(null);
@@ -82,6 +89,7 @@ const Editor = forwardRef(
const isStyle = ()=>isView('style');
const isMeta = ()=>isView('meta');
const isSnip = ()=>isView('snippet');
+ const isSettings = ()=>isView('settings');
const isView = (name)=>view === name;
@@ -92,6 +100,8 @@ const Editor = forwardRef(
const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY);
if(editorTheme && EditorThemes.includes(editorTheme)) setEditorTheme(editorTheme); else setEditorTheme('default');
+ const localEditorSettings = window.localStorage.getItem(EDITOR_SETTINGS_KEY);
+ if (localEditorSettings) setEditorSettings(JSON.parse(localEditorSettings));
const snippetBar = document.querySelector('.editor > .snippetBar');
if(!snippetBar) return;
@@ -210,6 +220,11 @@ const Editor = forwardRef(
window.localStorage.setItem(EDITOR_THEME_KEY, newTheme);
setEditorTheme(newTheme);
};
+
+ const updateEditorSettings = (newEditorSettings) => {
+ window.localStorage.setItem(EDITOR_SETTINGS_KEY, JSON.stringify(newEditorSettings));
+ setEditorSettings(newEditorSettings);
+ }
const renderEditor = ()=>{
if(isText()) {
@@ -228,6 +243,7 @@ const Editor = forwardRef(
editorTheme={currentEditorTheme}
renderer={brew.renderer}
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
+ settings={editorSettings}
/>
>
);
@@ -246,6 +262,7 @@ const Editor = forwardRef(
editorTheme={currentEditorTheme}
renderer={brew.renderer}
style={{ height: `calc(100% - ${snippetBarHeight}px)` }}
+ settings={editorSettings}
/>
>
);
@@ -268,6 +285,7 @@ const Editor = forwardRef(
editorTheme={currentEditorTheme}
renderer={brew.renderer}
style={{ height: `calc(100% - 25px)` }}
+ settings={editorSettings}
/>
>
);
@@ -275,7 +293,7 @@ const Editor = forwardRef(
if(isMeta()) {
return (
<>
-
+
);
}
+ if(isSettings()){
+ return (
+ <>
+
+
+ >
+ );
+ }
};
const redo = ()=>codeEditor.current?.redo();
@@ -305,7 +340,6 @@ const Editor = forwardRef(
unfoldCode,
historySize,
}));
-
return (
{
- const [settings, setSettings] = useState({
- blockShading: false,
- activeLineShading: true,
- showLineNumbers: true,
- });
+const SettingsEditor = ({settings, updateSettings = () => {} }) => {
+ const [currentSettings, setCurrentSettings] = useState(settings);
+
+ const validations = {
+ fontSize: [
+ (value) => {
+ const number = Number(value);
+
+ if (number < 9 || number > 30) {
+ return 'Font size must be between 9 and 30.';
+ }
+
+ return null;
+ },
+ ],
+ };
const handleFieldChange = (setting, e) => {
- setSettings((prevSettings) => ({
- ...prevSettings,
- [setting]: e.target.checked,
- }));
+ 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 = {
+ ...currentSettings,
+ [setting]: e.target.type === 'number'
+ ? Number(value)
+ : value,
+ };
+
+ setCurrentSettings(updatedSettings);
+ updateSettings(updatedSettings);
};
return (
-
+
);
};
-export default SettingsEditor;
+export default SettingsEditor;
\ No newline at end of file
diff --git a/client/homebrew/editor/settingsEditor/settingsEditor.less b/client/homebrew/editor/settingsEditor/settingsEditor.less
new file mode 100644
index 000000000..21e8e1af6
--- /dev/null
+++ b/client/homebrew/editor/settingsEditor/settingsEditor.less
@@ -0,0 +1,373 @@
+@import '@sharedStyles/core.less';
+
+
+.settingsEditor {
+ 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;
+ justify-content: space-between;
+ flex-wrap : wrap;
+ width : 100%;
+ min-width : 200px;
+ border-bottom:1px solid;
+ padding:5px;
+ & > label {
+ width : fit-content;
+ 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-decoration:unset;
+ }
+ }
+
+ .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; }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx
index 96edbd49c..ec4a583e5 100644
--- a/client/homebrew/editor/snippetbar/snippetbar.jsx
+++ b/client/homebrew/editor/snippetbar/snippetbar.jsx
@@ -245,7 +245,7 @@ const Snippetbar = createReactClass({
return (
- {this.props.view !== 'meta' && <>
+ {this.props.view !== 'meta' && this.props.view !== 'settings' && <>
@@ -293,6 +293,10 @@ const Snippetbar = createReactClass({
onClick={()=>this.props.onViewChange('meta')}>
+
this.props.onViewChange('settings')}>
+
+
diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less
index e49e02044..879693c2b 100644
--- a/client/homebrew/editor/snippetbar/snippetbar.less
+++ b/client/homebrew/editor/snippetbar/snippetbar.less
@@ -21,7 +21,7 @@
.editors {
display : flex;
justify-content : flex-end;
- min-width : 250px; //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;
&:only-child {min-width : unset; margin-left : auto;}
@@ -208,7 +208,7 @@
}
}
}
-@container editor (width < 816px) {
+@container editor (width < 841px) {
.snippetBar {
.editors {
flex : 1;