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

---
 .../views/nodes/formattedText/RichTextMenu.tsx     | 61 ++++++++++++++--------
 1 file changed, 39 insertions(+), 22 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 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

