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


From 54e38339b5292c3eddca0ef9665ec0c2b4d30ba2 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Jul 2020 14:20:53 -0400
Subject: added divider lines in richTextMen

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 --
 src/client/views/nodes/formattedText/RichTextMenu.scss    | 6 ++++++
 src/client/views/nodes/formattedText/RichTextMenu.tsx     | 7 ++++++-
 3 files changed, 12 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2a79e2f13..7e5509e85 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1049,7 +1049,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @action
     onFocused = (e: React.FocusEvent): void => {
-        console.log("FOUCSS")
         FormattedTextBox.FocusedBox = this;
         this.tryUpdateHeight();
 
@@ -1181,7 +1180,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
-        console.log("BLURRR")
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
         //DictationManager.Controls.stop(false);
         this.endUndoTypingBatch();
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index 7a0718c16..fbc468292 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -77,6 +77,12 @@
             color: white;
         }
     }
+    .richTextMenu-divider {
+        margin: auto;
+        border-left: solid #ffffff70 0.5px;
+        height: 20px;
+        width: 1px;
+    }
 }
 
 .link-menu {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 6fdcaceda..57719a8eb 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -876,11 +876,13 @@ export default class RichTextMenu extends AntimodeMenu {
                 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)),
+                <div className="richTextMenu-divider" />
             ]}</>,
             this.createColorButton(),
             this.createHighlighterButton(),
             this.createLinkButton(),
             this.createBrushButton(),
+            <div className="richTextMenu-divider" />,
             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),
@@ -893,12 +895,15 @@ export default class RichTextMenu extends AntimodeMenu {
         const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
             {this.collapsed ? this.getDragger() : (null)}
             <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
+                <div className="richTextMenu-divider" />,
                 {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
                 this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
+                <div className="richTextMenu-divider" />,
                 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("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),]}
+                this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
+                <div className="richTextMenu-divider" />,]}
             </div>
             <div key="button">
                 {/* <div key="collapser">
-- 
cgit v1.2.3-70-g09d2


From eff0c84e6f42bd6be1c58f1009d8d766bcf0a819 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Jul 2020 15:42:22 -0400
Subject: fixed warnings

---
 src/client/util/CurrentUserUtils.ts                       |  2 +-
 src/client/views/collections/CollectionSchemaView.tsx     |  8 ++++----
 src/client/views/collections/CollectionSubView.tsx        |  1 -
 src/client/views/collections/CollectionViewChromes.tsx    |  6 +++---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 +++++---------
 src/client/views/nodes/formattedText/RichTextMenu.tsx     |  8 ++++----
 src/fields/Doc.ts                                         |  2 +-
 7 files changed, 18 insertions(+), 23 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ef216001c..40bc24917 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -368,7 +368,7 @@ export class CurrentUserUtils {
             doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300 });
         }
         if (doc.emptyScript === undefined) {
-            doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script" })
+            doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script" });
         }
         if (doc.emptyDocHolder === undefined) {
             doc.emptyDocHolder = Docs.Create.DocumentDocument(
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 23e8a4d49..3c42a2f1c 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -581,14 +581,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     onZoomMenu = (e: React.WheelEvent) => {
         this.props.active(true) && e.stopPropagation();
         if (this.menuCoordinates[0] > e.screenX) {
-            this.menuCoordinates[0] -= e.screenX //* this.scale;
+            this.menuCoordinates[0] -= e.screenX; //* this.scale;
         } else {
-            this.menuCoordinates[0] += e.screenX //* this.scale;
+            this.menuCoordinates[0] += e.screenX; //* this.scale;
         }
         if (this.menuCoordinates[1] > e.screenY) {
-            this.menuCoordinates[1] -= e.screenY //* this.scale;
+            this.menuCoordinates[1] -= e.screenY; //* this.scale;
         } else {
-            this.menuCoordinates[1] += e.screenY //* this.scale;
+            this.menuCoordinates[1] += e.screenY; //* this.scale;
         }
     }
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ecd16d22f..ed8535ecb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,6 @@ import { ScriptField } from "../../../fields/ScriptField";
 import { WebField } from "../../../fields/URLField";
 import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types";
 import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { Upload } from "../../../server/SharedMediaTypes";
 import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Networking } from "../../Network";
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index ab0df88f8..4e91a2928 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -452,9 +452,9 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
 
     @action toggleSort = () => {
         this.props.CollectionView.props.Document._columnsSort =
-        this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
-            this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
-    };
+            this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
+                this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+    }
     @action resetValue = () => { this._currentKey = this.pivotField; };
 
     render() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 7e5509e85..664141607 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -68,15 +68,10 @@ export interface FormattedTextBoxProps {
     xMargin?: number;   // used to override document's settings for xMargin --- see CollectionCarouselView
     yMargin?: number;
 }
-
-const richTextSchema = createSchema({
-    documentText: "string",
-});
-
 export const GoogleRef = "googleDocId";
 
-type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>;
-const RichTextDocument = makeInterface(richTextSchema, documentSchema);
+type RichTextDocument = makeInterface<[typeof documentSchema]>;
+const RichTextDocument = makeInterface(documentSchema);
 
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
@@ -661,7 +656,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             incomingValue => {
                 if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
                     const updatedState = JSON.parse(incomingValue);
-                    if (JSON.stringify(this._editorView!.state.toJSON()) !== JSON.stringify(updatedState)) {
+                    if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
                         this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
                         this.tryUpdateHeight();
                     }
@@ -1293,7 +1288,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
                         pointerEvents: interactive ? undefined : "none",
                         fontSize: Cast(this.layoutDoc._fontSize, "number", null),
-                        fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit")
+                        fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
+                        transition: "opacity 1s"
                     }}
                     onContextMenu={this.specificContextMenu}
                     onKeyDown={this.onKeyPress}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 57719a8eb..0fcddfc43 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -149,7 +149,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this._reaction?.();
     }
 
-    public delayHide = () => { this._delayHide = true; }
+    public delayHide = () => this._delayHide = true;
 
     @action
     changeView(view: EditorView) {
@@ -599,7 +599,7 @@ 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 }
+    get TextView() { return (this.view as any).TextView as FormattedTextBox; }
 
     createColorButton() {
         const self = this;
@@ -608,7 +608,7 @@ export default class RichTextMenu extends AntimodeMenu {
             e.stopPropagation();
             self.TextView.endUndoTypingBatch();
             UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
-            self.TextView.EditorView!.focus()
+            self.TextView.EditorView!.focus();
         }
         function changeColor(e: React.PointerEvent, color: string) {
             e.preventDefault();
@@ -616,7 +616,7 @@ export default class RichTextMenu extends AntimodeMenu {
             self.setActiveColor(color);
             self.TextView.endUndoTypingBatch();
             UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
-            self.TextView.EditorView!.focus()
+            self.TextView.EditorView!.focus();
         }
 
         const button =
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e4d11dd4d..4a35ef018 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -965,7 +965,7 @@ export namespace Doc {
                     container._docFilters = new List<string>(docFilters);
                 }
             }
-        })
+        });
     }
     export function readDocRangeFilter(doc: Doc, key: string) {
         const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []);
-- 
cgit v1.2.3-70-g09d2


From 17b9f3672fde08864343afd9808881dd23023643 Mon Sep 17 00:00:00 2001
From: Lionel Han <47760119+IGoByJoe@users.noreply.github.com>
Date: Wed, 1 Jul 2020 16:47:47 -0700
Subject: added color picker for highlighter

---
 .../views/nodes/formattedText/RichTextMenu.tsx     |  2 +-
 src/client/views/pdf/PDFMenu.scss                  | 19 ++++++
 src/client/views/pdf/PDFMenu.tsx                   | 68 ++++++++++++++++++++--
 3 files changed, 82 insertions(+), 7 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 0fcddfc43..b9ca82243 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -933,7 +933,7 @@ interface ButtonDropdownProps {
 }
 
 @observer
-class ButtonDropdown extends React.Component<ButtonDropdownProps> {
+export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
 
     @observable private showDropdown: boolean = false;
     private ref: HTMLDivElement | null = null;
diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss
index 3c08ba80d..fa43a99b2 100644
--- a/src/client/views/pdf/PDFMenu.scss
+++ b/src/client/views/pdf/PDFMenu.scss
@@ -3,4 +3,23 @@
     width: 200px;
     padding: 5px;
     grid-template-columns: 90px 20px 90px;
+}
+
+.color-wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+
+    button.color-button {
+        width: 20px;
+        height: 20px;
+        border-radius: 15px !important;
+        margin: 3px;
+        border: 2px solid transparent !important;
+        padding: 3px;
+
+        &.active {
+            border: 2px solid white;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 6dcf5cce6..00c56d73e 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -1,22 +1,43 @@
 import React = require("react");
 import "./PDFMenu.scss";
-import { observable, action, } from "mobx";
+import { observable, action, computed, } from "mobx";
 import { observer } from "mobx-react";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { unimplementedFunction, returnFalse } from "../../../Utils";
+import { unimplementedFunction, returnFalse, Utils } from "../../../Utils";
 import AntimodeMenu from "../AntimodeMenu";
 import { Doc, Opt } from "../../../fields/Doc";
+import { ColorState } from "react-color";
+import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
+
 
 @observer
 export default class PDFMenu extends AntimodeMenu {
     static Instance: PDFMenu;
 
     private _commentCont = React.createRef<HTMLButtonElement>();
+    private _palette = [
+        "rgba(208, 2, 27, 0.8)",
+        "rgba(238, 0, 0, 0.8)",
+        "rgba(245, 166, 35, 0.8)",
+        "rgba(248, 231, 28, 0.8)",
+        "rgba(245, 230, 95, 0.616)",
+        "rgba(139, 87, 42, 0.8)",
+        "rgba(126, 211, 33, 0.8)",
+        "rgba(65, 117, 5, 0.8)",
+        "rgba(144, 19, 254, 0.8)",
+        "rgba(238, 169, 184, 0.8)",
+        "rgba(224, 187, 228, 0.8)",
+        "rgba(225, 223, 211, 0.8)",
+        "rgba(255, 255, 255, 0.8)",
+        "rgba(155, 155, 155, 0.8)",
+        "rgba(0, 0, 0, 0.8)"];
 
     @observable private _keyValue: string = "";
     @observable private _valueValue: string = "";
     @observable private _added: boolean = false;
+    @observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
 
+    @observable public _colorBtn = false;
     @observable public Highlighting: boolean = false;
     @observable public Status: "pdf" | "annotation" | "" = "";
 
@@ -70,11 +91,47 @@ export default class PDFMenu extends AntimodeMenu {
 
     @action
     highlightClicked = (e: React.MouseEvent) => {
-        if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { // yellowish highlight color for a marker type highlight
+        if (!this.Highlight(this.highlightColor) && this.Pinned) {
             this.Highlighting = !this.Highlighting;
         }
     }
 
+    @computed get highlighter() {
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={this.highlightClicked}>
+                <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
+                <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>Change highlighter color:</p>
+                <div className="color-wrapper">
+                    {this._palette.map(color => {
+                        if (color) {
+                            return this.highlightColor === color ?
+                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button> :
+                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button>;
+                        }
+                    })}
+                </div>
+            </div>;
+        return (
+            <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} />
+        );
+    }
+
+    @action
+    changeHighlightColor = (color: string, e: React.PointerEvent) => {
+        const col: ColorState = {
+            hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
+            rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
+        };
+        e.preventDefault();
+        e.stopPropagation();
+        this.highlightColor = Utils.colorString(col);
+    }
+
     deleteClicked = (e: React.PointerEvent) => {
         this.Delete();
     }
@@ -101,12 +158,11 @@ export default class PDFMenu extends AntimodeMenu {
     render() {
         const buttons = this.Status === "pdf" ?
             [
-                <button key="1" className="antimodeMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
-                    <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>,
+                this.highlighter,
                 <button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
                     <FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
                 <button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
-                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button>
+                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /></button>,
             ] : [
                 <button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
                     <FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
-- 
cgit v1.2.3-70-g09d2


From fcbf3a161839111ac609ec737cd56c71efde18a1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Jul 2020 21:13:10 -0400
Subject: added basic synchronizatoin between links and their hypertext
 anchors.  more work is needed to handle overlapping anchors.

---
 src/client/util/LinkManager.ts                     |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 39 ++++++++++++++-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 32 ++++---------
 .../nodes/formattedText/prosemirrorPatches.js      | 55 ++++++++++++++++++++++
 4 files changed, 103 insertions(+), 25 deletions(-)

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

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 9b4dc2630..749fabfcc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -49,7 +49,7 @@ export class LinkManager {
     }
 
     public deleteLink(linkDoc: Doc): boolean {
-        if (LinkManager.Instance.LinkManagerDoc) {
+        if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
             Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
             return true;
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 664141607..f32c17563 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -8,7 +8,7 @@ import { baseKeymap, selectAll } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { inputRules } from 'prosemirror-inputrules';
 import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Slice } from "prosemirror-model";
+import { Fragment, Mark, Node, Slice, Schema } from "prosemirror-model";
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
@@ -16,6 +16,7 @@ import { DateField } from '../../../../fields/DateField';
 import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import applyDevTools = require("prosemirror-dev-tools");
+import { removeMarkWithAttrs } from "./prosemirrorPatches";
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -56,7 +57,7 @@ import { DocumentButtonBar } from '../../DocumentButtonBar';
 import { AudioBox } from '../AudioBox';
 import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
+import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
 import React = require("react");
 
 library.add(faEdit);
@@ -87,6 +88,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     private _editorView: Opt<EditorView>;
     private _applyingChange: boolean = false;
     private _searchIndex = 0;
+    private _cachedLinks: Doc[] = [];
     private _undoTyping?: UndoManager.Batch;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -141,6 +143,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
 
+    // removes all hyperlink anchors for the removed linkDoc
+    // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. 
+    // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
+    public RemoveLinkFromDoc(linkDoc?: Doc) {
+        const state = this._editorView?.state;
+        if (state && linkDoc && this._editorView) {
+            var allHrefs: any[] = [];
+            state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
+                const foundMark = findLinkMark(node.marks);
+                const newHrefs = foundMark?.attrs.allHrefs.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
+                allHrefs = newHrefs.length ? newHrefs : allHrefs;
+                return true;
+            });
+            if (allHrefs.length) {
+                this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.link, { allHrefs }));
+            }
+        }
+    }
+    // removes all the specified link referneces from the selection. 
+    // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
+    public RemoveLinkFromSelection(allHrefs: { href: string, title: string, linkId: string, targetId: string }[]) {
+        const state = this._editorView?.state;
+        if (state && this._editorView) {
+            this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allHrefs }));
+        }
+    }
+
     linkOnDeselect: Map<string, string> = new Map();
 
     doLinkOnDeselect() {
@@ -626,6 +655,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
     componentDidMount() {
+        this._cachedLinks = DocListCast(this.Document.links);
+        this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+            newLinks => {
+                this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
+                this._cachedLinks = newLinks;
+            });
         this._disposers.buttonBar = reaction(
             () => DocumentButtonBar.Instance,
             instance => {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 0fcddfc43..6366f299d 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -772,28 +772,16 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     deleteLink = () => {
-        if (!this.view) return;
-
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
-        const href = link!.attrs.allHrefs.length > 0 ? link!.attrs.allHrefs[0].href : undefined;
-        if (href) {
-            if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                if (linkclicked) {
-                    DocServer.GetRefField(linkclicked).then(async linkDoc => {
-                        if (linkDoc instanceof Doc) {
-                            LinkManager.Instance.deleteLink(linkDoc);
-                            this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
-                        }
-                    });
-                }
-            } else {
-                if (node) {
-                    const { tr, schema, selection } = this.view.state;
-                    const extension = this.linkExtend(selection.$anchor, href);
-                    this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
-                }
+        if (this.view) {
+            const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.link);
+            if (link) {
+                const allHrefs = link.attrs.allHrefs.slice();
+                this.TextView.RemoveLinkFromSelection(link.attrs.allHrefs);
+                // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
+                allHrefs.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
+                    const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc))
+                });
             }
         }
     }
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 763961958..0969ea4ef 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -9,6 +9,7 @@ var prosemirrorModel = require('prosemirror-model');
 exports.liftListItem = liftListItem;
 exports.sinkListItem = sinkListItem;
 exports.wrappingInputRule = wrappingInputRule;
+exports.removeMarkWithAttrs = removeMarkWithAttrs;
 // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
 // Create a command to lift the list item around the selection up into
 // a wrapping list.
@@ -139,3 +140,57 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
 }
 
 
+// :: ([Mark]) → ?Mark
+// Tests whether there is a mark of this type in the given set.
+function isInSetWithAttrs(mark, set, attrs) {
+    for (var i = 0; i < set.length; i++) {
+        if (set[i].type == mark) {
+            if (Array.from(Object.keys(attrs)).reduce((p, akey) => {
+                return p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey]);
+            }, true)) {
+                return set[i];
+            }
+        }
+    }
+};
+
+// :: (number, number, ?union<Mark, MarkType>) → this
+// Remove marks from inline nodes between `from` and `to`. When `mark`
+// is a single mark, remove precisely that mark. When it is a mark type,
+// remove all marks of that type. When it is null, remove all marks of
+// any type.
+function removeMarkWithAttrs(tr, from, to, mark, attrs) {
+    if (mark === void 0) mark = null;
+
+    var matched = [], step = 0;
+    tr.doc.nodesBetween(from, to, function (node, pos) {
+        if (!node.isInline) { return }
+        step++;
+        var toRemove = null;
+        if (mark) {
+            if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; }
+        } else {
+            toRemove = node.marks;
+        }
+        if (toRemove && toRemove.length) {
+            var end = Math.min(pos + node.nodeSize, to);
+            for (var i = 0; i < toRemove.length; i++) {
+                var style = toRemove[i], found$1 = (void 0);
+                for (var j = 0; j < matched.length; j++) {
+                    var m = matched[j];
+                    if (m.step == step - 1 && style.eq(matched[j].style)) { found$1 = m; }
+                }
+                if (found$1) {
+                    found$1.to = end;
+                    found$1.step = step;
+                } else {
+                    matched.push({ style: style, from: Math.max(pos, from), to: end, step: step });
+                }
+            }
+        }
+    });
+    matched.forEach(function (m) { return tr.step(new prosemirrorTransform.RemoveMarkStep(m.from, m.to, m.style)); });
+    return tr
+};
+
+
-- 
cgit v1.2.3-70-g09d2


From 19a48bfe9b2087d23ab6bbb384f8edc18ed07d81 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Jul 2020 21:26:35 -0400
Subject: renamed link to linkAnchor and allHrefs to allLinks in rich text code

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 36 +++++++++++-----------
 .../formattedText/FormattedTextBoxComment.tsx      |  4 +--
 .../views/nodes/formattedText/RichTextMenu.tsx     | 18 +++++------
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 src/client/views/nodes/formattedText/marks_rts.ts  | 20 ++++++------
 src/fields/RichTextUtils.ts                        |  2 +-
 6 files changed, 41 insertions(+), 41 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f32c17563..9c7c46ae9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -149,24 +149,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     public RemoveLinkFromDoc(linkDoc?: Doc) {
         const state = this._editorView?.state;
         if (state && linkDoc && this._editorView) {
-            var allHrefs: any[] = [];
+            var allLinks: any[] = [];
             state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
                 const foundMark = findLinkMark(node.marks);
-                const newHrefs = foundMark?.attrs.allHrefs.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
-                allHrefs = newHrefs.length ? newHrefs : allHrefs;
+                const newHrefs = foundMark?.attrs.allLinks.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
+                allLinks = newHrefs.length ? newHrefs : allLinks;
                 return true;
             });
-            if (allHrefs.length) {
-                this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.link, { allHrefs }));
+            if (allLinks.length) {
+                this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allLinks }));
             }
         }
     }
     // removes all the specified link referneces from the selection. 
     // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
-    public RemoveLinkFromSelection(allHrefs: { href: string, title: string, linkId: string, targetId: string }[]) {
+    public RemoveLinkFromSelection(allLinks: { href: string, title: string, linkId: string, targetId: string }[]) {
         const state = this._editorView?.state;
         if (state && this._editorView) {
-            this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allHrefs }));
+            this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allLinks }));
         }
     }
 
@@ -206,8 +206,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.linkOnDeselect.set(key, value);
 
                     const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-                    const allHrefs = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
-                    const link = this._editorView.state.schema.marks.link.create({ allHrefs, location: "onRight", title: value });
+                    const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
+                    const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "onRight", title: value });
                     const mval = this._editorView.state.schema.marks.metadataVal.create();
                     const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
                     tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -275,8 +275,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const lastSel = Math.min(flattened.length - 1, this._searchIndex);
             this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
             const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
-            const allHrefs = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
-            const link = this._editorView.state.schema.marks.link.create({ allHrefs, title: "a link", location });
+            const allLinks = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
+            const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, title: "a link", location });
             this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
         }
     }
@@ -643,9 +643,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             let tr = state.tr.addMark(sel.from, sel.to, splitter);
             sel.from !== sel.to && tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
                 if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
-                    const allHrefs = [{ href, title, targetId, linkId }];
-                    allHrefs.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.link.name)?.attrs.allHrefs ?? []));
-                    const link = state.schema.marks.link.create({ allHrefs, title, location, linkId });
+                    const allLinks = [{ href, title, targetId, linkId }];
+                    allLinks.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allLinks ?? []));
+                    const link = state.schema.marks.linkAnchor.create({ allLinks, title, location, linkId });
                     tr = tr.addMark(pos, pos + node.nodeSize, link);
                 }
             });
@@ -766,8 +766,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         return node.copy(content.frag);
                     }
                     const marks = [...node.marks];
-                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
-                    return linkIndex !== -1 && marks[linkIndex].attrs.allHrefs.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
+                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
+                    return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
                 };
 
                 let start = 0;
@@ -949,8 +949,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type.name === "link");
-            const allHrefs = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
-            const link = view.state.schema.mark(view.state.schema.marks.link, { allHrefs, location: "onRight", title, docref: true });
+            const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
+            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "onRight", title, docref: true });
             marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
             return node.mark(marks);
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 90f2c0aa6..4c90b6afd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -27,7 +27,7 @@ export function findUserMark(marks: Mark[]): Mark | undefined {
     return marks.find(m => m.attrs.userid);
 }
 export function findLinkMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.type === schema.marks.link);
+    return marks.find(m => m.type === schema.marks.linkAnchor);
 }
 export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
     let before = 0;
@@ -182,7 +182,7 @@ export class FormattedTextBoxComment {
             state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             child = child || (nbef && state.selection.$from.nodeBefore);
             const mark = child ? findLinkMark(child.marks) : undefined;
-            const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allHrefs.find((item: { href: string }) => item.href)?.href || forceUrl;
+            const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href || forceUrl;
             if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
                 FormattedTextBoxComment.tooltipText.textContent = "external => " + href;
                 (FormattedTextBoxComment.tooltipText as any).href = href;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 6366f299d..3ec75e680 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -737,7 +737,7 @@ export default class RichTextMenu extends AntimodeMenu {
         const node = this.view.state.selection.$from.nodeAfter;
         const link = node && node.marks.find(m => m.type.name === "link");
         if (link) {
-            const href = link.attrs.allHrefs.length > 0 ? link.attrs.allHrefs[0].href : undefined;
+            const href = link.attrs.allLinks.length > 0 ? link.attrs.allLinks[0].href : undefined;
             if (href) {
                 if (href.indexOf(Utils.prepend("/doc/")) === 0) {
                     const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -773,27 +773,27 @@ export default class RichTextMenu extends AntimodeMenu {
 
     deleteLink = () => {
         if (this.view) {
-            const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.link);
+            const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
             if (link) {
-                const allHrefs = link.attrs.allHrefs.slice();
-                this.TextView.RemoveLinkFromSelection(link.attrs.allHrefs);
+                const allLinks = link.attrs.allLinks.slice();
+                this.TextView.RemoveLinkFromSelection(link.attrs.allLinks);
                 // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
-                allHrefs.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
+                allLinks.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
                     const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc))
+                    linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
                 });
             }
         }
     }
 
     linkExtend($start: ResolvedPos, href: string) {
-        const mark = this.view!.state.schema.marks.link;
+        const mark = this.view!.state.schema.marks.linkAnchor;
 
         let startIndex = $start.index();
         let endIndex = $start.indexAfter();
 
-        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) startIndex--;
-        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) endIndex++;
+        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) startIndex--;
+        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) endIndex++;
 
         let startPos = $start.start();
         let endPos = startPos;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 612a824fc..ca30dde9d 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -279,7 +279,7 @@ export class RichTextRules {
                                 DocUtils.Publish(target, docid, returnFalse, returnFalse);
                                 DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
                             });
-                            const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+                            const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
                             return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
                         }
                         return state.tr;
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 54b61aa20..3d7d71b14 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -17,12 +17,12 @@ export const marks: { [index: string]: MarkSpec } = {
             return ["div", { className: "dummy" }, 0];
         }
     },
-    // :: MarkSpec A link. Has `href` and `title` attributes. `title`
+    // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title`
     // defaults to the empty string. Rendered and parsed as an `<a>`
     // element.
-    link: {
+    linkAnchor: {
         attrs: {
-            allHrefs: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
+            allLinks: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
             showPreview: { default: true },
             location: { default: null },
             title: { default: null },
@@ -31,22 +31,22 @@ export const marks: { [index: string]: MarkSpec } = {
         inclusive: false,
         parseDOM: [{
             tag: "a[href]", getAttrs(dom: any) {
-                return { allHrefs: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), };
+                return { allLinks: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), };
             }
         }],
         toDOM(node: any) {
-            const targetids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
-            const linkids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
+            const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
+            const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
             return node.attrs.docref && node.attrs.title ?
-                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allHrefs[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
-                node.attrs.allHrefs.length === 1 ?
-                    ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allHrefs[0].href }, 0] :
+                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
+                node.attrs.allLinks.length === 1 ?
+                    ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] :
                     ["div", { class: "prosemirror-anchor" },
                         ["span", { class: "prosemirror-linkBtn" },
                             ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}` }, 0],
                             ["input", { class: "prosemirror-hrefoptions" }],
                         ],
-                        ["div", { class: "prosemirror-links" }, ...node.attrs.allHrefs.map((item: { href: string, title: string }) =>
+                        ["div", { class: "prosemirror-links" }, ...node.attrs.allLinks.map((item: { href: string, title: string }) =>
                             ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
                         )]
                     ];
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 7c7bf3e12..a590c88c4 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -392,7 +392,7 @@ export namespace RichTextUtils {
                     const { attrs } = mark;
                     switch (converted) {
                         case "link":
-                            let url = attrs.allHrefs.length ? attrs.allHrefs[0].href : "";
+                            let url = attrs.allLinks.length ? attrs.allLinks[0].href : "";
                             const delimiter = "/doc/";
                             const alreadyShared = "?sharing=true";
                             if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) {
-- 
cgit v1.2.3-70-g09d2


From 6b24899bcf2c099163c1ca872d65b6318c11a53b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 4 Jul 2020 12:25:09 -0400
Subject: fixed highlighting active alignment and bullet type in richtextmenu

---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 .../formattedText/ProsemirrorExampleTransfer.ts    | 10 ++--
 .../views/nodes/formattedText/RichTextMenu.tsx     | 57 ++++++++++++++++------
 3 files changed, 49 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 8e3087672..fc63dfbf5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1116,7 +1116,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @action
     onFocused = (e: React.FocusEvent): void => {
-        console.log("FOUCSS");
         FormattedTextBox.FocusedBox = this;
         this.tryUpdateHeight();
 
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 9d69f4be7..3f73ec436 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -16,13 +16,17 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
 
 export type KeyMap = { [key: string]: any };
 
-export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string, from?: number, to?: number) => {
+export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
+    let mapStyle = assignedMapStyle;
     tx2.doc.descendants((node: any, offset: any, index: any) => {
         if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
             const path = (tx2.doc.resolve(offset) as any).path;
             let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
-            if (node.type === schema.nodes.ordered_list) depth++;
-            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle || node.attrs.mapStyle, bulletStyle: depth, }, node.marks);
+            if (node.type === schema.nodes.ordered_list) {
+                if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
+                depth++;
+            }
+            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth, }, node.marks);
         }
     });
     return tx2;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 95d6c9fac..9075a6486 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -55,6 +55,7 @@ export default class RichTextMenu extends AntimodeMenu {
     @observable private activeFontSize: string = "";
     @observable private activeFontFamily: string = "";
     @observable private activeListType: string = "";
+    @observable private activeAlignment: string = "left";
 
     @observable private brushIsEmpty: boolean = true;
     @observable private brushMarks: Set<Mark> = new Set();
@@ -91,7 +92,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            { mark: null, title: "", label: "...", command: unimplementedFunction, hidden: true },
             { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
         ];
 
@@ -110,7 +111,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this.listTypeOptions = [
             { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
             { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType },
+            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType },
             //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
         ];
 
@@ -178,11 +179,13 @@ export default class RichTextMenu extends AntimodeMenu {
 
         // update active font family and size
         const active = this.getActiveFontStylesOnSelection();
-        const activeFamilies = active?.get("families");
-        const activeSizes = active?.get("sizes");
+        const activeFamilies = active.activeFamilies;
+        const activeSizes = active.activeSizes;
 
-        this.activeFontFamily = !activeFamilies?.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes?.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "various";
+        this.activeListType = this.getActiveListStyle();
+        this.activeAlignment = this.getActiveAlignment();
+        this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "...";
 
         // update link in current selection
         const targetTitle = await this.getTextLinkTargetTitle();
@@ -212,9 +215,35 @@ export default class RichTextMenu extends AntimodeMenu {
         }
     }
 
+    // finds font sizes and families in selection
+    getActiveAlignment() {
+        if (this.view) {
+            const path = (this.view.state.selection.$from as any).path;
+            for (let i = path.length - 3; i < path.length; i -= 3) {
+                if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
+                    return path[i].attrs.align || "left";
+                }
+            }
+        }
+        return "left";
+    }
+
+    // finds font sizes and families in selection
+    getActiveListStyle() {
+        if (this.view) {
+            const path = (this.view.state.selection.$from as any).path;
+            for (let i = 0; i < path.length; i += 3) {
+                if (path[i].type === this.view.state.schema.nodes.ordered_list) {
+                    return path[i].attrs.mapStyle;
+                }
+            }
+        }
+        return "decimal";
+    }
+
     // finds font sizes and families in selection
     getActiveFontStylesOnSelection() {
-        if (!this.view) return;
+        if (!this.view) return { activeFamilies: [], activeSizes: [] };
 
         const activeFamilies: string[] = [];
         const activeSizes: string[] = [];
@@ -228,10 +257,7 @@ export default class RichTextMenu extends AntimodeMenu {
             });
         }
 
-        const styles = new Map<String, String[]>();
-        styles.set("families", activeFamilies);
-        styles.set("sizes", activeSizes);
-        return styles;
+        return { activeFamilies, activeSizes };
     }
 
     getMarksInSelection(state: EditorState<any>) {
@@ -354,7 +380,8 @@ export default class RichTextMenu extends AntimodeMenu {
         return <select onChange={onChange} key={key}>{items}</select>;
     }
 
-    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+    createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+        const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : "A.1";
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
@@ -871,9 +898,9 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createLinkButton(),
             this.createBrushButton(),
             <div className="richTextMenu-divider" />,
-            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("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
+            this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
+            this.createButton("align-right", "Align Right", this.activeAlignment === "right", 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),
-- 
cgit v1.2.3-70-g09d2

