diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx
index c5cf669bd..25bf4f88e 100644
--- a/client/components/codeEditor/codeEditor.jsx
+++ b/client/components/codeEditor/codeEditor.jsx
@@ -308,7 +308,7 @@ const CodeEditor = forwardRef(
view.dispatch({
effects : themeCompartment.reconfigure(themeExtension),
});
- }, [editorTheme]);
+ }, [editorTheme, tab]);
useEffect(()=>{
//rebuild syntax highlight when changing tab or renderer
diff --git a/client/components/codeEditor/extensions/customKeyMaps.js b/client/components/codeEditor/extensions/customKeyMaps.js
index db3edf47f..581268222 100644
--- a/client/components/codeEditor/extensions/customKeyMaps.js
+++ b/client/components/codeEditor/extensions/customKeyMaps.js
@@ -1,33 +1,44 @@
/* eslint max-lines: ["error", { "max": 300 }] */
import { keymap } from '@codemirror/view';
-import { undo, redo, indentMore, deleteLine } from '@codemirror/commands';
+import { undo, redo, indentMore, indentLess, deleteLine } from '@codemirror/commands';
+import { EditorSelection } from '@codemirror/state';
import { Prec } from '@codemirror/state';
const insertTab = (view)=>{
- const { from, to } = view.state.selection.main;
+ // If any selection spans multiple lines, delegates to CodeMirror's indentMore
+ // Otherwise inserts two spaces at each cursor/selection
+ const shouldIndent = view.state.selection.ranges.some((range)=>view.state.doc.lineAt(range.from).number !==
+ view.state.doc.lineAt(range.to).number
+ );
+
+ if(shouldIndent) return indentMore(view);
+
+ const changes = [];
+
+ for (const range of view.state.selection.ranges) {
+ changes.push({
+ from : range.from,
+ to : range.to,
+ insert : ' ' // Insert two spaces, not a tab char!
+ });
+ }
+ // Create a transaction so we can map old positions to
+ // their new positions after the edits are applied
+ const mappedChanges = view.state.update({ changes });
view.dispatch({
- changes : { from, to, insert: ' ' },
- selection : { anchor: from + 2 }
+ changes,
+ selection : EditorSelection.create(
+ view.state.selection.ranges.map((range)=>EditorSelection.cursor(
+ mappedChanges.changes.mapPos(range.from, 1) + 2
+ )
+ )
+ )
});
return true;
};
-const indentLess = (view)=>{
- const { from, to } = view.state.selection.main;
- const lines = [];
- for (let l = view.state.doc.lineAt(from).number; l <= view.state.doc.lineAt(to).number; l++) {
- const line = view.state.doc.line(l);
- const match = line.text.match(/^ {1,2}/); // match up to 2 spaces
- if(match) {
- lines.push({ from: line.from, to: line.from + match[0].length, insert: '' });
- }
- }
- if(lines.length > 0) view.dispatch({ changes: lines });
- return true;
-};
-
const wrapSelection = (prefix, suffix)=>(view)=>{
const changes = [];
@@ -169,16 +180,16 @@ const newPage = (view)=>{
};
export const generalKeymap = Prec.high(keymap.of([
- { key: 'Tab', run: insertTab },
- { key: 'Mod-z', run: undo }, //i think it may be unnecessary
+ { key: 'Tab', run: insertTab }, //runs indentMore if multiple lines selected in a single selection
+ { key: 'Shift-Tab', run: indentLess },
+ { key: 'Mod-z', run: undo }, //it may be unnecessary
{ key: 'Mod-Shift-z', run: redo },
- { key: 'Mod-y', run: redo },
- { key: 'Mod-d', run: deleteLine },
+ { key: 'Mod-y', run: redo }, //user asked, so double keybind
+ { key: 'Mod-d', run: deleteLine }, //annoyingly overrides "selectNextOccurrence" because users asked
]));
export const markdownKeymap = Prec.highest(keymap.of([
- //{ key: 'Shift-Tab', run: indentMore },
- { key: 'Shift-Tab', run: indentLess },
+
{ key: 'Mod-b', run: wrapSelection('**', '**') }, // makeBold
{ key: 'Mod-i', run: wrapSelection('*', '*') }, // makeItalic
{ key: 'Mod-u', run: wrapSelection('', '') }, // makeUnderline
diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index 976f8c9c1..05ca73bea 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -29,6 +29,7 @@ const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState';
const INITIAL_CONTENT = dedent`
+ Rendered Brew Content
@@ -210,7 +211,7 @@ const BrewRenderer = (props)=>{
classes = [classes, injectedTags.classes].join(' ').trim();
attributes = injectedTags.attributes;
if(global.enablev4) {
- if (attributes && Object.hasOwn(attributes, 'hbtemplate')) {
+ if(attributes && Object.hasOwn(attributes, 'hbtemplate')) {
pageTemplates[index] = attributes['hbtemplate'];
}
}
@@ -220,7 +221,7 @@ const BrewRenderer = (props)=>{
if(!pageTemplates[index]) {
for (let i=index;i>=0; i--) {
// If one is found, add the template attribute
- if (pageTemplates[i]) attributes['hbtemplate'] = pageTemplates[i];
+ if(pageTemplates[i]) attributes['hbtemplate'] = pageTemplates[i];
}
}
}
@@ -348,10 +349,11 @@ const BrewRenderer = (props)=>{
0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
{/*render in iFrame so broken code doesn't crash the site.*/}
- {emitClick();}}
+ sandbox="allow-same-origin allow-modals allow-top-navigation"
>
{
if(blacklistAttrs.some((test)=>{return test(attribute);})) {
- element.removeAttribute(attribute.localName);
- break;
+ element.removeAttribute(attribute.name);
+ return;
};
- };
+ });
});
return div.innerHTML;
diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.jsx b/client/homebrew/brewRenderer/toolBar/toolBar.jsx
index 16c89ea59..0f158e0e6 100644
--- a/client/homebrew/brewRenderer/toolBar/toolBar.jsx
+++ b/client/homebrew/brewRenderer/toolBar/toolBar.jsx
@@ -99,11 +99,16 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
return (
-
+
+
+
Options
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
index 2ce76188c..14ea97305 100644
--- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx
+++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
@@ -44,6 +44,7 @@ const MetadataEditor = createReactClass({
getInitialState : function(){
return {
+ isOwner : global.account?.username && global.account?.username === this.props.metadata?.authors[0],
showThumbnail : true
};
},
@@ -144,6 +145,15 @@ const MetadataEditor = createReactClass({
});
},
+ handleDeleteAuthor : function(author){
+ if(!confirm('Are you sure you want to remove this author? They will lose all edit access to this brew, and it will dissapear from their userpage.')) return;
+ if(!this.props.metadata.authors.includes(author)) return;
+ this.props.onChange({
+ ...this.props.metadata,
+ authors : this.props.metadata.authors.filter((a)=>a !== author)
+ });
+ },
+
renderPublish : function(){
if(this.props.metadata.published){
return