From 6af97864aabe89153487d37bf78391ff525deadd Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 30 Jun 2020 23:57:47 -0400
Subject: added left/right/center justify text buttons.  changed unpinned text
 menu

---
 .../views/nodes/formattedText/RichTextMenu.tsx     | 45 +++++++++++++++++-----
 1 file changed, 36 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')

diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 839943aac..a06fcec9a 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -412,6 +412,28 @@ export default class RichTextMenu extends AntimodeMenu {
         dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         return true;
     }
+    alignCenter = (state: EditorState<any>, dispatch: any) => {
+        return this.alignParagraphs(state, "center", dispatch);
+    }
+    alignLeft = (state: EditorState<any>, dispatch: any) => {
+        return this.alignParagraphs(state, "left", dispatch);
+    }
+    alignRight = (state: EditorState<any>, dispatch: any) => {
+        return this.alignParagraphs(state, "right", dispatch);
+    }
+
+    alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
+        var tr = state.tr;
+        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+            if (node.type === schema.nodes.paragraph) {
+                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align }, node.marks);
+                return false;
+            }
+            return true;
+        });
+        dispatch?.(tr);
+        return true;
+    }
 
     insertBlockquote(state: EditorState<any>, dispatch: any) {
         const path = (state.selection.$from as any).path;
@@ -768,18 +790,21 @@ export default class RichTextMenu extends AntimodeMenu {
         TraceMobx();
         const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
             !this.collapsed ? this.getDragger() : (null),
-            this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-            this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-            this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-            this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-            this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-            this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+            !this.Pinned ? (null) : <> {[
+                this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+                this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+                this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+                this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+                this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+                this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+            ]}</>,
             this.createColorButton(),
             this.createHighlighterButton(),
             this.createLinkButton(),
             this.createBrushButton(),
-            this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
-            this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
+            this.createButton("align-left", "Align Left", undefined, this.alignLeft),
+            this.createButton("align-center", "Align Center", undefined, this.alignCenter),
+            this.createButton("align-right", "Align Right", undefined, this.alignRight),
         ]}</div>;
 
         const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
@@ -787,7 +812,9 @@ export default class RichTextMenu extends AntimodeMenu {
             <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
                 {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
                 this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"),
+                this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
+                this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),]}
             </div>
             <div key="button">
                 {/* <div key="collapser">
-- 
cgit v1.2.3-70-g09d2


From 9b8b6f79bef78be68a9982ea008e308bba4872b3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Jul 2020 11:37:48 -0400
Subject: fixed text menu to make button commands undoable.  fixed hiding of
 menu to not disappear after pulldown command selection in a cleaner way

---
 src/client/views/AntimodeMenu.tsx                  |  9 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 38 +++++++++-----
 .../views/nodes/formattedText/RichTextMenu.tsx     | 61 ++++++++++++++--------
 3 files changed, 68 insertions(+), 40 deletions(-)

(limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')

diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 3e4d20fea..a4634103c 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -51,18 +51,15 @@ export default abstract class AntimodeMenu extends React.Component {
             if (this._opacity === 0.2) {
                 this._transitionProperty = "opacity";
                 this._transitionDuration = "0.1s";
-                this._transitionDelay = "";
-                this._opacity = 0;
-                this._left = this._top = -300;
             }
 
             if (forceOut) {
                 this._transitionProperty = "";
                 this._transitionDuration = "";
-                this._transitionDelay = "";
-                this._opacity = 0;
-                this._left = this._top = -300;
             }
+            this._transitionDelay = "";
+            this._opacity = 0;
+            this._left = this._top = -300;
         }
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2cb55e0fa..f4aa7919f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -86,6 +86,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
     public static Instance: FormattedTextBox;
     public ProseRef?: HTMLDivElement;
+    public get EditorView() { return this._editorView; }
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _editorView: Opt<EditorView>;
@@ -93,7 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     private _searchIndex = 0;
     private _undoTyping?: UndoManager.Batch;
     private _disposers: { [name: string]: IReactionDisposer } = {};
-    private dropDisposer?: DragManager.DragDropDisposer;
+    private _dropDisposer?: DragManager.DragDropDisposer;
 
     @computed get _recording() { return this.dataDoc.audioState === "recording"; }
     set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
@@ -291,8 +292,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.ProseRef = ele;
-        this.dropDisposer?.();
-        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
+        this._dropDisposer?.();
+        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
     }
 
     @undoBatch
@@ -660,8 +661,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             incomingValue => {
                 if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
                     const updatedState = JSON.parse(incomingValue);
-                    this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
-                    this.tryUpdateHeight();
+                    if (JSON.stringify(this._editorView!.state.toJSON()) !== JSON.stringify(updatedState)) {
+                        this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+                        this.tryUpdateHeight();
+                    }
                 }
             }
         );
@@ -1046,6 +1049,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @action
     onFocused = (e: React.FocusEvent): void => {
+        console.log("FOUCSS")
         FormattedTextBox.FocusedBox = this;
         this.tryUpdateHeight();
 
@@ -1163,18 +1167,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
     }
 
-    public static HadSelection: boolean = false;
-    onBlur = (e: any) => {
-        FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
-        //DictationManager.Controls.stop(false);
+    public startUndoTypingBatch() {
+        this._undoTyping = UndoManager.StartBatch("undoTyping");
+    }
+
+    public endUndoTypingBatch() {
+        const wasUndoing = this._undoTyping;
         if (this._undoTyping) {
             this._undoTyping.end();
             this._undoTyping = undefined;
         }
+        return wasUndoing;
+    }
+    public static HadSelection: boolean = false;
+    onBlur = (e: any) => {
+        console.log("BLURRR")
+        FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
+        //DictationManager.Controls.stop(false);
+        this.endUndoTypingBatch();
         this.doLinkOnDeselect();
 
         // move the richtextmenu offscreen
-        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
+        if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide();
     }
 
     _lastTimedMark: Mark | undefined = undefined;
@@ -1208,7 +1222,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
 
         if (!this._undoTyping) {
-            this._undoTyping = UndoManager.StartBatch("undoTyping");
+            this.startUndoTypingBatch();
         }
     }
 
@@ -1308,7 +1322,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         onScroll={this.onscrolled} onDrop={this.ondrop} >
                         <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
-                                padding: this.layoutDoc._textBoxPadding ? this.layoutDoc._textBoxPadding : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
+                                padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
                                 pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
                             }}
                         />
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index a06fcec9a..7c7a76e1b 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
 import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
+import { action, observable, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { lift, wrapIn } from "prosemirror-commands";
 import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
@@ -23,6 +23,7 @@ import { updateBullets } from "./ProsemirrorExampleTransfer";
 import "./RichTextMenu.scss";
 import { schema } from "./schema_rts";
 import { TraceMobx } from "../../../../fields/util";
+import { UndoManager } from "../../../util/UndoManager";
 const { toggleMark } = require("prosemirror-commands");
 
 library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
@@ -68,6 +69,8 @@ export default class RichTextMenu extends AntimodeMenu {
     @observable private currentLink: string | undefined = "";
     @observable private showLinkDropdown: boolean = false;
 
+    _reaction: IReactionDisposer | undefined;
+    _delayHide = false;
     constructor(props: Readonly<{}>) {
         super(props);
         RichTextMenu.Instance = this;
@@ -138,6 +141,16 @@ export default class RichTextMenu extends AntimodeMenu {
         ];
     }
 
+    componentDidMount() {
+        this._reaction = reaction(() => SelectionManager.SelectedDocuments(),
+            () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true));
+    }
+    componentWillUnmount() {
+        this._reaction?.();
+    }
+
+    public delayHide = () => { this._delayHide = true; }
+
     @action
     changeView(view: EditorView) {
         this.view = view;
@@ -147,16 +160,6 @@ export default class RichTextMenu extends AntimodeMenu {
         this.updateFromDash(view, lastState, this.editorProps);
     }
 
-    public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
-        if (this.view) {
-            const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
-            this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
-                addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-            return this.view.state.selection.$from.nodeAfter?.text || "";
-        }
-        return "";
-    }
-
     @action
     public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
         if (!view) {
@@ -310,8 +313,11 @@ export default class RichTextMenu extends AntimodeMenu {
         function onClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && command && command(self.view.state, self.view.dispatch, self.view);
-            self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
+            self.TextView.endUndoTypingBatch();
+            UndoManager.RunInBatch(() => {
+                self.view && command && command(self.view.state, self.view.dispatch, self.view);
+                self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
+            }, "rich text menu command");
             self.setActiveMarkButtons(self.getActiveMarksOnSelection());
         }
 
@@ -338,9 +344,10 @@ export default class RichTextMenu extends AntimodeMenu {
         function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
             e.stopPropagation();
             e.preventDefault();
+            self.TextView.endUndoTypingBatch();
             options.forEach(({ label, mark, command }) => {
                 if (e.target.value === label) {
-                    self.view && mark && command(mark, self.view);
+                    UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
                 }
             });
         }
@@ -361,9 +368,10 @@ export default class RichTextMenu extends AntimodeMenu {
 
         const self = this;
         function onChange(val: string) {
+            self.TextView.endUndoTypingBatch();
             options.forEach(({ label, node, command }) => {
                 if (val === label) {
-                    self.view && node && command(node);
+                    UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
                 }
             });
         }
@@ -461,7 +469,8 @@ export default class RichTextMenu extends AntimodeMenu {
         function onBrushClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.fillBrush(self.view.state, self.view.dispatch);
+            self.TextView.endUndoTypingBatch();
+            UndoManager.RunInBatch(() => self.view && self.fillBrush(self.view.state, self.view.dispatch), "rt brush");
         }
 
         let label = "Stored marks: ";
@@ -528,19 +537,24 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
     @action setActiveColor(color: string) { this.activeFontColor = color; }
+    get TextView() { return (this.view as any).TextView as FormattedTextBox }
 
     createColorButton() {
         const self = this;
         function onColorClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+            self.TextView.endUndoTypingBatch();
+            UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
+            self.TextView.EditorView!.focus()
         }
         function changeColor(e: React.PointerEvent, color: string) {
             e.preventDefault();
             e.stopPropagation();
             self.setActiveColor(color);
-            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+            self.TextView.endUndoTypingBatch();
+            UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
+            self.TextView.EditorView!.focus()
         }
 
         const button =
@@ -585,13 +599,15 @@ export default class RichTextMenu extends AntimodeMenu {
         function onHighlightClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+            self.TextView.endUndoTypingBatch();
+            UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highligher");
         }
         function changeHighlight(e: React.PointerEvent, color: string) {
             e.preventDefault();
             e.stopPropagation();
             self.setActiveHighlight(color);
-            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+            self.TextView.endUndoTypingBatch();
+            UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
         }
 
         const button =
@@ -631,7 +647,8 @@ export default class RichTextMenu extends AntimodeMenu {
         const self = this;
 
         function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
-            self.setCurrentLink(e.target.value);
+            self.TextView.endUndoTypingBatch();
+            UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change");
         }
 
         const link = this.currentLink ? this.currentLink : "";
@@ -766,7 +783,7 @@ export default class RichTextMenu extends AntimodeMenu {
         return ref_node;
     }
 
-    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; }
+    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
     @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 3712228f853e80ff05eced253a0bf197bdf47bde Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Jul 2020 12:26:54 -0400
Subject: added several new commands to RichTextMenu

---
 .../views/nodes/formattedText/RichTextMenu.tsx     | 73 +++++++++++++++++++++-
 1 file changed, 70 insertions(+), 3 deletions(-)

(limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')

diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 7c7a76e1b..6fdcaceda 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,6 +1,6 @@
 import React = require("react");
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, observable, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
@@ -26,7 +26,7 @@ import { TraceMobx } from "../../../../fields/util";
 import { UndoManager } from "../../../util/UndoManager";
 const { toggleMark } = require("prosemirror-commands");
 
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
+library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
 
 
 @observer
@@ -443,6 +443,63 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
 
+    insetParagraph(state: EditorState<any>, dispatch: any) {
+        var tr = state.tr;
+        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+            if (node.type === schema.nodes.paragraph) {
+                const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
+                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
+                return false;
+            }
+            return true;
+        });
+        dispatch?.(tr);
+        return true;
+    }
+    outsetParagraph(state: EditorState<any>, dispatch: any) {
+        var tr = state.tr;
+        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+            if (node.type === schema.nodes.paragraph) {
+                const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
+                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
+                return false;
+            }
+            return true;
+        });
+        dispatch?.(tr);
+        return true;
+    }
+
+    indentParagraph(state: EditorState<any>, dispatch: any) {
+        var tr = state.tr;
+        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+            if (node.type === schema.nodes.paragraph) {
+                const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
+                const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
+                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+                return false;
+            }
+            return true;
+        });
+        dispatch?.(tr);
+        return true;
+    }
+
+    hangingIndentParagraph(state: EditorState<any>, dispatch: any) {
+        var tr = state.tr;
+        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+            if (node.type === schema.nodes.paragraph) {
+                const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
+                const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
+                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+                return false;
+            }
+            return true;
+        });
+        dispatch?.(tr);
+        return true;
+    }
+
     insertBlockquote(state: EditorState<any>, dispatch: any) {
         const path = (state.selection.$from as any).path;
         if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
@@ -453,6 +510,11 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
 
+    insertHorizontalRule(state: EditorState<any>, dispatch: any) {
+        dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView());
+        return true;
+    }
+
     @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
 
     // todo: add brushes to brushMap to save with a style name
@@ -822,6 +884,10 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("align-left", "Align Left", undefined, this.alignLeft),
             this.createButton("align-center", "Align Center", undefined, this.alignCenter),
             this.createButton("align-right", "Align Right", undefined, this.alignRight),
+            this.createButton("indent", "Inset More", undefined, this.insetParagraph),
+            this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
+            this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
+            this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
         ]}</div>;
 
         const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
@@ -831,7 +897,8 @@ export default class RichTextMenu extends AntimodeMenu {
                 this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
                 this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"),
                 this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
-                this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),]}
+                this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
+                this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),]}
             </div>
             <div key="button">
                 {/* <div key="collapser">
-- 
cgit v1.2.3-70-g09d2

