From 80d4bf70cb8e29f15d8e51b1e0bd378a26d68fcf Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 03:07:18 -0500
Subject: converted tool tip text menu to react component with basic marks and
 dropdown

---
 src/client/util/RichTextMenu.tsx | 337 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 337 insertions(+)
 create mode 100644 src/client/util/RichTextMenu.tsx

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
new file mode 100644
index 000000000..7e20d126c
--- /dev/null
+++ b/src/client/util/RichTextMenu.tsx
@@ -0,0 +1,337 @@
+import React = require("react");
+import AntimodeMenu from "../views/AntimodeMenu";
+import { observable, action, } from "mobx";
+import { observer } from "mobx-react";
+import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
+import { schema } from "./RichTextSchema";
+import { EditorView } from "prosemirror-view";
+import { EditorState, NodeSelection } from "prosemirror-state";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript } from "@fortawesome/free-solid-svg-icons";
+import { MenuItem, Dropdown } from "prosemirror-menu";
+import { updateBullets } from "./ProsemirrorExampleTransfer";
+import { FieldViewProps } from "../views/nodes/FieldView";
+import { NumCast } from "../../new_fields/Types";
+import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
+import { unimplementedFunction } from "../../Utils";
+const { toggleMark, setBlockType } = require("prosemirror-commands");
+
+library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript);
+
+@observer
+export default class RichTextMenu extends AntimodeMenu {
+    static Instance: RichTextMenu;
+
+    private view?: EditorView;
+    private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+
+    private _marksToDoms: Map<Mark, JSX.Element> = new Map();
+
+    @observable private activeFontSize: string = "";
+    @observable private activeFontFamily: string = "";
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        RichTextMenu.Instance = this;
+    }
+
+    @action
+    changeView(view: EditorView) {
+        this.view = view;
+    }
+
+    // update() {
+    //     console.log("update");
+    // }
+
+    update(view: EditorView, lastState: EditorState | undefined) {
+        console.log("update");
+        this.updateFromDash(view, lastState, this.editorProps);
+    }
+
+    @action
+    public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
+        if (!view) {
+            console.log("no editor?  why?");
+            return;
+        }
+        this.view = view;
+        const state = view.state;
+        console.log("update from dash");
+        // DocumentDecorations.Instance.showTextBar();
+        props && (this.editorProps = props);
+        // // Don't do anything if the document/selection didn't change
+        // if (lastState && lastState.doc.eq(state.doc) &&
+        //     lastState.selection.eq(state.selection)) return;
+
+        // this.reset_mark_doms();
+
+        // // update link dropdown
+        // const linkDropdown = await this.createLinkDropdown();
+        // const newLinkDropdowndom = linkDropdown.render(this.view).dom;
+        // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom);
+        // this._linkDropdownDom = newLinkDropdowndom;
+
+        // update active font family and size
+        const active = this.getActiveFontStylesOnSelection();
+        const activeFamilies = active && active.get("families");
+        const activeSizes = active && active.get("sizes");
+
+        this.activeFontFamily = !activeFamilies || activeFamilies.length == 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes || activeSizes.length == 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
+
+        // this.update_mark_doms();
+    }
+
+
+    destroy() {
+        console.log("destroy");
+    }
+
+    createButton(faIcon: string, title: string, command: any) {
+        const self = this;
+        function onClick(e: React.PointerEvent) {
+            // dom.addEventListener("pointerdown", e => {
+            e.preventDefault();
+            self.view && self.view.focus();
+            //     if (dom.contains(e.target as Node)) {
+            e.stopPropagation();
+            //         command(this.view.state, this.view.dispatch, this.view);
+            //     }
+            // });
+            self.view && command(self.view!.state, self.view!.dispatch, self.view);
+        }
+
+        return (
+            <button className="antimodeMenu-button" title="title" onPointerDown={onClick}>
+                <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
+            </button>
+        );
+    }
+
+    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean }[]): JSX.Element {
+        let items = options.map(({ title, label, hidden }) => {
+            if (hidden) {
+                return label === activeOption ?
+                    <option value={label} title={title} selected hidden>{label}</option> :
+                    <option value={label} title={title} hidden>{label}</option>;
+            }
+            return label === activeOption ?
+                <option value={label} title={title} selected>{label}</option> :
+                <option value={label} title={title}>{label}</option>;
+        });
+
+        const self = this;
+        function onChange(val: string) {
+            options.forEach(({ label, mark, command }) => {
+                if (val === label) {
+                    self.view && mark && command(mark, self.view);
+                }
+            });
+        }
+        return <select onChange={e => onChange(e.target.value)}>{items}</select>;
+
+        // let items: MenuItem[] = [];
+        // options.forEach(({ mark, title, label, command }) => {
+        //     const self = this;
+        //     function onSelect() {
+        //         self.view && command(mark, self.view);
+        //     }
+        //     // this.createMarksOption("Set font size", String(mark.attrs.fontSize), onSelect)
+        //     items.push(
+        //         new MenuItem({
+        //             title: title,
+        //             label: label,
+        //             execEvent: "",
+        //             class: "dropdown-item",
+        //             css: "",
+        //             run() { onSelect(); }
+        //         })
+        //     );
+        // });
+
+        // return <div>{(new Dropdown(items, { label: label }) as MenuItem).render(this.view!).dom}</div>;
+    }
+
+    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+        if (mark) {
+            const node = (state.selection as NodeSelection).node;
+            if (node?.type === schema.nodes.ordered_list) {
+                let attrs = node.attrs;
+                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
+                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
+                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
+                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
+                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
+            } else {
+                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
+                    const { from, $from, to, empty } = tx.selection;
+                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+                    } else dispatch(tx);
+                });
+            }
+        }
+    }
+
+    changeFontSize = (mark: Mark, view: EditorView) => {
+        console.log("change font size!!");
+        const size = mark.attrs.fontSize;
+        if (this.editorProps) {
+            const ruleProvider = this.editorProps.ruleProvider;
+            const heading = NumCast(this.editorProps.Document.heading);
+            if (ruleProvider && heading) {
+                ruleProvider["ruleSize_" + heading] = size;
+            }
+        }
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
+    }
+
+    changeFontFamily = (mark: Mark, view: EditorView) => {
+        const fontName = mark.attrs.family;
+        // if (fontName) { this.updateFontStyleDropdown(fontName); }
+        if (this.editorProps) {
+            const ruleProvider = this.editorProps.ruleProvider;
+            const heading = NumCast(this.editorProps.Document.heading);
+            if (ruleProvider && heading) {
+                ruleProvider["ruleFont_" + heading] = fontName;
+            }
+        }
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
+    }
+
+    // finds font sizes and families in selection
+    getActiveFontStylesOnSelection() {
+        if (!this.view) return;
+
+        const activeFamilies: string[] = [];
+        const activeSizes: string[] = [];
+        const state = this.view.state;
+        const pos = this.view.state.selection.$from;
+        const ref_node = this.reference_node(pos);
+        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+            ref_node.marks.forEach(m => {
+                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+            });
+        }
+
+        let styles = new Map<String, String[]>();
+        styles.set("families", activeFamilies);
+        styles.set("sizes", activeSizes);
+        return styles;
+    }
+
+    // changeToFontSize = (mark: Mark, view: EditorView) => {
+    //     const size = mark.attrs.fontSize;
+    //     if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+    //     if (this.editorProps) {
+    //         const ruleProvider = this.editorProps.ruleProvider;
+    //         const heading = NumCast(this.editorProps.Document.heading);
+    //         if (ruleProvider && heading) {
+    //             ruleProvider["ruleSize_" + heading] = size;
+    //         }
+    //     }
+    //     this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
+    // }
+
+    reference_node(pos: ResolvedPos<any>): ProsNode | null {
+        if (!this.view) return null;
+
+        let ref_node: ProsNode = this.view.state.doc;
+        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+            ref_node = pos.nodeBefore;
+        }
+        else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+            ref_node = pos.nodeAfter;
+        }
+        else if (pos.pos > 0) {
+            let skip = false;
+            for (let i: number = pos.pos - 1; i > 0; i--) {
+                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
+                    if (node.isLeaf && !skip) {
+                        ref_node = node;
+                        skip = true;
+                    }
+
+                });
+            }
+        }
+        if (!ref_node.isLeaf && ref_node.childCount > 0) {
+            ref_node = ref_node.child(0);
+        }
+        return ref_node;
+    }
+
+    render() {
+        console.log("render");
+        const fontSizeOptions = [
+            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
+            { 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: "default", command: unimplementedFunction, hidden: true },
+        ]
+
+        const fontFamilyOptions = [
+            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily },
+            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
+        ]
+
+        const buttons = [
+            this.createButton("bold", "Bold", toggleMark(schema.marks.strong)),
+            this.createButton("italic", "Italic", toggleMark(schema.marks.em)),
+            this.createButton("underline", "Underline", toggleMark(schema.marks.underline)),
+            this.createButton("strikethrough", "Strikethrough", toggleMark(schema.marks.strikethrough)),
+            this.createButton("superscript", "Superscript", toggleMark(schema.marks.superscript)),
+            this.createButton("subscript", "Subscript", toggleMark(schema.marks.subscript)),
+            this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
+            this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions),
+        ];
+
+        // this._marksToDoms = new Map();
+        // items.forEach(({ title, dom, command }) => {
+        //     // this.tooltip.appendChild(dom);
+        //     switch (title) {
+        //         case "Bold":
+        //             this._marksToDoms.set(schema.mark(schema.marks.strong), dom);
+        //             // this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
+        //             break;
+        //         case "Italic":
+        //             this._marksToDoms.set(schema.mark(schema.marks.em), dom);
+        //             // this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
+        //             break;
+        //     }
+
+        //     //pointer down handler to activate button effects
+        //     dom.addEventListener("pointerdown", e => {
+        //         e.preventDefault();
+        //         this.view.focus();
+        //         if (dom.contains(e.target as Node)) {
+        //             e.stopPropagation();
+        //             command(this.view.state, this.view.dispatch, this.view);
+        //         }
+        //     });
+        // });
+
+        return this.getElement(buttons);
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 7d58927ba9da5ea146ef6daed336036b01447302 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 05:23:53 -0500
Subject: added summarizing button to richtextmenu

---
 src/client/util/RichTextMenu.tsx | 215 ++++++++++++++++++++++++---------------
 1 file changed, 134 insertions(+), 81 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 7e20d126c..6b6a2620e 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -8,16 +8,17 @@ import { EditorView } from "prosemirror-view";
 import { EditorState, NodeSelection } from "prosemirror-state";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent } from "@fortawesome/free-solid-svg-icons";
 import { MenuItem, Dropdown } from "prosemirror-menu";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { NumCast } from "../../new_fields/Types";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
 import { unimplementedFunction } from "../../Utils";
+import { wrapInList } from "prosemirror-schema-list";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 
-library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript);
+library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent);
 
 @observer
 export default class RichTextMenu extends AntimodeMenu {
@@ -26,10 +27,9 @@ export default class RichTextMenu extends AntimodeMenu {
     private view?: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
 
-    private _marksToDoms: Map<Mark, JSX.Element> = new Map();
-
     @observable private activeFontSize: string = "";
     @observable private activeFontFamily: string = "";
+    @observable private activeListType: string = "";
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -41,12 +41,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this.view = view;
     }
 
-    // update() {
-    //     console.log("update");
-    // }
-
     update(view: EditorView, lastState: EditorState | undefined) {
-        console.log("update");
         this.updateFromDash(view, lastState, this.editorProps);
     }
 
@@ -58,7 +53,6 @@ export default class RichTextMenu extends AntimodeMenu {
         }
         this.view = view;
         const state = view.state;
-        console.log("update from dash");
         // DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
         // // Don't do anything if the document/selection didn't change
@@ -84,14 +78,57 @@ export default class RichTextMenu extends AntimodeMenu {
         // this.update_mark_doms();
     }
 
+    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+        if (mark) {
+            const node = (state.selection as NodeSelection).node;
+            if (node?.type === schema.nodes.ordered_list) {
+                let attrs = node.attrs;
+                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
+                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
+                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
+                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
+                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
+            } else {
+                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
+                    const { from, $from, to, empty } = tx.selection;
+                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+                    } else dispatch(tx);
+                });
+            }
+        }
+    }
+
+    // finds font sizes and families in selection
+    getActiveFontStylesOnSelection() {
+        if (!this.view) return;
+
+        const activeFamilies: string[] = [];
+        const activeSizes: string[] = [];
+        const state = this.view.state;
+        const pos = this.view.state.selection.$from;
+        const ref_node = this.reference_node(pos);
+        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+            ref_node.marks.forEach(m => {
+                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+            });
+        }
+
+        let styles = new Map<String, String[]>();
+        styles.set("families", activeFamilies);
+        styles.set("sizes", activeSizes);
+        return styles;
+    }
 
     destroy() {
         console.log("destroy");
     }
 
-    createButton(faIcon: string, title: string, command: any) {
+    createButton(faIcon: string, title: string, command?: any, onclick?: any) {
         const self = this;
         function onClick(e: React.PointerEvent) {
+            console.log("clicked button");
             // dom.addEventListener("pointerdown", e => {
             e.preventDefault();
             self.view && self.view.focus();
@@ -100,7 +137,8 @@ export default class RichTextMenu extends AntimodeMenu {
             //         command(this.view.state, this.view.dispatch, this.view);
             //     }
             // });
-            self.view && command(self.view!.state, self.view!.dispatch, self.view);
+            self.view && command && command(self.view!.state, self.view!.dispatch, self.view);
+            self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view);
         }
 
         return (
@@ -131,48 +169,29 @@ export default class RichTextMenu extends AntimodeMenu {
             });
         }
         return <select onChange={e => onChange(e.target.value)}>{items}</select>;
-
-        // let items: MenuItem[] = [];
-        // options.forEach(({ mark, title, label, command }) => {
-        //     const self = this;
-        //     function onSelect() {
-        //         self.view && command(mark, self.view);
-        //     }
-        //     // this.createMarksOption("Set font size", String(mark.attrs.fontSize), onSelect)
-        //     items.push(
-        //         new MenuItem({
-        //             title: title,
-        //             label: label,
-        //             execEvent: "",
-        //             class: "dropdown-item",
-        //             css: "",
-        //             run() { onSelect(); }
-        //         })
-        //     );
-        // });
-
-        // return <div>{(new Dropdown(items, { label: label }) as MenuItem).render(this.view!).dom}</div>;
     }
 
-    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
-        if (mark) {
-            const node = (state.selection as NodeSelection).node;
-            if (node?.type === schema.nodes.ordered_list) {
-                let attrs = node.attrs;
-                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
-                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
-                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
-                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
-            } else {
-                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
-                    const { from, $from, to, empty } = tx.selection;
-                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
-                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
-                    } else dispatch(tx);
-                });
+    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean }[]): JSX.Element {
+        let items = options.map(({ title, label, hidden }) => {
+            if (hidden) {
+                return label === activeOption ?
+                    <option value={label} title={title} selected hidden>{label}</option> :
+                    <option value={label} title={title} hidden>{label}</option>;
             }
+            return label === activeOption ?
+                <option value={label} title={title} selected>{label}</option> :
+                <option value={label} title={title}>{label}</option>;
+        });
+
+        const self = this;
+        function onChange(val: string) {
+            options.forEach(({ label, node, command }) => {
+                if (val === label) {
+                    self.view && node && command(node);
+                }
+            });
         }
+        return <select onChange={e => onChange(e.target.value)}>{items}</select>;
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
@@ -201,40 +220,44 @@ export default class RichTextMenu extends AntimodeMenu {
         this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
     }
 
-    // finds font sizes and families in selection
-    getActiveFontStylesOnSelection() {
+    // TODO: remove doesn't work
+    //remove all node type and apply the passed-in one to the selected text
+    changeListType = (nodeType: NodeType | undefined) => {
+        console.log("change the list type ");
         if (!this.view) return;
-
-        const activeFamilies: string[] = [];
-        const activeSizes: string[] = [];
-        const state = this.view.state;
-        const pos = this.view.state.selection.$from;
-        const ref_node = this.reference_node(pos);
-        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
-            ref_node.marks.forEach(m => {
-                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
-                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
-            });
+        console.log("change the list type has view");
+
+        if (nodeType === schema.nodes.bullet_list) {
+            wrapInList(nodeType)(this.view.state, this.view.dispatch);
+        } else {
+            const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
+            if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+
+                this.view!.dispatch(tx2);
+            })) {
+                const tx2 = this.view.state.tr;
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+
+                this.view.dispatch(tx3);
+            }
         }
-
-        let styles = new Map<String, String[]>();
-        styles.set("families", activeFamilies);
-        styles.set("sizes", activeSizes);
-        return styles;
     }
 
-    // changeToFontSize = (mark: Mark, view: EditorView) => {
-    //     const size = mark.attrs.fontSize;
-    //     if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-    //     if (this.editorProps) {
-    //         const ruleProvider = this.editorProps.ruleProvider;
-    //         const heading = NumCast(this.editorProps.Document.heading);
-    //         if (ruleProvider && heading) {
-    //             ruleProvider["ruleSize_" + heading] = size;
-    //         }
-    //     }
-    //     this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
-    // }
+    insertSummarizer(state: EditorState<any>, dispatch: any) {
+        if (state.selection.empty) return false;
+        const mark = state.schema.marks.summarize.create();
+        const tr = state.tr;
+        tr.addMark(state.selection.from, state.selection.to, mark);
+        const content = tr.selection.content();
+        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
+        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+        return true;
+    }
 
     reference_node(pos: ResolvedPos<any>): ProsNode | null {
         if (!this.view) return null;
@@ -265,7 +288,6 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     render() {
-        console.log("render");
         const fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
@@ -296,6 +318,35 @@ export default class RichTextMenu extends AntimodeMenu {
             { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
         ]
 
+        // //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
+        // updateListItemDropdown(label: string, listTypeBtn: any) {
+        //     //remove old btn
+        //     if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
+
+        //     //Make a dropdown of all list types
+        //     const toAdd: MenuItem[] = [];
+        //     this.listTypeToIcon.forEach((icon, type) => {
+        //         toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType));
+        //     });
+        //     //option to remove the list formatting
+        //     toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType));
+
+        //     listTypeBtn = (new Dropdown(toAdd, { label: label, css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom;
+
+        //     //add this new button and return it
+        //     this.tooltip.appendChild(listTypeBtn);
+        //     return listTypeBtn;
+        // }
+
+        const 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: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
+        ]
+
+        // options: { node: NodeType | null, title: string, label: string, command: (node: NodeType) => void, hidden ?: boolean } []
+
         const buttons = [
             this.createButton("bold", "Bold", toggleMark(schema.marks.strong)),
             this.createButton("italic", "Italic", toggleMark(schema.marks.em)),
@@ -303,8 +354,10 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("strikethrough", "Strikethrough", toggleMark(schema.marks.strikethrough)),
             this.createButton("superscript", "Superscript", toggleMark(schema.marks.superscript)),
             this.createButton("subscript", "Subscript", toggleMark(schema.marks.subscript)),
+            this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
             this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
             this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions),
+            this.createNodesDropdown(this.activeListType, listTypeOptions),
         ];
 
         // this._marksToDoms = new Map();
-- 
cgit v1.2.3-70-g09d2


From 4791f95d6be68e590d0a80689d89be4c05e9ffc3 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 06:43:26 -0500
Subject: starting integrating brush tool into richtextmenu

---
 src/client/util/RichTextMenu.scss  |  10 +++
 src/client/util/RichTextMenu.tsx   | 137 +++++++++++++++++++++++++++++++++++--
 src/client/views/AntimodeMenu.scss |   2 +-
 src/client/views/AntimodeMenu.tsx  |  12 ++--
 4 files changed, 150 insertions(+), 11 deletions(-)
 create mode 100644 src/client/util/RichTextMenu.scss

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
new file mode 100644
index 000000000..f14703006
--- /dev/null
+++ b/src/client/util/RichTextMenu.scss
@@ -0,0 +1,10 @@
+.button-dropdown-wrapper {
+    position: relative;
+
+    .dropdown {
+        position: absolute;
+        top: 30px;
+        left: 0;
+        background-color: black;
+    }
+}
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 6b6a2620e..fa65adca5 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -5,10 +5,10 @@ import { observer } from "mobx-react";
 import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
 import { schema } from "./RichTextSchema";
 import { EditorView } from "prosemirror-view";
-import { EditorState, NodeSelection } from "prosemirror-state";
+import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown } from "@fortawesome/free-solid-svg-icons";
 import { MenuItem, Dropdown } from "prosemirror-menu";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../views/nodes/FieldView";
@@ -16,9 +16,10 @@ import { NumCast } from "../../new_fields/Types";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
 import { unimplementedFunction } from "../../Utils";
 import { wrapInList } from "prosemirror-schema-list";
+import "./RichTextMenu.scss";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 
-library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent);
+library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown);
 
 @observer
 export default class RichTextMenu extends AntimodeMenu {
@@ -31,6 +32,9 @@ export default class RichTextMenu extends AntimodeMenu {
     @observable private activeFontFamily: string = "";
     @observable private activeListType: string = "";
 
+    @observable private brushIsEmpty: boolean = true;
+    @observable private brushMarks: Set<Mark> = new Set();
+
     constructor(props: Readonly<{}>) {
         super(props);
         RichTextMenu.Instance = this;
@@ -121,6 +125,13 @@ export default class RichTextMenu extends AntimodeMenu {
         return styles;
     }
 
+    getMarksInSelection(state: EditorState<any>) {
+        const found = new Set<Mark>();
+        const { from, to } = state.selection as TextSelection;
+        state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m)));
+        return found;
+    }
+
     destroy() {
         console.log("destroy");
     }
@@ -142,7 +153,7 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         return (
-            <button className="antimodeMenu-button" title="title" onPointerDown={onClick}>
+            <button className="antimodeMenu-button" title={title} onPointerDown={onClick}>
                 <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
             </button>
         );
@@ -259,6 +270,123 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
 
+    createBrushButton() {
+        const self = this;
+        function onClick(e: React.PointerEvent) {
+            console.log("clicked button");
+            // dom.addEventListener("pointerdown", e => {
+            e.preventDefault();
+            self.view && self.view.focus();
+            //     if (dom.contains(e.target as Node)) {
+            e.stopPropagation();
+            //         command(this.view.state, this.view.dispatch, this.view);
+            //     }
+            // });
+
+            self.view && self.brush_function(self.view.state, self.view.dispatch);
+
+            // // update dropdown with marks
+            // const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
+            // self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
+            // self._brushDropdownDom = newBrushDropdowndom;
+        }
+
+        let label = "Stored marks: ";
+        if (this.brushMarks && this.brushMarks.size > 0) {
+            this.brushMarks.forEach((mark: Mark) => {
+                const markType = mark.type;
+                label += markType.name;
+                label += ", ";
+            });
+            label = label.substring(0, label.length - 2);
+        } else {
+            label = "No marks are currently stored";
+        }
+
+        return (
+            <div className="button-dropdown-wrapper">
+                <button className="antimodeMenu-button" title="" onPointerDown={onClick}>
+                    <FontAwesomeIcon icon="eye-dropper" size="lg" />
+                    <button className="dropdown-button"><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+                </button>
+                <div className="dropdown">
+                    <p>{label}</p>
+                    <button onPointerDown={this.clearBrush}>Clear brush</button>
+                    {/* <input placeholder="Enter URL"></input> */}
+                </div>
+            </div>
+        );
+    }
+
+    @action
+    clearBrush() {
+        this.brushIsEmpty = true;
+        this.brushMarks = new Set();
+    }
+
+    @action
+    brush_function(state: EditorState<any>, dispatch: any) {
+        if (!this.view) return;
+
+        if (this.brushIsEmpty) {
+            const selected_marks = this.getMarksInSelection(this.view.state);
+            // if (this._brushdom) {
+            if (selected_marks.size >= 0) {
+                this.brushMarks = selected_marks;
+                // const newbrush = this.createBrush(true).render(this.view).dom;
+                // this.tooltip.replaceChild(newbrush, this._brushdom);
+                // this._brushdom = newbrush;
+                this.brushIsEmpty = !this.brushIsEmpty;
+                // TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
+            }
+            // }
+        }
+        else {
+            const { from, to, $from } = this.view.state.selection;
+            // if (this._brushdom) {
+            if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
+                if (this.brushMarks && to - from > 0) {
+                    this.view.dispatch(this.view.state.tr.removeMark(from, to));
+                    Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
+                        this.setMark(mark, this.view!.state, this.view!.dispatch);
+                    });
+                }
+            }
+            else {
+                // const newbrush = this.createBrush(false).render(this.view).dom;
+                // this.tooltip.replaceChild(newbrush, this._brushdom);
+                // this._brushdom = newbrush;
+                this.brushIsEmpty = !this.brushIsEmpty;
+                // TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
+            }
+            // }
+        }
+        console.log("brush marks are ", this.brushMarks);
+    }
+
+    // createColorButton() {
+    //     const self = this;
+    //     function onClick(e: React.PointerEvent) {
+    //         console.log("clicked button");
+    //         // dom.addEventListener("pointerdown", e => {
+    //         e.preventDefault();
+    //         self.view && self.view.focus();
+    //         //     if (dom.contains(e.target as Node)) {
+    //         e.stopPropagation();
+    //         //         command(this.view.state, this.view.dispatch, this.view);
+    //         //     }
+    //         // });
+    //         self.view && command && command(self.view!.state, self.view!.dispatch, self.view);
+    //         self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view);
+    //     }
+
+    //     return (
+    //         <button className="antimodeMenu-button" title="Set font color" onPointerDown={onClick}>
+    //             <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
+    //         </button>
+    //     );
+    // }
+
     reference_node(pos: ResolvedPos<any>): ProsNode | null {
         if (!this.view) return null;
 
@@ -354,6 +482,7 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("strikethrough", "Strikethrough", toggleMark(schema.marks.strikethrough)),
             this.createButton("superscript", "Superscript", toggleMark(schema.marks.superscript)),
             this.createButton("subscript", "Subscript", toggleMark(schema.marks.subscript)),
+            this.createBrushButton(),
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
             this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
             this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions),
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index f3da5f284..a45f3346a 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -5,7 +5,7 @@
     background: #323232;
     box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
     border-radius: 0px 6px 6px 6px;
-    overflow: hidden;
+    // overflow: hidden;    
     display: flex;
 
     .antimodeMenu-button {
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 408df8bc2..2e9d29ef9 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -62,12 +62,12 @@ export default abstract class AntimodeMenu extends React.Component {
 
     @action
     protected pointerLeave = (e: React.PointerEvent) => {
-        if (!this.Pinned) {
-            this._transition = "opacity 0.5s";
-            this._transitionDelay = "1s";
-            this._opacity = 0.2;
-            setTimeout(() => this.fadeOut(false), 3000);
-        }
+        // if (!this.Pinned) {
+        //     this._transition = "opacity 0.5s";
+        //     this._transitionDelay = "1s";
+        //     this._opacity = 0.2;
+        //     setTimeout(() => this.fadeOut(false), 3000);
+        // }
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From e29ba39e74d2d22a74e767c39c30055ebf2ca5e0 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 07:14:29 -0500
Subject: styling for brush dropdown

---
 src/client/util/RichTextMenu.scss | 16 ++++++++++++++--
 src/client/util/RichTextMenu.tsx  | 10 +++++-----
 2 files changed, 19 insertions(+), 7 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index f14703006..403e9f88a 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -1,10 +1,22 @@
 .button-dropdown-wrapper {
     position: relative;
 
+    .dropdown-button {
+        width: 20px;
+        padding-left: 7px;
+        padding-right: 7px;
+    }
+
     .dropdown {
         position: absolute;
-        top: 30px;
+        top: 35px;
         left: 0;
-        background-color: black;
+        background-color: #323232;
+        color: $light-color-secondary;
+        border: 1px solid #4d4d4d;
+        border-radius: 0 6px 6px 6px;
+        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+        min-width: 150px;
+        padding: 5px;
     }
 }
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index fa65adca5..ad7b3f2fc 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -283,7 +283,7 @@ export default class RichTextMenu extends AntimodeMenu {
             //     }
             // });
 
-            self.view && self.brush_function(self.view.state, self.view.dispatch);
+            self.view && self.fillBrush(self.view.state, self.view.dispatch);
 
             // // update dropdown with marks
             // const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
@@ -307,8 +307,8 @@ export default class RichTextMenu extends AntimodeMenu {
             <div className="button-dropdown-wrapper">
                 <button className="antimodeMenu-button" title="" onPointerDown={onClick}>
                     <FontAwesomeIcon icon="eye-dropper" size="lg" />
-                    <button className="dropdown-button"><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 </button>
+                <button className="dropdown-button antimodeMenu-button"><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 <div className="dropdown">
                     <p>{label}</p>
                     <button onPointerDown={this.clearBrush}>Clear brush</button>
@@ -320,12 +320,12 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action
     clearBrush() {
-        this.brushIsEmpty = true;
-        this.brushMarks = new Set();
+        RichTextMenu.Instance.brushIsEmpty = true;
+        RichTextMenu.Instance.brushMarks = new Set();
     }
 
     @action
-    brush_function(state: EditorState<any>, dispatch: any) {
+    fillBrush(state: EditorState<any>, dispatch: any) {
         if (!this.view) return;
 
         if (this.brushIsEmpty) {
-- 
cgit v1.2.3-70-g09d2


From 372d8623ce5b0fd545739cb53a38f6d3f759e7e4 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 08:04:32 -0500
Subject: integrated color and highlight tools in richtextmenu

---
 src/client/util/RichTextMenu.scss |   2 +
 src/client/util/RichTextMenu.tsx  | 225 +++++++++++++++++++++++++++-----------
 2 files changed, 164 insertions(+), 63 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index 403e9f88a..9c5c55b62 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -1,3 +1,5 @@
+@import "../views/globalCssVariables";
+
 .button-dropdown-wrapper {
     position: relative;
 
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index ad7b3f2fc..a9de87572 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -8,7 +8,7 @@ import { EditorView } from "prosemirror-view";
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter } from "@fortawesome/free-solid-svg-icons";
 import { MenuItem, Dropdown } from "prosemirror-menu";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../views/nodes/FieldView";
@@ -16,10 +16,11 @@ import { NumCast } from "../../new_fields/Types";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
 import { unimplementedFunction } from "../../Utils";
 import { wrapInList } from "prosemirror-schema-list";
+import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
 import "./RichTextMenu.scss";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 
-library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown);
+library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter);
 
 @observer
 export default class RichTextMenu extends AntimodeMenu {
@@ -34,6 +35,13 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @observable private brushIsEmpty: boolean = true;
     @observable private brushMarks: Set<Mark> = new Set();
+    @observable private showBrushDropdown: boolean = false;
+
+    @observable private activeFontColor: string = "black";
+    @observable private showColorDropdown: boolean = false;
+
+    @observable private activeHighlightColor: string = "transparent";
+    @observable private showHighlightDropdown: boolean = false;
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -76,8 +84,8 @@ export default class RichTextMenu extends AntimodeMenu {
         const activeFamilies = active && active.get("families");
         const activeSizes = active && active.get("sizes");
 
-        this.activeFontFamily = !activeFamilies || activeFamilies.length == 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes || activeSizes.length == 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
+        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
 
         // this.update_mark_doms();
     }
@@ -160,7 +168,7 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean }[]): JSX.Element {
-        let items = options.map(({ title, label, hidden }) => {
+        const items = options.map(({ title, label, hidden }) => {
             if (hidden) {
                 return label === activeOption ?
                     <option value={label} title={title} selected hidden>{label}</option> :
@@ -183,7 +191,7 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean }[]): JSX.Element {
-        let items = options.map(({ title, label, hidden }) => {
+        const items = options.map(({ title, label, hidden }) => {
             if (hidden) {
                 return label === activeOption ?
                     <option value={label} title={title} selected hidden>{label}</option> :
@@ -270,25 +278,22 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
 
+    @action 
+    toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+
     createBrushButton() {
         const self = this;
-        function onClick(e: React.PointerEvent) {
-            console.log("clicked button");
-            // dom.addEventListener("pointerdown", e => {
+        function onBrushClick(e: React.PointerEvent) {
             e.preventDefault();
-            self.view && self.view.focus();
-            //     if (dom.contains(e.target as Node)) {
             e.stopPropagation();
-            //         command(this.view.state, this.view.dispatch, this.view);
-            //     }
-            // });
-
+            self.view && self.view.focus();
             self.view && self.fillBrush(self.view.state, self.view.dispatch);
-
-            // // update dropdown with marks
-            // const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
-            // self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
-            // self._brushDropdownDom = newBrushDropdowndom;
+        }
+        function onDropdownClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.toggleBrushDropdown();
         }
 
         let label = "Stored marks: ";
@@ -305,15 +310,17 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onClick}>
+                <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick}>
                     <FontAwesomeIcon icon="eye-dropper" size="lg" />
                 </button>
-                <button className="dropdown-button antimodeMenu-button"><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                <div className="dropdown">
-                    <p>{label}</p>
-                    <button onPointerDown={this.clearBrush}>Clear brush</button>
-                    {/* <input placeholder="Enter URL"></input> */}
-                </div>
+                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+                {this.showBrushDropdown ? 
+                    (<div className="dropdown">
+                        <p>{label}</p>
+                        <button onPointerDown={this.clearBrush}>Clear brush</button>
+                        {/* <input placeholder="Enter URL"></input> */}
+                    </div>) 
+                    : <></> }
             </div>
         );
     }
@@ -330,20 +337,14 @@ export default class RichTextMenu extends AntimodeMenu {
 
         if (this.brushIsEmpty) {
             const selected_marks = this.getMarksInSelection(this.view.state);
-            // if (this._brushdom) {
             if (selected_marks.size >= 0) {
                 this.brushMarks = selected_marks;
-                // const newbrush = this.createBrush(true).render(this.view).dom;
-                // this.tooltip.replaceChild(newbrush, this._brushdom);
-                // this._brushdom = newbrush;
                 this.brushIsEmpty = !this.brushIsEmpty;
-                // TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
             }
             // }
         }
         else {
             const { from, to, $from } = this.view.state.selection;
-            // if (this._brushdom) {
             if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
                 if (this.brushMarks && to - from > 0) {
                     this.view.dispatch(this.view.state.tr.removeMark(from, to));
@@ -353,39 +354,135 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             }
             else {
-                // const newbrush = this.createBrush(false).render(this.view).dom;
-                // this.tooltip.replaceChild(newbrush, this._brushdom);
-                // this._brushdom = newbrush;
                 this.brushIsEmpty = !this.brushIsEmpty;
-                // TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
             }
-            // }
         }
         console.log("brush marks are ", this.brushMarks);
     }
 
-    // createColorButton() {
-    //     const self = this;
-    //     function onClick(e: React.PointerEvent) {
-    //         console.log("clicked button");
-    //         // dom.addEventListener("pointerdown", e => {
-    //         e.preventDefault();
-    //         self.view && self.view.focus();
-    //         //     if (dom.contains(e.target as Node)) {
-    //         e.stopPropagation();
-    //         //         command(this.view.state, this.view.dispatch, this.view);
-    //         //     }
-    //         // });
-    //         self.view && command && command(self.view!.state, self.view!.dispatch, self.view);
-    //         self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view);
-    //     }
-
-    //     return (
-    //         <button className="antimodeMenu-button" title="Set font color" onPointerDown={onClick}>
-    //             <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
-    //         </button>
-    //     );
-    // }
+    @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
+    @action setActiveColor(color: string) { this.activeFontColor = color; }
+
+    createColorButton() {
+        const self = this;
+        function onColorClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+        }
+        function onDropdownClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.toggleColorDropdown();
+        }
+        function changeColor(e: React.PointerEvent, color: string) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.setActiveColor(color);
+            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+        }
+
+        const colors = [
+            DarkPastelSchemaPalette.get("pink2"),
+            DarkPastelSchemaPalette.get("purple4"),
+            DarkPastelSchemaPalette.get("bluegreen1"),
+            DarkPastelSchemaPalette.get("yellow4"),
+            DarkPastelSchemaPalette.get("red2"),
+            DarkPastelSchemaPalette.get("bluegreen7"),
+            DarkPastelSchemaPalette.get("bluegreen5"),
+            DarkPastelSchemaPalette.get("orange1"),
+            "#757472",
+            "#000"
+        ];
+
+        return (
+            <div className="button-dropdown-wrapper">
+                <button className="antimodeMenu-button" title="" onPointerDown={onColorClick}>
+                    <FontAwesomeIcon icon="palette" size="lg" />
+                </button>
+                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+                {this.showColorDropdown ? 
+                    (<div className="dropdown">
+                        {colors.map(color => {
+                            return <button style={{backgroundColor: color}} onPointerDown={e => changeColor(e, color)}></button>;
+                        })}
+                    </div>) 
+                    : <></> }
+            </div>
+        );
+    }
+
+    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
+        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
+        if (state.selection.empty) {
+            dispatch(state.tr.addStoredMark(colorMark));
+            return false;
+        }
+        this.setMark(colorMark, state, dispatch);
+    }
+
+    @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
+    @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
+
+    createHighlighterButton() {
+        const self = this;
+        function onHighlightClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+        }
+        function onDropdownClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.toggleHighlightDropdown();
+        }
+        function changeHighlight(e: React.PointerEvent, color: string) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.setActiveHighlight(color);
+            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+        }
+
+        const colors = [
+            PastelSchemaPalette.get("pink2"),
+            PastelSchemaPalette.get("purple4"),
+            PastelSchemaPalette.get("bluegreen1"),
+            PastelSchemaPalette.get("yellow4"),
+            PastelSchemaPalette.get("red2"),
+            PastelSchemaPalette.get("bluegreen7"),
+            PastelSchemaPalette.get("bluegreen5"),
+            PastelSchemaPalette.get("orange1"),
+            "white",
+            "transparent"
+        ];
+
+        return (
+            <div className="button-dropdown-wrapper">
+                <button className="antimodeMenu-button" title="" onPointerDown={onHighlightClick}>
+                    <FontAwesomeIcon icon="highlighter" size="lg" />
+                </button>
+                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+                {this.showHighlightDropdown ? 
+                    (<div className="dropdown">
+                        {colors.map(color => {
+                            return <button style={{backgroundColor: color}} onPointerDown={e => changeHighlight(e, color)}></button>;
+                        })}
+                    </div>) 
+                    : <></> }
+            </div>
+        );
+    }
+
+    insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
+        if (state.selection.empty) return false;
+        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
+    }
 
     reference_node(pos: ResolvedPos<any>): ProsNode | null {
         if (!this.view) return null;
@@ -432,7 +529,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { 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: "default", command: unimplementedFunction, hidden: true },
-        ]
+        ];
 
         const fontFamilyOptions = [
             { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily },
@@ -444,7 +541,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily },
             { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
             { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
-        ]
+        ];
 
         // //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
         // updateListItemDropdown(label: string, listTypeBtn: any) {
@@ -471,7 +568,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { 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: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
-        ]
+        ];
 
         // options: { node: NodeType | null, title: string, label: string, command: (node: NodeType) => void, hidden ?: boolean } []
 
@@ -482,6 +579,8 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("strikethrough", "Strikethrough", toggleMark(schema.marks.strikethrough)),
             this.createButton("superscript", "Superscript", toggleMark(schema.marks.superscript)),
             this.createButton("subscript", "Subscript", toggleMark(schema.marks.subscript)),
+            this.createColorButton(),
+            this.createHighlighterButton(),
             this.createBrushButton(),
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
             this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
-- 
cgit v1.2.3-70-g09d2


From 73403134ceae1e5665e8021175a54b0953541d23 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 08:33:27 -0500
Subject: started integrating link button with richtextmenu

---
 src/client/util/RichTextMenu.tsx | 176 ++++++++++++++++++++++++---------------
 1 file changed, 108 insertions(+), 68 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index a9de87572..12bd3348f 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -8,7 +8,7 @@ import { EditorView } from "prosemirror-view";
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink } from "@fortawesome/free-solid-svg-icons";
 import { MenuItem, Dropdown } from "prosemirror-menu";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../views/nodes/FieldView";
@@ -20,7 +20,7 @@ import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/S
 import "./RichTextMenu.scss";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 
-library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter);
+library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink);
 
 @observer
 export default class RichTextMenu extends AntimodeMenu {
@@ -43,6 +43,9 @@ export default class RichTextMenu extends AntimodeMenu {
     @observable private activeHighlightColor: string = "transparent";
     @observable private showHighlightDropdown: boolean = false;
 
+    @observable private currentLink: string | undefined = "";
+    @observable private showLinkDropdown: boolean = false;
+
     constructor(props: Readonly<{}>) {
         super(props);
         RichTextMenu.Instance = this;
@@ -67,9 +70,8 @@ export default class RichTextMenu extends AntimodeMenu {
         const state = view.state;
         // DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
-        // // Don't do anything if the document/selection didn't change
-        // if (lastState && lastState.doc.eq(state.doc) &&
-        //     lastState.selection.eq(state.selection)) return;
+        // Don't do anything if the document/selection didn't change
+        if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
 
         // this.reset_mark_doms();
 
@@ -147,15 +149,9 @@ export default class RichTextMenu extends AntimodeMenu {
     createButton(faIcon: string, title: string, command?: any, onclick?: any) {
         const self = this;
         function onClick(e: React.PointerEvent) {
-            console.log("clicked button");
-            // dom.addEventListener("pointerdown", e => {
             e.preventDefault();
-            self.view && self.view.focus();
-            //     if (dom.contains(e.target as Node)) {
             e.stopPropagation();
-            //         command(this.view.state, this.view.dispatch, this.view);
-            //     }
-            // });
+            self.view && self.view.focus();
             self.view && command && command(self.view!.state, self.view!.dispatch, self.view);
             self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view);
         }
@@ -310,9 +306,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick}>
-                    <FontAwesomeIcon icon="eye-dropper" size="lg" />
-                </button>
+                <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick}><FontAwesomeIcon icon="eye-dropper" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showBrushDropdown ? 
                     (<div className="dropdown">
@@ -400,9 +394,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onColorClick}>
-                    <FontAwesomeIcon icon="palette" size="lg" />
-                </button>
+                <button className="antimodeMenu-button" title="" onPointerDown={onColorClick}><FontAwesomeIcon icon="palette" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showColorDropdown ? 
                     (<div className="dropdown">
@@ -464,9 +456,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onHighlightClick}>
-                    <FontAwesomeIcon icon="highlighter" size="lg" />
-                </button>
+                <button className="antimodeMenu-button" title="" onPointerDown={onHighlightClick}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showHighlightDropdown ? 
                     (<div className="dropdown">
@@ -484,6 +474,102 @@ export default class RichTextMenu extends AntimodeMenu {
         toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
     }
 
+    @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; }
+    @action setCurrentLink(link: string) { this.currentLink = link; }
+
+    createLinkButton() {
+        const self = this;
+        function onDropdownClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.toggleLinkDropdown();
+        }
+        function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
+            self.setCurrentLink(e.target.value);
+        }
+
+        const targetTitle = await this.getTextLinkTargetTitle();
+        console.log(targetTitle);
+        // this.setCurrentLink(targetTitle);
+
+        return (
+            <div className="button-dropdown-wrapper">
+                <button className="antimodeMenu-button" title="" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="link" size="lg" /></button>
+                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+                {this.showLinkDropdown ? 
+                    (<div className="dropdown">
+                        <p>Linked to:</p>
+                        {/* <input value={this.currentLink} placeholder="Enter URL" onChange={onLinkChange}/>
+                        <button onPointerDown={e => this.makeLinkToURL("input value", "onRight")}>Apply hyperlink</button>
+                        <button onPointerDown={e => this.deleteLink()}>Remove link</button> */}
+                    </div>) 
+                    : <></> }
+            </div>
+        );
+    }
+
+    async getTextLinkTargetTitle() {
+        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.href;
+            if (href) {
+                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    if (linkclicked) {
+                        const linkDoc = await DocServer.GetRefField(linkclicked);
+                        if (linkDoc instanceof Doc) {
+                            const anchor1 = await Cast(linkDoc.anchor1, Doc);
+                            const anchor2 = await Cast(linkDoc.anchor2, Doc);
+                            const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
+                            if (currentDoc && anchor1 && anchor2) {
+                                if (Doc.AreProtosEqual(currentDoc, anchor1)) {
+                                    return StrCast(anchor2.title);
+                                }
+                                if (Doc.AreProtosEqual(currentDoc, anchor2)) {
+                                    return StrCast(anchor1.title);
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    return href;
+                }
+            } else {
+                return link.attrs.title;
+            }
+        }
+    }
+
+    makeLinkToURL = (target: String, lcoation: string) => {
+        let node = this.view.state.selection.$from.nodeAfter;
+        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
+        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+        node = this.view.state.selection.$from.nodeAfter;
+        link = node && node.marks.find(m => m.type.name === "link");
+    }
+
+    deleteLink = () => {
+        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.href;
+        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));
+                        }
+                    });
+                }
+            }
+        }
+    }
+
     reference_node(pos: ResolvedPos<any>): ProsNode | null {
         if (!this.view) return null;
 
@@ -543,26 +629,6 @@ export default class RichTextMenu extends AntimodeMenu {
             { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
         ];
 
-        // //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
-        // updateListItemDropdown(label: string, listTypeBtn: any) {
-        //     //remove old btn
-        //     if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
-
-        //     //Make a dropdown of all list types
-        //     const toAdd: MenuItem[] = [];
-        //     this.listTypeToIcon.forEach((icon, type) => {
-        //         toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType));
-        //     });
-        //     //option to remove the list formatting
-        //     toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType));
-
-        //     listTypeBtn = (new Dropdown(toAdd, { label: label, css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom;
-
-        //     //add this new button and return it
-        //     this.tooltip.appendChild(listTypeBtn);
-        //     return listTypeBtn;
-        // }
-
         const 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 },
@@ -570,8 +636,6 @@ export default class RichTextMenu extends AntimodeMenu {
             { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
         ];
 
-        // options: { node: NodeType | null, title: string, label: string, command: (node: NodeType) => void, hidden ?: boolean } []
-
         const buttons = [
             this.createButton("bold", "Bold", toggleMark(schema.marks.strong)),
             this.createButton("italic", "Italic", toggleMark(schema.marks.em)),
@@ -581,6 +645,7 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("subscript", "Subscript", toggleMark(schema.marks.subscript)),
             this.createColorButton(),
             this.createHighlighterButton(),
+            this.createLinkButton(),
             this.createBrushButton(),
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
             this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
@@ -588,31 +653,6 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createNodesDropdown(this.activeListType, listTypeOptions),
         ];
 
-        // this._marksToDoms = new Map();
-        // items.forEach(({ title, dom, command }) => {
-        //     // this.tooltip.appendChild(dom);
-        //     switch (title) {
-        //         case "Bold":
-        //             this._marksToDoms.set(schema.mark(schema.marks.strong), dom);
-        //             // this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
-        //             break;
-        //         case "Italic":
-        //             this._marksToDoms.set(schema.mark(schema.marks.em), dom);
-        //             // this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
-        //             break;
-        //     }
-
-        //     //pointer down handler to activate button effects
-        //     dom.addEventListener("pointerdown", e => {
-        //         e.preventDefault();
-        //         this.view.focus();
-        //         if (dom.contains(e.target as Node)) {
-        //             e.stopPropagation();
-        //             command(this.view.state, this.view.dispatch, this.view);
-        //         }
-        //     });
-        // });
-
         return this.getElement(buttons);
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 34f96cb1fe36cc5448d2cd42c4b208751cdb3403 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 09:13:43 -0500
Subject: integrated link button on richtextmenu

---
 src/client/util/RichTextMenu.scss | 10 ++++--
 src/client/util/RichTextMenu.tsx  | 69 +++++++++++++++++++++++++--------------
 2 files changed, 51 insertions(+), 28 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index 9c5c55b62..f7414cc7f 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -4,9 +4,9 @@
     position: relative;
 
     .dropdown-button {
-        width: 20px;
-        padding-left: 7px;
-        padding-right: 7px;
+        width: 15px;
+        padding-left: 5px;
+        padding-right: 5px;
     }
 
     .dropdown {
@@ -21,4 +21,8 @@
         min-width: 150px;
         padding: 5px;
     }
+
+    input {
+        color: black;
+    }
 }
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 12bd3348f..b6b2c53ff 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -12,12 +12,16 @@ import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscr
 import { MenuItem, Dropdown } from "prosemirror-menu";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../views/nodes/FieldView";
-import { NumCast } from "../../new_fields/Types";
+import { NumCast, Cast, StrCast } from "../../new_fields/Types";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { unimplementedFunction } from "../../Utils";
+import { unimplementedFunction, Utils } from "../../Utils";
 import { wrapInList } from "prosemirror-schema-list";
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
 import "./RichTextMenu.scss";
+import { DocServer } from "../DocServer";
+import { Doc } from "../../new_fields/Doc";
+import { SelectionManager } from "./SelectionManager";
+import { LinkManager } from "./LinkManager";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 
 library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink);
@@ -81,6 +85,10 @@ export default class RichTextMenu extends AntimodeMenu {
         // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom);
         // this._linkDropdownDom = newLinkDropdowndom;
 
+        // const targetTitle = await this.getTextLinkTargetTitle();
+        // // console.log(targetTitle);
+        // this.setCurrentLink(targetTitle);
+
         // update active font family and size
         const active = this.getActiveFontStylesOnSelection();
         const activeFamilies = active && active.get("families");
@@ -89,6 +97,9 @@ export default class RichTextMenu extends AntimodeMenu {
         this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
         this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
 
+        const targetTitle = await this.getTextLinkTargetTitle();
+        this.setCurrentLink(targetTitle);
+
         // this.update_mark_doms();
     }
 
@@ -274,7 +285,7 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
 
-    @action 
+    @action
     toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
 
     createBrushButton() {
@@ -308,13 +319,13 @@ export default class RichTextMenu extends AntimodeMenu {
             <div className="button-dropdown-wrapper">
                 <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick}><FontAwesomeIcon icon="eye-dropper" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showBrushDropdown ? 
+                {this.showBrushDropdown ?
                     (<div className="dropdown">
                         <p>{label}</p>
                         <button onPointerDown={this.clearBrush}>Clear brush</button>
                         {/* <input placeholder="Enter URL"></input> */}
-                    </div>) 
-                    : <></> }
+                    </div>)
+                    : <></>}
             </div>
         );
     }
@@ -396,13 +407,13 @@ export default class RichTextMenu extends AntimodeMenu {
             <div className="button-dropdown-wrapper">
                 <button className="antimodeMenu-button" title="" onPointerDown={onColorClick}><FontAwesomeIcon icon="palette" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showColorDropdown ? 
+                {this.showColorDropdown ?
                     (<div className="dropdown">
                         {colors.map(color => {
-                            return <button style={{backgroundColor: color}} onPointerDown={e => changeColor(e, color)}></button>;
+                            if (color) return <button style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
                         })}
-                    </div>) 
-                    : <></> }
+                    </div>)
+                    : <></>}
             </div>
         );
     }
@@ -458,13 +469,13 @@ export default class RichTextMenu extends AntimodeMenu {
             <div className="button-dropdown-wrapper">
                 <button className="antimodeMenu-button" title="" onPointerDown={onHighlightClick}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showHighlightDropdown ? 
+                {this.showHighlightDropdown ?
                     (<div className="dropdown">
                         {colors.map(color => {
-                            return <button style={{backgroundColor: color}} onPointerDown={e => changeHighlight(e, color)}></button>;
+                            if (color) return <button style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}></button>;
                         })}
-                    </div>) 
-                    : <></> }
+                    </div>)
+                    : <></>}
             </div>
         );
     }
@@ -489,27 +500,30 @@ export default class RichTextMenu extends AntimodeMenu {
             self.setCurrentLink(e.target.value);
         }
 
-        const targetTitle = await this.getTextLinkTargetTitle();
-        console.log(targetTitle);
-        // this.setCurrentLink(targetTitle);
+        // const targetTitle = this.getTextLinkTargetTitle();
+        console.log("link curr is ", this.currentLink);
+        // // this.setCurrentLink(targetTitle);
+        const link = this.currentLink ? this.currentLink : "";
 
         return (
             <div className="button-dropdown-wrapper">
                 <button className="antimodeMenu-button" title="" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="link" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showLinkDropdown ? 
+                {this.showLinkDropdown ?
                     (<div className="dropdown">
                         <p>Linked to:</p>
-                        {/* <input value={this.currentLink} placeholder="Enter URL" onChange={onLinkChange}/>
-                        <button onPointerDown={e => this.makeLinkToURL("input value", "onRight")}>Apply hyperlink</button>
-                        <button onPointerDown={e => this.deleteLink()}>Remove link</button> */}
-                    </div>) 
-                    : <></> }
+                        <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
+                        <button onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+                        <button onPointerDown={e => this.deleteLink()}>Remove link</button>
+                    </div>)
+                    : <></>}
             </div>
         );
     }
 
     async getTextLinkTargetTitle() {
+        if (!this.view) return;
+
         const node = this.view.state.selection.$from.nodeAfter;
         const link = node && node.marks.find(m => m.type.name === "link");
         if (link) {
@@ -542,7 +556,10 @@ export default class RichTextMenu extends AntimodeMenu {
         }
     }
 
+    // TODO: should check for valid URL
     makeLinkToURL = (target: String, lcoation: string) => {
+        if (!this.view) return;
+
         let node = this.view.state.selection.$from.nodeAfter;
         let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
         this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
@@ -552,8 +569,10 @@ 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 link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
         const href = link!.attrs.href;
         if (href) {
             if (href.indexOf(Utils.prepend("/doc/")) === 0) {
@@ -562,7 +581,7 @@ export default class RichTextMenu extends AntimodeMenu {
                     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));
+                            this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
                         }
                     });
                 }
-- 
cgit v1.2.3-70-g09d2


From 9614ba541c30dc7d2b5183d5450864354d911643 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 10:11:14 -0500
Subject: can remove links without needing to select whole link

---
 src/client/util/RichTextMenu.tsx | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index b6b2c53ff..371ba97a9 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -574,21 +574,53 @@ export default class RichTextMenu extends AntimodeMenu {
         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.href;
+        console.log("delete link", node, link, href);
         if (href) {
+            console.log("has href");
             if (href.indexOf(Utils.prepend("/doc/")) === 0) {
                 const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                 if (linkclicked) {
+                    console.log("linkclicked");
                     DocServer.GetRefField(linkclicked).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
+                            console.log("is doc");
                             LinkManager.Instance.deleteLink(linkDoc);
+                            console.log("remove the link! ", this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link);
                             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 {
+                console.log("remove the link! ", this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link);
+                if (node) {
+                    let extension = this.linkExtend(this.view!.state.selection.$anchor, href);
+                    console.log("remove the link", extension.from, extension.to);
+                    this.view!.dispatch(this.view!.state.tr.removeMark(extension.from, extension.to, this.view!.state.schema.marks.link));
+
+                }
             }
         }
     }
 
+    linkExtend($start: ResolvedPos, href: string) {
+        const mark = this.view!.state.schema.marks.link;
+
+        let startIndex = $start.index();
+        let endIndex = $start.indexAfter();
+
+        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--;
+        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++;
+
+        let startPos = $start.start();
+        let endPos = startPos;
+        for (let i = 0; i < endIndex; i++) {
+            let size = $start.parent.child(i).nodeSize;
+            if (i < startIndex) startPos += size;
+            endPos += size;
+        }
+        return { from: startPos, to: endPos };
+    }
+
     reference_node(pos: ResolvedPos<any>): ProsNode | null {
         if (!this.view) return null;
 
-- 
cgit v1.2.3-70-g09d2


From 80c417f24bcc1109e12645cfc522a820cf22e099 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 10:51:40 -0500
Subject: pull from master

---
 package-lock.json                        | 77 ++++++++++++++++----------------
 src/client/util/RichTextMenu.tsx         | 20 +--------
 src/client/util/SelectionManager.ts      |  1 +
 src/client/views/DocumentDecorations.tsx | 10 ++++-
 4 files changed, 50 insertions(+), 58 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/package-lock.json b/package-lock.json
index 0fbc4dcf5..2c70ba94d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2423,7 +2423,7 @@
             "resolved": "https://registry.npmjs.org/boolify-string/-/boolify-string-2.0.2.tgz",
             "integrity": "sha1-n4m9l9YKFEijlAF8SjuaPSQNRY4=",
             "requires": {
-                "type-detect": "1.0.0"
+                "type-detect": "^1.0.0"
             },
             "dependencies": {
                 "type-detect": {
@@ -2561,7 +2561,7 @@
         },
         "browserify-aes": {
             "version": "1.2.0",
-            "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+            "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
                 "buffer-xor": "^1.0.3",
@@ -2595,7 +2595,7 @@
         },
         "browserify-rsa": {
             "version": "4.0.1",
-            "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -2632,7 +2632,7 @@
         },
         "buffer": {
             "version": "4.9.1",
-            "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+            "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
             "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
             "dev": true,
             "requires": {
@@ -2779,7 +2779,7 @@
         },
         "camelcase-keys": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
@@ -3482,8 +3482,8 @@
             "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
             "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
             "requires": {
-                "object-assign": "4.1.1",
-                "vary": "1.1.2"
+                "object-assign": "^4",
+                "vary": "^1"
             }
         },
         "cosmiconfig": {
@@ -3563,7 +3563,7 @@
         },
         "create-hash": {
             "version": "1.2.0",
-            "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+            "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
                 "cipher-base": "^1.0.1",
@@ -3575,7 +3575,7 @@
         },
         "create-hmac": {
             "version": "1.1.7",
-            "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+            "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
                 "cipher-base": "^1.0.3",
@@ -4118,7 +4118,7 @@
         },
         "diffie-hellman": {
             "version": "5.0.3",
-            "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+            "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -4318,9 +4318,10 @@
         },
         "emit-logger": {
             "version": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070",
+            "from": "github:chocolateboy/emit-logger#better-emitter-name",
             "requires": {
-                "chalk": "1.1.3",
-                "moment": "2.24.0"
+                "chalk": "^1.1.1",
+                "moment": "^2.10.6"
             }
         },
         "emoji-regex": {
@@ -7069,18 +7070,18 @@
             "resolved": "https://registry.npmjs.org/ipc-event-emitter/-/ipc-event-emitter-2.0.2.tgz",
             "integrity": "sha512-hJsN8zCg8MZwl5nbTutqINDO4pJPbKwmCfrTJaRLNE+5H15mJx7Mxo3pXIAi8zlh+N5xpf+PdMOQ0pbtZQvRKA==",
             "requires": {
-                "babel-runtime": "6.26.0",
-                "bluebird": "3.5.5",
-                "boolify-string": "2.0.2",
-                "emit-logger": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070",
-                "lodash": "4.17.15",
-                "semver": "5.7.0",
-                "source-map-support": "0.5.12"
+                "babel-runtime": "^6.22.0",
+                "bluebird": "^3.4.7",
+                "boolify-string": "^2.0.2",
+                "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name",
+                "lodash": "^4.17.4",
+                "semver": "^5.0.3",
+                "source-map-support": "^0.5.9"
             }
         },
         "is-accessor-descriptor": {
             "version": "0.1.6",
-            "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7129,7 +7130,7 @@
         },
         "is-data-descriptor": {
             "version": "0.1.4",
-            "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7914,7 +7915,7 @@
         },
         "load-json-file": {
             "version": "1.1.0",
-            "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8228,7 +8229,7 @@
         },
         "media-typer": {
             "version": "0.3.0",
-            "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
             "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "mem": {
@@ -8260,7 +8261,7 @@
         },
         "meow": {
             "version": "3.7.0",
-            "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+            "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
@@ -8453,7 +8454,7 @@
         },
         "mkdirp": {
             "version": "0.5.1",
-            "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
             "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
             "requires": {
                 "minimist": "0.0.8"
@@ -8799,7 +8800,7 @@
         },
         "next-tick": {
             "version": "1.0.0",
-            "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
             "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
         },
         "nice-try": {
@@ -8963,7 +8964,7 @@
                 },
                 "semver": {
                     "version": "5.3.0",
-                    "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
                     "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "tar": {
@@ -12572,7 +12573,7 @@
         },
         "os-homedir": {
             "version": "1.0.2",
-            "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
             "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-locale": {
@@ -12585,7 +12586,7 @@
         },
         "os-tmpdir": {
             "version": "1.0.2",
-            "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
             "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
@@ -12825,7 +12826,7 @@
         },
         "path-is-absolute": {
             "version": "1.0.1",
-            "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
@@ -14145,7 +14146,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -14490,7 +14491,7 @@
         },
         "safe-regex": {
             "version": "1.1.0",
-            "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
@@ -14755,7 +14756,7 @@
         },
         "sha.js": {
             "version": "2.4.11",
-            "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+            "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
                 "inherits": "^2.0.1",
@@ -15498,7 +15499,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -15528,7 +15529,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -15544,7 +15545,7 @@
         },
         "strip-eof": {
             "version": "1.0.0",
-            "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
             "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
         },
         "strip-indent": {
@@ -16297,7 +16298,7 @@
         },
         "tty-browserify": {
             "version": "0.0.0",
-            "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
             "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
             "dev": true
         },
@@ -17739,7 +17740,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 371ba97a9..111822307 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -53,6 +53,7 @@ export default class RichTextMenu extends AntimodeMenu {
     constructor(props: Readonly<{}>) {
         super(props);
         RichTextMenu.Instance = this;
+        this.Pinned = true;
     }
 
     @action
@@ -79,16 +80,6 @@ export default class RichTextMenu extends AntimodeMenu {
 
         // this.reset_mark_doms();
 
-        // // update link dropdown
-        // const linkDropdown = await this.createLinkDropdown();
-        // const newLinkDropdowndom = linkDropdown.render(this.view).dom;
-        // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom);
-        // this._linkDropdownDom = newLinkDropdowndom;
-
-        // const targetTitle = await this.getTextLinkTargetTitle();
-        // // console.log(targetTitle);
-        // this.setCurrentLink(targetTitle);
-
         // update active font family and size
         const active = this.getActiveFontStylesOnSelection();
         const activeFamilies = active && active.get("families");
@@ -97,6 +88,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
         this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
 
+        // update link in current selection
         const targetTitle = await this.getTextLinkTargetTitle();
         this.setCurrentLink(targetTitle);
 
@@ -574,29 +566,21 @@ export default class RichTextMenu extends AntimodeMenu {
         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.href;
-        console.log("delete link", node, link, href);
         if (href) {
-            console.log("has href");
             if (href.indexOf(Utils.prepend("/doc/")) === 0) {
                 const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                 if (linkclicked) {
-                    console.log("linkclicked");
                     DocServer.GetRefField(linkclicked).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
-                            console.log("is doc");
                             LinkManager.Instance.deleteLink(linkDoc);
-                            console.log("remove the link! ", this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link);
                             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 {
-                console.log("remove the link! ", this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link);
                 if (node) {
                     let extension = this.linkExtend(this.view!.state.selection.$anchor, href);
-                    console.log("remove the link", extension.from, extension.to);
                     this.view!.dispatch(this.view!.state.tr.removeMark(extension.from, extension.to, this.view!.state.schema.marks.link));
-
                 }
             }
         }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 4612f10f4..0c733ac47 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -14,6 +14,7 @@ export namespace SelectionManager {
 
         @action
         SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
+            console.log("select doc!!!");
             // if doc is not in SelectedDocuments, add it
             if (!manager.SelectedDocuments.get(docView)) {
                 if (!ctrlPressed) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 76b6a8834..093761f1f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,6 +77,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         DocumentDecorations.Instance = this;
         this._keyinput = React.createRef();
         reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this.titleBlur(false));
+        console.log("constructing document decorations!!!");
+    }
+
+    componentDidMount() {
+        console.log("mounting deocument deoctirioeon!!!!");
     }
 
     @action titleChanged = (event: any) => this._accumulatedTitle = event.target.value;
@@ -574,8 +579,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (bounds.y > bounds.b) {
             bounds.y = bounds.b - (this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight);
         }
-        // RichTextMenu.Instance.jumpTo(this._lastX, this._lastY - 50);
-        RichTextMenu.Instance.jumpTo(500, 300);
+        // // RichTextMenu.Instance.jumpTo(this._lastX, this._lastY - 50);
+        // console.log("jump to ");
+        // RichTextMenu.Instance.jumpTo(500, 300);
         return (<div className="documentDecorations">
             <div className="documentDecorations-background" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
-- 
cgit v1.2.3-70-g09d2


From ced3532239c582938271635802e0d2d62703e148 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 14:20:36 -0500
Subject: richtextmenu appears whenever formattedtextbox is focused

---
 src/client/util/RichTextMenu.scss           | 64 +++++++++++++++++++++++++++++
 src/client/util/RichTextMenu.tsx            | 58 +++++++++++++++++---------
 src/client/util/SelectionManager.ts         |  3 +-
 src/client/util/TooltipTextMenu.tsx         |  2 +
 src/client/views/DocumentDecorations.tsx    |  8 ----
 src/client/views/nodes/FormattedTextBox.tsx |  7 ++++
 6 files changed, 113 insertions(+), 29 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index f7414cc7f..85d2765e3 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -20,9 +20,73 @@
         box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
         min-width: 150px;
         padding: 5px;
+
+        button {
+            background-color: #323232;
+            border: 1px solid black;
+            border-radius: 1px;
+            padding: 6px;
+            margin: 5px 0;
+
+            &:hover {
+                background-color: black;
+            }
+        }
     }
 
     input {
         color: black;
     }
+}
+
+.link-menu {
+    .divider {
+        background-color: white;
+        height: 1px;
+        width: 100%;
+    }
+}
+
+.color-preview-button {
+    .color-preview {
+        width: 100%;
+        height: 3px;
+        margin-top: 3px;
+    }
+}
+
+.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 !important;
+        }
+    }
+}
+
+select {
+    background-color: #323232;
+    color: white;
+    border: 1px solid black;
+    border-top: none;
+    border-bottom: none;
+
+    &:focus,
+    &:hover {
+        background-color: black;
+    }
+
+    &::-ms-expand {
+        color: white;
+    }
 }
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 111822307..8c373b818 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -29,6 +29,7 @@ library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSub
 @observer
 export default class RichTextMenu extends AntimodeMenu {
     static Instance: RichTextMenu;
+    @observable private isVisible: boolean = false;
 
     private view?: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
@@ -53,7 +54,6 @@ export default class RichTextMenu extends AntimodeMenu {
     constructor(props: Readonly<{}>) {
         super(props);
         RichTextMenu.Instance = this;
-        this.Pinned = true;
     }
 
     @action
@@ -73,6 +73,7 @@ export default class RichTextMenu extends AntimodeMenu {
         }
         this.view = view;
         const state = view.state;
+        this.isVisible = true;
         // DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
         // Don't do anything if the document/selection didn't change
@@ -146,7 +147,6 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     destroy() {
-        console.log("destroy");
     }
 
     createButton(faIcon: string, title: string, command?: any, onclick?: any) {
@@ -213,7 +213,6 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
-        console.log("change font size!!");
         const size = mark.attrs.fontSize;
         if (this.editorProps) {
             const ruleProvider = this.editorProps.ruleProvider;
@@ -241,9 +240,7 @@ export default class RichTextMenu extends AntimodeMenu {
     // TODO: remove doesn't work
     //remove all node type and apply the passed-in one to the selected text
     changeListType = (nodeType: NodeType | undefined) => {
-        console.log("change the list type ");
         if (!this.view) return;
-        console.log("change the list type has view");
 
         if (nodeType === schema.nodes.bullet_list) {
             wrapInList(nodeType)(this.view.state, this.view.dispatch);
@@ -354,7 +351,6 @@ export default class RichTextMenu extends AntimodeMenu {
                 this.brushIsEmpty = !this.brushIsEmpty;
             }
         }
-        console.log("brush marks are ", this.brushMarks);
     }
 
     @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
@@ -397,13 +393,23 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onColorClick}><FontAwesomeIcon icon="palette" size="lg" /></button>
+                <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
+                    <FontAwesomeIcon icon="palette" size="lg" />
+                    <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
+                </button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showColorDropdown ?
                     (<div className="dropdown">
-                        {colors.map(color => {
-                            if (color) return <button style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
-                        })}
+                        <p>Change font color:</p>
+                        <div className="color-wrapper">
+                            {colors.map(color => {
+                                if (color) {
+                                    return this.activeFontColor === color ?
+                                        <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+                                        <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+                                }
+                            })}
+                        </div>
                     </div>)
                     : <></>}
             </div>
@@ -459,13 +465,23 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onHighlightClick}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
+                <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}>
+                    <FontAwesomeIcon icon="highlighter" size="lg" />
+                    <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
+                </button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showHighlightDropdown ?
                     (<div className="dropdown">
-                        {colors.map(color => {
-                            if (color) return <button style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}></button>;
-                        })}
+                        <p>Change highlight color:</p>
+                        <div className="color-wrapper">
+                            {colors.map(color => {
+                                if (color) {
+                                    return this.activeHighlightColor === color ?
+                                        <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+                                        <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+                                }
+                            })}
+                        </div>
                     </div>)
                     : <></>}
             </div>
@@ -492,9 +508,6 @@ export default class RichTextMenu extends AntimodeMenu {
             self.setCurrentLink(e.target.value);
         }
 
-        // const targetTitle = this.getTextLinkTargetTitle();
-        console.log("link curr is ", this.currentLink);
-        // // this.setCurrentLink(targetTitle);
         const link = this.currentLink ? this.currentLink : "";
 
         return (
@@ -502,11 +515,12 @@ export default class RichTextMenu extends AntimodeMenu {
                 <button className="antimodeMenu-button" title="" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="link" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showLinkDropdown ?
-                    (<div className="dropdown">
+                    (<div className="dropdown link-menu">
                         <p>Linked to:</p>
                         <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
-                        <button onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
-                        <button onPointerDown={e => this.deleteLink()}>Remove link</button>
+                        <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+                        <div className="divider"></div>
+                        <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
                     </div>)
                     : <></>}
             </div>
@@ -634,6 +648,10 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     render() {
+        // if (!this.isVisible) return <></>;
+        SelectionManager.SelectedDocuments()
+        // if (this.Pinned || )
+
         const fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0c733ac47..86a7a620e 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,6 +3,8 @@ import { Doc } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { computedFn } from "mobx-utils";
 import { List } from "../../new_fields/List";
+import { DocumentDecorations } from "../views/DocumentDecorations";
+import RichTextMenu from "./RichTextMenu";
 
 export namespace SelectionManager {
 
@@ -14,7 +16,6 @@ export namespace SelectionManager {
 
         @action
         SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
-            console.log("select doc!!!");
             // if doc is not in SelectedDocuments, add it
             if (!manager.SelectedDocuments.get(docView)) {
                 if (!ctrlPressed) {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 33257b658..2c4d08b82 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -20,6 +20,8 @@ import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/S
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
+// deprecated in favor of richtextmenu
+
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
     public static Toolbar: HTMLDivElement | undefined;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 093761f1f..799b3695c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,11 +77,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         DocumentDecorations.Instance = this;
         this._keyinput = React.createRef();
         reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this.titleBlur(false));
-        console.log("constructing document decorations!!!");
-    }
-
-    componentDidMount() {
-        console.log("mounting deocument deoctirioeon!!!!");
     }
 
     @action titleChanged = (event: any) => this._accumulatedTitle = event.target.value;
@@ -579,9 +574,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (bounds.y > bounds.b) {
             bounds.y = bounds.b - (this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight);
         }
-        // // RichTextMenu.Instance.jumpTo(this._lastX, this._lastY - 50);
-        // console.log("jump to ");
-        // RichTextMenu.Instance.jumpTo(500, 300);
         return (<div className="documentDecorations">
             <div className="documentDecorations-background" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f00a24d41..7d48a0859 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -48,6 +48,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { InkTool } from '../../../new_fields/InkField';
 import { TraceMobx } from '../../../new_fields/util';
 import RichTextMenu from '../../util/RichTextMenu';
+import { DocumentDecorations } from '../DocumentDecorations';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -910,6 +911,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         prosediv && (prosediv.keeplocation = undefined);
         const pos = this._editorView?.state.selection.$from.pos || 1;
         keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+
+        // jump rich text menu to this textbox
+        this._ref.current && RichTextMenu.Instance.jumpTo(this._ref.current.getBoundingClientRect().x, this._ref.current?.getBoundingClientRect().y - 70);
     }
     onPointerWheel = (e: React.WheelEvent): void => {
         // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
@@ -1055,6 +1059,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             this._undoTyping = undefined;
         }
         this.doLinkOnDeselect();
+
+        // move the richtextmenu offscreen
+        if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.jumpTo(-300, -300);
     }
 
     _lastTimedMark: Mark | undefined = undefined;
-- 
cgit v1.2.3-70-g09d2


From e9dcc0f18498d9415d204dfd3e61465a70486374 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 15:08:31 -0500
Subject: kind of hack to get selects in richtextmenu to be selectable again

---
 src/client/util/RichTextMenu.scss           |  9 ++++++--
 src/client/util/RichTextMenu.tsx            | 35 ++++++++++++++++++-----------
 src/client/views/AntimodeMenu.scss          |  9 ++++++++
 src/client/views/AntimodeMenu.tsx           | 10 +++++++++
 src/client/views/nodes/FormattedTextBox.tsx |  5 +++--
 5 files changed, 51 insertions(+), 17 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index 85d2765e3..5a57f4b98 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -20,6 +20,7 @@
         box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
         min-width: 150px;
         padding: 5px;
+        font-size: 12px;
 
         button {
             background-color: #323232;
@@ -27,6 +28,7 @@
             border-radius: 1px;
             padding: 6px;
             margin: 5px 0;
+            font-size: 12px;
 
             &:hover {
                 background-color: black;
@@ -78,8 +80,11 @@ select {
     background-color: #323232;
     color: white;
     border: 1px solid black;
-    border-top: none;
-    border-bottom: none;
+    // border-top: none;
+    // border-bottom: none;
+    font-size: 12px;
+    height: 100%;
+    margin-right: 3px;
 
     &:focus,
     &:hover {
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 8c373b818..4538a77d6 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -29,7 +29,7 @@ library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSub
 @observer
 export default class RichTextMenu extends AntimodeMenu {
     static Instance: RichTextMenu;
-    @observable private isVisible: boolean = false;
+    public overDropdown: boolean = false; // kind of hacky way to prevent selects not being selectable
 
     private view?: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
@@ -73,7 +73,6 @@ export default class RichTextMenu extends AntimodeMenu {
         }
         this.view = view;
         const state = view.state;
-        this.isVisible = true;
         // DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
         // Don't do anything if the document/selection didn't change
@@ -179,14 +178,19 @@ export default class RichTextMenu extends AntimodeMenu {
         });
 
         const self = this;
-        function onChange(val: string) {
+        function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
+            e.stopPropagation();
+            e.preventDefault();
+            console.log("on change marks");
             options.forEach(({ label, mark, command }) => {
-                if (val === label) {
+                if (e.target.value === label) {
                     self.view && mark && command(mark, self.view);
                 }
             });
         }
-        return <select onChange={e => onChange(e.target.value)}>{items}</select>;
+        function onPointerEnter() { self.overDropdown = true; }
+        function onPointerLeave() { self.overDropdown = false; }
+        return <select onChange={onChange} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>{items}</select>;
     }
 
     createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean }[]): JSX.Element {
@@ -209,7 +213,9 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        return <select onChange={e => onChange(e.target.value)}>{items}</select>;
+        function onPointerEnter() { self.overDropdown = true; }
+        function onPointerLeave() { self.overDropdown = false; }
+        return <select onChange={e => onChange(e.target.value)} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>{items}</select>;
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
@@ -274,8 +280,7 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
 
-    @action
-    toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+    @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
 
     createBrushButton() {
         const self = this;
@@ -648,9 +653,6 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     render() {
-        // if (!this.isVisible) return <></>;
-        SelectionManager.SelectedDocuments()
-        // if (this.Pinned || )
 
         const fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
@@ -689,7 +691,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
         ];
 
-        const buttons = [
+        const row1 = <div className="antimodeMenu-row">{[
             this.createButton("bold", "Bold", toggleMark(schema.marks.strong)),
             this.createButton("italic", "Italic", toggleMark(schema.marks.em)),
             this.createButton("underline", "Underline", toggleMark(schema.marks.underline)),
@@ -701,11 +703,18 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createLinkButton(),
             this.createBrushButton(),
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
+        ]}</div>
+
+        const row2 = <div className="antimodeMenu-row">{[
             this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
             this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions),
             this.createNodesDropdown(this.activeListType, listTypeOptions),
+        ]}</div>
+
+        const buttons = [
+            row1, row2
         ];
 
-        return this.getElement(buttons);
+        return this.getElementWithRows(buttons, 2);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index a45f3346a..f78b1dbfb 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -8,6 +8,15 @@
     // overflow: hidden;    
     display: flex;
 
+    &.with-rows {
+        flex-direction: column
+    }
+
+    .antimodeMenu-row {
+        display: flex;
+        height: 35px;
+    }
+
     .antimodeMenu-button {
         background-color: transparent;
         width: 35px;
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 2e9d29ef9..713a8a189 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -125,4 +125,14 @@ export default abstract class AntimodeMenu extends React.Component {
             </div>
         );
     }
+
+    protected getElementWithRows(rows: JSX.Element[], numRows: number) {
+        return (
+            <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
+                style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay, height: 35 * numRows + "px" }}>
+                {rows}
+                <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
+            </div>
+        );
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7d48a0859..7291d2e8e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -913,7 +913,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
 
         // jump rich text menu to this textbox
-        this._ref.current && RichTextMenu.Instance.jumpTo(this._ref.current.getBoundingClientRect().x, this._ref.current?.getBoundingClientRect().y - 70);
+        this._ref.current && RichTextMenu.Instance.jumpTo(this._ref.current.getBoundingClientRect().x, this._ref.current?.getBoundingClientRect().y - 105);
     }
     onPointerWheel = (e: React.WheelEvent): void => {
         // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
@@ -1053,6 +1053,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         });
     }
     onBlur = (e: any) => {
+        console.log("formated blur");
         //DictationManager.Controls.stop(false);
         if (this._undoTyping) {
             this._undoTyping.end();
@@ -1061,7 +1062,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this.doLinkOnDeselect();
 
         // move the richtextmenu offscreen
-        if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.jumpTo(-300, -300);
+        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overDropdown) RichTextMenu.Instance.jumpTo(-300, -300);
     }
 
     _lastTimedMark: Mark | undefined = undefined;
-- 
cgit v1.2.3-70-g09d2


From 89d3ec54886a588f3b7c8e46e771830eeefc3c8b Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 15:58:10 -0500
Subject: added logic so that text nodes started on the very left still show
 the full richtextmenu

---
 src/client/util/RichTextMenu.scss           |  9 ++++++
 src/client/util/RichTextMenu.tsx            | 49 ++++++++++++++++++++---------
 src/client/views/AntimodeMenu.tsx           |  6 +++-
 src/client/views/nodes/FormattedTextBox.tsx |  8 +++--
 4 files changed, 55 insertions(+), 17 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index 5a57f4b98..ff9270829 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -94,4 +94,13 @@ select {
     &::-ms-expand {
         color: white;
     }
+}
+
+.row-2 {
+    display: flex;
+    justify-content: space-between;
+
+    >div {
+        display: flex;
+    }
 }
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 4538a77d6..ae55dbd30 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -29,7 +29,7 @@ library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSub
 @observer
 export default class RichTextMenu extends AntimodeMenu {
     static Instance: RichTextMenu;
-    public overDropdown: boolean = false; // kind of hacky way to prevent selects not being selectable
+    public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
 
     private view?: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
@@ -188,9 +188,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        function onPointerEnter() { self.overDropdown = true; }
-        function onPointerLeave() { self.overDropdown = false; }
-        return <select onChange={onChange} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>{items}</select>;
+        return <select onChange={onChange}>{items}</select>;
     }
 
     createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean }[]): JSX.Element {
@@ -213,9 +211,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        function onPointerEnter() { self.overDropdown = true; }
-        function onPointerLeave() { self.overDropdown = false; }
-        return <select onChange={e => onChange(e.target.value)} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>{items}</select>;
+        return <select onChange={e => onChange(e.target.value)}>{items}</select>;
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
@@ -311,7 +307,9 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick}><FontAwesomeIcon icon="eye-dropper" size="lg" /></button>
+                <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks && this.brushMarks.size > 0 ? { backgroundColor: "121212" } : {}}>
+                    <FontAwesomeIcon icon="eye-dropper" size="lg" style={{ transition: "transform 0.1s", transform: this.brushMarks && this.brushMarks.size > 0 ? "rotate(45deg)" : "" }} />
+                </button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showBrushDropdown ?
                     (<div className="dropdown">
@@ -652,6 +650,17 @@ export default class RichTextMenu extends AntimodeMenu {
         return ref_node;
     }
 
+    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; }
+    @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
+
+    @action
+    toggleMenuPin = (e: React.MouseEvent) => {
+        this.Pinned = !this.Pinned;
+        if (!this.Pinned) {
+            this.fadeOut(true);
+        }
+    }
+
     render() {
 
         const fontSizeOptions = [
@@ -705,16 +714,28 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
         ]}</div>
 
-        const row2 = <div className="antimodeMenu-row">{[
-            this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
-            this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions),
-            this.createNodesDropdown(this.activeListType, listTypeOptions),
-        ]}</div>
+        const row2 = <div className="antimodeMenu-row row-2">
+            <div>{[
+                this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
+                this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions),
+                this.createNodesDropdown(this.activeListType, listTypeOptions),
+            ]}</div>
+            <div>
+                <button className="antimodeMenu-button" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
+                </button>
+                {this.getDragger()}
+            </div>
+        </div>
 
         const buttons = [
             row1, row2
         ];
 
-        return this.getElementWithRows(buttons, 2);
+        return (
+            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+                {this.getElementWithRows(buttons, 2)}
+            </div>
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 713a8a189..62ecdffaf 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -116,6 +116,10 @@ export default abstract class AntimodeMenu extends React.Component {
         e.preventDefault();
     }
 
+    protected getDragger = () => {
+        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
+    }
+
     protected getElement(buttons: JSX.Element[]) {
         return (
             <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
@@ -131,7 +135,7 @@ export default abstract class AntimodeMenu extends React.Component {
             <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
                 style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay, height: 35 * numRows + "px" }}>
                 {rows}
-                <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
+                {/* <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} /> */}
             </div>
         );
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7291d2e8e..4712b1974 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -913,7 +913,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
 
         // jump rich text menu to this textbox
-        this._ref.current && RichTextMenu.Instance.jumpTo(this._ref.current.getBoundingClientRect().x, this._ref.current?.getBoundingClientRect().y - 105);
+        if (this._ref.current) {
+            let x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().x, 0), window.innerWidth - 445);
+            let y = this._ref.current!.getBoundingClientRect().y - 105;
+            RichTextMenu.Instance.jumpTo(x, y);
+        }
     }
     onPointerWheel = (e: React.WheelEvent): void => {
         // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
@@ -1062,7 +1066,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this.doLinkOnDeselect();
 
         // move the richtextmenu offscreen
-        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overDropdown) RichTextMenu.Instance.jumpTo(-300, -300);
+        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
     }
 
     _lastTimedMark: Mark | undefined = undefined;
-- 
cgit v1.2.3-70-g09d2


From 531147df4186df3d4326748a5d083579cbc9e4c6 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 12:01:56 -0500
Subject: minor cleanup

---
 src/client/util/RichTextMenu.tsx  |  3 ++-
 src/client/views/AntimodeMenu.tsx | 25 ++++++++++++++++---------
 2 files changed, 18 insertions(+), 10 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index ae55dbd30..af2e7cc48 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -54,6 +54,7 @@ export default class RichTextMenu extends AntimodeMenu {
     constructor(props: Readonly<{}>) {
         super(props);
         RichTextMenu.Instance = this;
+        this._canFade = false;
     }
 
     @action
@@ -734,7 +735,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
-                {this.getElementWithRows(buttons, 2)}
+                {this.getElementWithRows(buttons, 2, false)}
             </div>
         );
     }
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 62ecdffaf..25bd90d87 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -18,6 +18,7 @@ export default abstract class AntimodeMenu extends React.Component {
     @observable protected _opacity: number = 1;
     @observable protected _transition: string = "opacity 0.5s";
     @observable protected _transitionDelay: string = "";
+    @observable protected _canFade: boolean = true;
 
     @observable public Pinned: boolean = false;
 
@@ -43,7 +44,7 @@ export default abstract class AntimodeMenu extends React.Component {
      * Called when you want the menu to disappear
      */
     public fadeOut = (forceOut: boolean) => {
-        if (!this.Pinned) {
+        if (!this.Pinned && this._canFade) {
             if (this._opacity === 0.2) {
                 this._transition = "opacity 0.1s";
                 this._transitionDelay = "";
@@ -62,12 +63,12 @@ export default abstract class AntimodeMenu extends React.Component {
 
     @action
     protected pointerLeave = (e: React.PointerEvent) => {
-        // if (!this.Pinned) {
-        //     this._transition = "opacity 0.5s";
-        //     this._transitionDelay = "1s";
-        //     this._opacity = 0.2;
-        //     setTimeout(() => this.fadeOut(false), 3000);
-        // }
+        if (!this.Pinned && this._canFade) {
+            this._transition = "opacity 0.5s";
+            this._transitionDelay = "1s";
+            this._opacity = 0.2;
+            setTimeout(() => this.fadeOut(false), 3000);
+        }
     }
 
     @action
@@ -100,6 +101,12 @@ export default abstract class AntimodeMenu extends React.Component {
         this._left = e.pageX - this._offsetX;
         this._top = e.pageY - this._offsetY;
 
+        // assumes dragger is on right
+        // let width = this._mainCont.current!.getBoundingClientRect().width;
+        // let height = this._mainCont.current!.getBoundingClientRect().width;
+        // this._left = Math.max(width, e.pageX - this._offsetX);
+        // this._top = Math.min(height, e.pageY - this._offsetY);
+
         e.stopPropagation();
         e.preventDefault();
     }
@@ -130,12 +137,12 @@ export default abstract class AntimodeMenu extends React.Component {
         );
     }
 
-    protected getElementWithRows(rows: JSX.Element[], numRows: number) {
+    protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) {
         return (
             <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
                 style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay, height: 35 * numRows + "px" }}>
                 {rows}
-                {/* <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} /> */}
+                {hasDragger ? <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} /> : <></>}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 8f030c9a8ca1f61f8430e5ea92c48a7a6b6f079f Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 13:32:13 -0500
Subject: bounded antimodemenu to the boundaries of the screen and minor
 styling of richtextmenu

---
 package-lock.json                           | 95 ++++++++++++-----------------
 src/client/util/RichTextMenu.scss           |  7 ++-
 src/client/util/RichTextMenu.tsx            | 19 +++---
 src/client/views/AntimodeMenu.tsx           | 25 ++++----
 src/client/views/nodes/FormattedTextBox.tsx | 22 +++----
 5 files changed, 79 insertions(+), 89 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/package-lock.json b/package-lock.json
index 2c70ba94d..cf61e6aab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2561,7 +2561,7 @@
         },
         "browserify-aes": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
                 "buffer-xor": "^1.0.3",
@@ -2595,7 +2595,7 @@
         },
         "browserify-rsa": {
             "version": "4.0.1",
-            "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -2632,7 +2632,7 @@
         },
         "buffer": {
             "version": "4.9.1",
-            "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+            "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
             "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
             "dev": true,
             "requires": {
@@ -2779,7 +2779,7 @@
         },
         "camelcase-keys": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
@@ -3563,7 +3563,7 @@
         },
         "create-hash": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
                 "cipher-base": "^1.0.1",
@@ -3575,7 +3575,7 @@
         },
         "create-hmac": {
             "version": "1.1.7",
-            "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+            "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
                 "cipher-base": "^1.0.3",
@@ -4118,7 +4118,7 @@
         },
         "diffie-hellman": {
             "version": "5.0.3",
-            "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+            "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -5361,8 +5361,7 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5380,13 +5379,11 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -5399,18 +5396,15 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5513,8 +5507,7 @@
                 },
                 "inherits": {
                     "version": "2.0.3",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5524,7 +5517,6 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5537,20 +5529,17 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "minipass": {
                     "version": "2.3.5",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5567,7 +5556,6 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5640,8 +5628,7 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -5651,7 +5638,6 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -5727,8 +5713,7 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -5758,7 +5743,6 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -5776,7 +5760,6 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -5815,13 +5798,11 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "yallist": {
                     "version": "3.0.3",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 }
             }
         },
@@ -7073,7 +7054,7 @@
                 "babel-runtime": "^6.22.0",
                 "bluebird": "^3.4.7",
                 "boolify-string": "^2.0.2",
-                "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name",
+                "emit-logger": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070",
                 "lodash": "^4.17.4",
                 "semver": "^5.0.3",
                 "source-map-support": "^0.5.9"
@@ -7081,7 +7062,7 @@
         },
         "is-accessor-descriptor": {
             "version": "0.1.6",
-            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+            "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7130,7 +7111,7 @@
         },
         "is-data-descriptor": {
             "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+            "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7915,7 +7896,7 @@
         },
         "load-json-file": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8229,7 +8210,7 @@
         },
         "media-typer": {
             "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+            "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
             "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "mem": {
@@ -8261,7 +8242,7 @@
         },
         "meow": {
             "version": "3.7.0",
-            "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+            "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
@@ -8454,7 +8435,7 @@
         },
         "mkdirp": {
             "version": "0.5.1",
-            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
             "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
             "requires": {
                 "minimist": "0.0.8"
@@ -8800,7 +8781,7 @@
         },
         "next-tick": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
             "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
         },
         "nice-try": {
@@ -8964,7 +8945,7 @@
                 },
                 "semver": {
                     "version": "5.3.0",
-                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+                    "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
                     "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "tar": {
@@ -12573,7 +12554,7 @@
         },
         "os-homedir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
             "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-locale": {
@@ -12586,7 +12567,7 @@
         },
         "os-tmpdir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
             "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
@@ -12826,7 +12807,7 @@
         },
         "path-is-absolute": {
             "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
@@ -14146,7 +14127,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -14491,7 +14472,7 @@
         },
         "safe-regex": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
@@ -14756,7 +14737,7 @@
         },
         "sha.js": {
             "version": "2.4.11",
-            "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+            "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
                 "inherits": "^2.0.1",
@@ -15499,7 +15480,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -15529,7 +15510,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -15545,7 +15526,7 @@
         },
         "strip-eof": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
             "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
         },
         "strip-indent": {
@@ -16298,7 +16279,7 @@
         },
         "tty-browserify": {
             "version": "0.0.0",
-            "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
             "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
             "dev": true
         },
@@ -17740,7 +17721,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index ff9270829..6383e0b7b 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -21,6 +21,7 @@
         min-width: 150px;
         padding: 5px;
         font-size: 12px;
+        z-index: 10001;
 
         button {
             background-color: #323232;
@@ -28,11 +29,15 @@
             border-radius: 1px;
             padding: 6px;
             margin: 5px 0;
-            font-size: 12px;
+            font-size: 10px;
 
             &:hover {
                 background-color: black;
             }
+
+            &:last-child {
+                margin-bottom: 0;
+            }
         }
     }
 
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index af2e7cc48..ca6b90e36 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -8,7 +8,7 @@ import { EditorView } from "prosemirror-view";
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller } from "@fortawesome/free-solid-svg-icons";
 import { MenuItem, Dropdown } from "prosemirror-menu";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../views/nodes/FieldView";
@@ -24,7 +24,7 @@ import { SelectionManager } from "./SelectionManager";
 import { LinkManager } from "./LinkManager";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 
-library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink);
+library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
 
 @observer
 export default class RichTextMenu extends AntimodeMenu {
@@ -86,8 +86,9 @@ export default class RichTextMenu extends AntimodeMenu {
         const activeFamilies = active && active.get("families");
         const activeSizes = active && active.get("sizes");
 
-        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
+        console.log("update from dash, activefontsize", this.activeFontSize, activeSizes, activeSizes && activeSizes.length, activeSizes && String(activeSizes[0]));
+        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
 
         // update link in current selection
         const targetTitle = await this.getTextLinkTargetTitle();
@@ -99,7 +100,7 @@ export default class RichTextMenu extends AntimodeMenu {
     setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
         if (mark) {
             const node = (state.selection as NodeSelection).node;
-            if (node?.type === schema.nodes.ordered_list) {
+            if (node ?.type === schema.nodes.ordered_list) {
                 let attrs = node.attrs;
                 if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
                 if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
@@ -142,7 +143,7 @@ export default class RichTextMenu extends AntimodeMenu {
     getMarksInSelection(state: EditorState<any>) {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m)));
+        state.doc.nodesBetween(from, to, (node) => node.marks ?.forEach(m => found.add(m)));
         return found;
     }
 
@@ -309,7 +310,7 @@ export default class RichTextMenu extends AntimodeMenu {
         return (
             <div className="button-dropdown-wrapper">
                 <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks && this.brushMarks.size > 0 ? { backgroundColor: "121212" } : {}}>
-                    <FontAwesomeIcon icon="eye-dropper" size="lg" style={{ transition: "transform 0.1s", transform: this.brushMarks && this.brushMarks.size > 0 ? "rotate(45deg)" : "" }} />
+                    <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transition: "transform 0.1s", transform: this.brushMarks && this.brushMarks.size > 0 ? "rotate(45deg)" : "" }} />
                 </button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showBrushDropdown ?
@@ -679,7 +680,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { 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: "default", 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
         ];
 
         const fontFamilyOptions = [
@@ -691,7 +692,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily },
             { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily },
             { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
-            { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
+            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
         ];
 
         const listTypeOptions = [
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 25bd90d87..e358d88cf 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -22,6 +22,9 @@ export default abstract class AntimodeMenu extends React.Component {
 
     @observable public Pinned: boolean = false;
 
+    get width() { return this._mainCont.current ? this._mainCont.current.getBoundingClientRect().width : 0; }
+    get height() { return this._mainCont.current ? this._mainCont.current.getBoundingClientRect().height : 0; }
+
     @action
     /**
      * @param x
@@ -44,7 +47,7 @@ export default abstract class AntimodeMenu extends React.Component {
      * Called when you want the menu to disappear
      */
     public fadeOut = (forceOut: boolean) => {
-        if (!this.Pinned && this._canFade) {
+        if (!this.Pinned) {
             if (this._opacity === 0.2) {
                 this._transition = "opacity 0.1s";
                 this._transitionDelay = "";
@@ -89,8 +92,8 @@ export default abstract class AntimodeMenu extends React.Component {
         document.removeEventListener("pointerup", this.dragEnd);
         document.addEventListener("pointerup", this.dragEnd);
 
-        this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX;
-        this._offsetY = e.nativeEvent.offsetY;
+        this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
+        this._offsetY = e.pageY - this._mainCont.current!.getBoundingClientRect().top;
 
         e.stopPropagation();
         e.preventDefault();
@@ -98,14 +101,14 @@ export default abstract class AntimodeMenu extends React.Component {
 
     @action
     protected dragging = (e: PointerEvent) => {
-        this._left = e.pageX - this._offsetX;
-        this._top = e.pageY - this._offsetY;
-
-        // assumes dragger is on right
-        // let width = this._mainCont.current!.getBoundingClientRect().width;
-        // let height = this._mainCont.current!.getBoundingClientRect().width;
-        // this._left = Math.max(width, e.pageX - this._offsetX);
-        // this._top = Math.min(height, e.pageY - this._offsetY);
+        const width = this._mainCont.current!.getBoundingClientRect().width;
+        const height = this._mainCont.current!.getBoundingClientRect().height;
+
+        const left = e.pageX - this._offsetX;
+        const top = e.pageY - this._offsetY;
+
+        this._left = Math.min(Math.max(left, 0), window.innerWidth - width);
+        this._top = Math.min(Math.max(top, 0), window.innerHeight - height);
 
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 4712b1974..027bd492c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -906,16 +906,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this.tryUpdateHeight();
 
         // see if we need to preserve the insertion point
-        const prosediv = this.ProseRef?.children?.[0] as any;
-        const keeplocation = prosediv?.keeplocation;
+        const prosediv = this.ProseRef ?.children ?.[0] as any;
+        const keeplocation = prosediv ?.keeplocation;
         prosediv && (prosediv.keeplocation = undefined);
-        const pos = this._editorView?.state.selection.$from.pos || 1;
-        keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+        const pos = this._editorView ?.state.selection.$from.pos || 1;
+        keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
 
         // jump rich text menu to this textbox
         if (this._ref.current) {
-            let x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().x, 0), window.innerWidth - 445);
-            let y = this._ref.current!.getBoundingClientRect().y - 105;
+            const x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
+            const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50;
             RichTextMenu.Instance.jumpTo(x, y);
         }
     }
@@ -933,7 +933,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
             const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
             const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
-            if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
+            if (pcords && node ?.type === this._editorView!.state.schema.nodes.dashComment) {
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
                 e.preventDefault();
             }
@@ -996,7 +996,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 for (let off = 1; off < 100; off++) {
                     const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
                     const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
-                    if (node?.type === schema.nodes.list_item) {
+                    if (node ?.type === schema.nodes.list_item) {
                         list_node = node;
                         break;
                     }
@@ -1088,7 +1088,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         }
         if (e.key === "Escape") {
             this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-            (document.activeElement as any).blur?.();
+            (document.activeElement as any).blur ?.();
             SelectionManager.DeselectAll();
         }
         e.stopPropagation();
@@ -1110,7 +1110,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     @action
     tryUpdateHeight(limitHeight?: number) {
-        let scrollHeight = this._ref.current?.scrollHeight;
+        let scrollHeight = this._ref.current ?.scrollHeight;
         if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
             getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             if (limitHeight && scrollHeight > limitHeight) {
@@ -1173,7 +1173,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
                     <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
                     <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
-                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}>
+                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc ?.backgroundColor, "transparent")}` }}>
                         <CollectionFreeFormView {...this.props}
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={() => this.sidebarWidth}
-- 
cgit v1.2.3-70-g09d2


From 3b08b8c340bf889c99f7b2fa76cda0f8b2d4b0a1 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 15:03:54 -0500
Subject: refactored dropdown buttons into a buttondropdown component for
 richtextmenu

---
 src/client/util/RichTextMenu.tsx            | 382 +++++++++++++++-------------
 src/client/views/nodes/FormattedTextBox.tsx |   1 -
 2 files changed, 206 insertions(+), 177 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index ca6b90e36..2d4f0fc1b 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -34,6 +34,12 @@ export default class RichTextMenu extends AntimodeMenu {
     private view?: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
 
+    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean }[];
+    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean }[];
+    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any }[];
+    private fontColors: (string | undefined)[];
+    private highlightColors: (string | undefined)[];
+
     @observable private activeFontSize: string = "";
     @observable private activeFontFamily: string = "";
     @observable private activeListType: string = "";
@@ -55,6 +61,69 @@ export default class RichTextMenu extends AntimodeMenu {
         super(props);
         RichTextMenu.Instance = this;
         this._canFade = false;
+
+        this.fontSizeOptions = [
+            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
+            { 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: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
+        ];
+
+        this.fontFamilyOptions = [
+            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily },
+            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
+        ];
+
+        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: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
+        ];
+
+        this.fontColors = [
+            DarkPastelSchemaPalette.get("pink2"),
+            DarkPastelSchemaPalette.get("purple4"),
+            DarkPastelSchemaPalette.get("bluegreen1"),
+            DarkPastelSchemaPalette.get("yellow4"),
+            DarkPastelSchemaPalette.get("red2"),
+            DarkPastelSchemaPalette.get("bluegreen7"),
+            DarkPastelSchemaPalette.get("bluegreen5"),
+            DarkPastelSchemaPalette.get("orange1"),
+            "#757472",
+            "#000"
+        ];
+
+        this.highlightColors = [
+            PastelSchemaPalette.get("pink2"),
+            PastelSchemaPalette.get("purple4"),
+            PastelSchemaPalette.get("bluegreen1"),
+            PastelSchemaPalette.get("yellow4"),
+            PastelSchemaPalette.get("red2"),
+            PastelSchemaPalette.get("bluegreen7"),
+            PastelSchemaPalette.get("bluegreen5"),
+            PastelSchemaPalette.get("orange1"),
+            "white",
+            "transparent"
+        ];
     }
 
     @action
@@ -74,27 +143,23 @@ export default class RichTextMenu extends AntimodeMenu {
         }
         this.view = view;
         const state = view.state;
-        // DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
+
         // Don't do anything if the document/selection didn't change
         if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
 
-        // this.reset_mark_doms();
 
         // update active font family and size
         const active = this.getActiveFontStylesOnSelection();
         const activeFamilies = active && active.get("families");
         const activeSizes = active && active.get("sizes");
 
-        console.log("update from dash, activefontsize", this.activeFontSize, activeSizes, activeSizes && activeSizes.length, activeSizes && String(activeSizes[0]));
         this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
         this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
 
         // update link in current selection
         const targetTitle = await this.getTextLinkTargetTitle();
         this.setCurrentLink(targetTitle);
-
-        // this.update_mark_doms();
     }
 
     setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
@@ -134,7 +199,7 @@ export default class RichTextMenu extends AntimodeMenu {
             });
         }
 
-        let styles = new Map<String, String[]>();
+        const styles = new Map<String, String[]>();
         styles.set("families", activeFamilies);
         styles.set("sizes", activeSizes);
         return styles;
@@ -183,7 +248,6 @@ export default class RichTextMenu extends AntimodeMenu {
         function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
             e.stopPropagation();
             e.preventDefault();
-            console.log("on change marks");
             options.forEach(({ label, mark, command }) => {
                 if (e.target.value === label) {
                     self.view && mark && command(mark, self.view);
@@ -230,7 +294,6 @@ export default class RichTextMenu extends AntimodeMenu {
 
     changeFontFamily = (mark: Mark, view: EditorView) => {
         const fontName = mark.attrs.family;
-        // if (fontName) { this.updateFontStyleDropdown(fontName); }
         if (this.editorProps) {
             const ruleProvider = this.editorProps.ruleProvider;
             const heading = NumCast(this.editorProps.Document.heading);
@@ -288,12 +351,6 @@ export default class RichTextMenu extends AntimodeMenu {
             self.view && self.view.focus();
             self.view && self.fillBrush(self.view.state, self.view.dispatch);
         }
-        function onDropdownClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.toggleBrushDropdown();
-        }
 
         let label = "Stored marks: ";
         if (this.brushMarks && this.brushMarks.size > 0) {
@@ -307,20 +364,20 @@ export default class RichTextMenu extends AntimodeMenu {
             label = "No marks are currently stored";
         }
 
+        const button =
+            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks && this.brushMarks.size > 0 ? { backgroundColor: "121212" } : {}}>
+                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transition: "transform 0.1s", transform: this.brushMarks && this.brushMarks.size > 0 ? "rotate(45deg)" : "" }} />
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>{label}</p>
+                <button onPointerDown={this.clearBrush}>Clear brush</button>
+                {/* <input placeholder="Enter URL"></input> */}
+            </div>;
+
         return (
-            <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks && this.brushMarks.size > 0 ? { backgroundColor: "121212" } : {}}>
-                    <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transition: "transform 0.1s", transform: this.brushMarks && this.brushMarks.size > 0 ? "rotate(45deg)" : "" }} />
-                </button>
-                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showBrushDropdown ?
-                    (<div className="dropdown">
-                        <p>{label}</p>
-                        <button onPointerDown={this.clearBrush}>Clear brush</button>
-                        {/* <input placeholder="Enter URL"></input> */}
-                    </div>)
-                    : <></>}
-            </div>
+            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -340,7 +397,6 @@ export default class RichTextMenu extends AntimodeMenu {
                 this.brushMarks = selected_marks;
                 this.brushIsEmpty = !this.brushIsEmpty;
             }
-            // }
         }
         else {
             const { from, to, $from } = this.view.state.selection;
@@ -369,12 +425,6 @@ export default class RichTextMenu extends AntimodeMenu {
             self.view && self.view.focus();
             self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
         }
-        function onDropdownClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.toggleColorDropdown();
-        }
         function changeColor(e: React.PointerEvent, color: string) {
             e.preventDefault();
             e.stopPropagation();
@@ -383,41 +433,28 @@ export default class RichTextMenu extends AntimodeMenu {
             self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
         }
 
-        const colors = [
-            DarkPastelSchemaPalette.get("pink2"),
-            DarkPastelSchemaPalette.get("purple4"),
-            DarkPastelSchemaPalette.get("bluegreen1"),
-            DarkPastelSchemaPalette.get("yellow4"),
-            DarkPastelSchemaPalette.get("red2"),
-            DarkPastelSchemaPalette.get("bluegreen7"),
-            DarkPastelSchemaPalette.get("bluegreen5"),
-            DarkPastelSchemaPalette.get("orange1"),
-            "#757472",
-            "#000"
-        ];
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
+                <FontAwesomeIcon icon="palette" size="lg" />
+                <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>Change font color:</p>
+                <div className="color-wrapper">
+                    {this.fontColors.map(color => {
+                        if (color) {
+                            return this.activeFontColor === color ?
+                                <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+                                <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+                        }
+                    })}
+                </div>
+            </div>;
 
         return (
-            <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
-                    <FontAwesomeIcon icon="palette" size="lg" />
-                    <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
-                </button>
-                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showColorDropdown ?
-                    (<div className="dropdown">
-                        <p>Change font color:</p>
-                        <div className="color-wrapper">
-                            {colors.map(color => {
-                                if (color) {
-                                    return this.activeFontColor === color ?
-                                        <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
-                                        <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
-                                }
-                            })}
-                        </div>
-                    </div>)
-                    : <></>}
-            </div>
+            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -441,12 +478,6 @@ export default class RichTextMenu extends AntimodeMenu {
             self.view && self.view.focus();
             self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
         }
-        function onDropdownClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.toggleHighlightDropdown();
-        }
         function changeHighlight(e: React.PointerEvent, color: string) {
             e.preventDefault();
             e.stopPropagation();
@@ -455,41 +486,28 @@ export default class RichTextMenu extends AntimodeMenu {
             self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
         }
 
-        const colors = [
-            PastelSchemaPalette.get("pink2"),
-            PastelSchemaPalette.get("purple4"),
-            PastelSchemaPalette.get("bluegreen1"),
-            PastelSchemaPalette.get("yellow4"),
-            PastelSchemaPalette.get("red2"),
-            PastelSchemaPalette.get("bluegreen7"),
-            PastelSchemaPalette.get("bluegreen5"),
-            PastelSchemaPalette.get("orange1"),
-            "white",
-            "transparent"
-        ];
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}>
+                <FontAwesomeIcon icon="highlighter" size="lg" />
+                <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>Change highlight color:</p>
+                <div className="color-wrapper">
+                    {this.highlightColors.map(color => {
+                        if (color) {
+                            return this.activeHighlightColor === color ?
+                                <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+                                <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+                        }
+                    })}
+                </div>
+            </div>;
 
         return (
-            <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}>
-                    <FontAwesomeIcon icon="highlighter" size="lg" />
-                    <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
-                </button>
-                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showHighlightDropdown ?
-                    (<div className="dropdown">
-                        <p>Change highlight color:</p>
-                        <div className="color-wrapper">
-                            {colors.map(color => {
-                                if (color) {
-                                    return this.activeHighlightColor === color ?
-                                        <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
-                                        <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
-                                }
-                            })}
-                        </div>
-                    </div>)
-                    : <></>}
-            </div>
+            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -503,32 +521,26 @@ export default class RichTextMenu extends AntimodeMenu {
 
     createLinkButton() {
         const self = this;
-        function onDropdownClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.toggleLinkDropdown();
-        }
+
         function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
             self.setCurrentLink(e.target.value);
         }
 
         const link = this.currentLink ? this.currentLink : "";
 
+        const button = <button className="antimodeMenu-button" title=""><FontAwesomeIcon icon="link" size="lg" /></button>;
+
+        const dropdownContent =
+            <div className="dropdown link-menu">
+                <p>Linked to:</p>
+                <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
+                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+                <div className="divider"></div>
+                <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
+            </div>;
+
         return (
-            <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="link" size="lg" /></button>
-                <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
-                {this.showLinkDropdown ?
-                    (<div className="dropdown link-menu">
-                        <p>Linked to:</p>
-                        <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
-                        <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
-                        <div className="divider"></div>
-                        <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
-                    </div>)
-                    : <></>}
-            </div>
+            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
         );
     }
 
@@ -598,7 +610,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             } else {
                 if (node) {
-                    let extension = this.linkExtend(this.view!.state.selection.$anchor, href);
+                    const extension = this.linkExtend(this.view!.state.selection.$anchor, href);
                     this.view!.dispatch(this.view!.state.tr.removeMark(extension.from, extension.to, this.view!.state.schema.marks.link));
                 }
             }
@@ -617,7 +629,7 @@ export default class RichTextMenu extends AntimodeMenu {
         let startPos = $start.start();
         let endPos = startPos;
         for (let i = 0; i < endIndex; i++) {
-            let size = $start.parent.child(i).nodeSize;
+            const size = $start.parent.child(i).nodeSize;
             if (i < startIndex) startPos += size;
             endPos += size;
         }
@@ -665,43 +677,6 @@ export default class RichTextMenu extends AntimodeMenu {
 
     render() {
 
-        const fontSizeOptions = [
-            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
-            { 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: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
-        ];
-
-        const fontFamilyOptions = [
-            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
-            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
-        ];
-
-        const 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: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
-        ];
-
         const row1 = <div className="antimodeMenu-row">{[
             this.createButton("bold", "Bold", toggleMark(schema.marks.strong)),
             this.createButton("italic", "Italic", toggleMark(schema.marks.em)),
@@ -714,30 +689,85 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createLinkButton(),
             this.createBrushButton(),
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
-        ]}</div>
+        ]}</div>;
 
         const row2 = <div className="antimodeMenu-row row-2">
-            <div>{[
-                this.createMarksDropdown(this.activeFontSize, fontSizeOptions),
-                this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions),
-                this.createNodesDropdown(this.activeListType, listTypeOptions),
-            ]}</div>
+            <div>
+                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions),
+                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions),
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions)]}
+            </div>
             <div>
                 <button className="antimodeMenu-button" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
                     <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
                 </button>
                 {this.getDragger()}
             </div>
-        </div>
-
-        const buttons = [
-            row1, row2
-        ];
+        </div>;
 
         return (
             <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
-                {this.getElementWithRows(buttons, 2, false)}
+                {this.getElementWithRows([row1, row2], 2, false)}
             </div>
         );
     }
+}
+
+interface ButtonDropdownProps {
+    // Document: Doc;
+    // remove: (self: ImportMetadataEntry) => void;
+    // next: () => void;
+    view?: EditorView;
+    button: JSX.Element;
+    dropdownContent: JSX.Element;
+    openDropdownOnButton?: boolean;
+}
+
+@observer
+class ButtonDropdown extends React.Component<ButtonDropdownProps> {
+
+    @observable private showDropdown: boolean = false;
+    private ref: HTMLDivElement | null = null;
+
+    componentDidMount() {
+        document.addEventListener("pointerdown", this.onBlur);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onBlur);
+    }
+
+    @action
+    setShowDropdown(show: boolean) {
+        this.showDropdown = show;
+    }
+    @action
+    toggleDropdown() {
+        this.showDropdown = !this.showDropdown;
+    }
+
+    onDropdownClick = (e: React.PointerEvent) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.props.view && this.props.view.focus();
+        this.toggleDropdown();
+    }
+
+    onBlur = (e: PointerEvent) => {
+        setTimeout(() => {
+            if (this.ref !== null && !this.ref.contains(e.target as Node)) {
+                this.setShowDropdown(false);
+            }
+        }, 0);
+    }
+
+    render() {
+        return (
+            <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
+                {this.props.openDropdownOnButton ? <div onPointerDown={this.onDropdownClick}>{this.props.button}</div> : this.props.button}
+                <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+                {this.showDropdown ? this.props.dropdownContent : <></>}
+            </div>
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 027bd492c..df055a2ab 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1057,7 +1057,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         });
     }
     onBlur = (e: any) => {
-        console.log("formated blur");
         //DictationManager.Controls.stop(false);
         if (this._undoTyping) {
             this._undoTyping.end();
-- 
cgit v1.2.3-70-g09d2


From 47700705b6e0304eaed00843959561b5baa0a31a Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 15:14:19 -0500
Subject: added styling options to select dropdowns on rich text menu

---
 src/client/util/RichTextMenu.tsx | 45 ++++++++++++++++++++--------------------
 1 file changed, 23 insertions(+), 22 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 2d4f0fc1b..da5979f57 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -34,9 +34,9 @@ export default class RichTextMenu extends AntimodeMenu {
     private view?: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
 
-    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean }[];
-    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean }[];
-    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any }[];
+    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
+    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
+    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
     private fontColors: (string | undefined)[];
     private highlightColors: (string | undefined)[];
 
@@ -81,13 +81,13 @@ export default class RichTextMenu extends AntimodeMenu {
         ];
 
         this.fontFamilyOptions = [
-            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily },
-            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily },
+            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } },
             { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
             // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
         ];
@@ -154,6 +154,7 @@ export default class RichTextMenu extends AntimodeMenu {
         const activeFamilies = active && active.get("families");
         const activeSizes = active && active.get("sizes");
 
+        console.log("active families", activeFamilies);
         this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
         this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
 
@@ -232,16 +233,16 @@ export default class RichTextMenu extends AntimodeMenu {
         );
     }
 
-    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean }[]): JSX.Element {
-        const items = options.map(({ title, label, hidden }) => {
+    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[]): JSX.Element {
+        const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
-                    <option value={label} title={title} selected hidden>{label}</option> :
-                    <option value={label} title={title} hidden>{label}</option>;
+                    <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
             }
             return label === activeOption ?
-                <option value={label} title={title} selected>{label}</option> :
-                <option value={label} title={title}>{label}</option>;
+                <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
@@ -257,16 +258,16 @@ export default class RichTextMenu extends AntimodeMenu {
         return <select onChange={onChange}>{items}</select>;
     }
 
-    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean }[]): JSX.Element {
-        const items = options.map(({ title, label, hidden }) => {
+    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[]): JSX.Element {
+        const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
-                    <option value={label} title={title} selected hidden>{label}</option> :
-                    <option value={label} title={title} hidden>{label}</option>;
+                    <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
             }
             return label === activeOption ?
-                <option value={label} title={title} selected>{label}</option> :
-                <option value={label} title={title}>{label}</option>;
+                <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
-- 
cgit v1.2.3-70-g09d2


From e0a8d325d601d305a166c1683dccfcc67e91fe95 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 15:29:47 -0500
Subject: code cleaning richtextmenu

---
 src/client/util/RichTextMenu.tsx | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index da5979f57..454afe3b7 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -155,6 +155,7 @@ export default class RichTextMenu extends AntimodeMenu {
         const activeSizes = active && active.get("sizes");
 
         console.log("active families", activeFamilies);
+        console.log("other active families", this.activeFontFamilyOnSelection());
         this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
         this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
 
@@ -184,10 +185,26 @@ export default class RichTextMenu extends AntimodeMenu {
         }
     }
 
+    activeFontFamilyOnSelection() {
+        if (!this.view) return;
+
+        //current selection
+        const state = this.view.state;
+        const activeFamilies: string[] = [];
+        const pos = this.view.state.selection.$from;
+        const ref_node: ProsNode | null = this.reference_node(pos);
+        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+            ref_node.marks.forEach(m => m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family));
+        }
+        return activeFamilies;
+    }
+
     // finds font sizes and families in selection
     getActiveFontStylesOnSelection() {
         if (!this.view) return;
 
+        console.log("\nget active font styles");
+
         const activeFamilies: string[] = [];
         const activeSizes: string[] = [];
         const state = this.view.state;
@@ -195,6 +212,7 @@ export default class RichTextMenu extends AntimodeMenu {
         const ref_node = this.reference_node(pos);
         if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
             ref_node.marks.forEach(m => {
+                console.log("attribute", m.attrs);
                 m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
                 m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
             });
-- 
cgit v1.2.3-70-g09d2


From 5abc4de3c3495db71b0a9bcbe2468828f674eff3 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 16:41:52 -0500
Subject: minor merge

---
 package-lock.json                   |  6 +-----
 src/client/util/RichTextMenu.tsx    |  4 ++--
 src/client/util/TooltipTextMenu.tsx | 14 ++++++--------
 src/client/views/AntimodeMenu.tsx   |  2 +-
 4 files changed, 10 insertions(+), 16 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/package-lock.json b/package-lock.json
index cad3edd82..6aa0ffc02 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7073,11 +7073,7 @@
                 "babel-runtime": "^6.22.0",
                 "bluebird": "^3.4.7",
                 "boolify-string": "^2.0.2",
-<<<<<<< HEAD
-                "emit-logger": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070",
-=======
                 "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name",
->>>>>>> 540bda7295f6ee7c2eed848598de6f5df74b2723
                 "lodash": "^4.17.4",
                 "semver": "^5.0.3",
                 "source-map-support": "^0.5.9"
@@ -18091,4 +18087,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index e367cf350..375c10da3 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -208,7 +208,7 @@ export default class RichTextMenu extends AntimodeMenu {
     getMarksInSelection(state: EditorState<any>) {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => node.marks ?.forEach(m => found.add(m)));
+        state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
         return found;
     }
 
@@ -768,6 +768,6 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> {
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showDropdown ? this.props.dropdownContent : <></>}
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 8177593c2..1c15dca7f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -18,8 +18,6 @@ import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
 const { toggleMark } = require("prosemirror-commands");
 
-// deprecated in favor of richtextmenu
-
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
     public static Toolbar: HTMLDivElement | undefined;
@@ -95,7 +93,7 @@ export class TooltipTextMenu {
             { mark: schema.marks.subscript, dom: svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
         ];
 
-        basicItems.map(({ dom, mark }) => this.basicTools?.appendChild(dom.cloneNode(true)));
+        basicItems.map(({ dom, mark }) => this.basicTools ?.appendChild(dom.cloneNode(true)));
         basicItems.concat(items).forEach(({ dom, mark }) => {
             this.tooltip.appendChild(dom);
             this._marksToDoms.set(mark, dom);
@@ -476,7 +474,7 @@ export class TooltipTextMenu {
                 const node = self.view.state.selection.$from.nodeAfter;
                 const link = node && node.marks.find(m => m.type === self.view.state.schema.marks.link);
                 const href = link!.attrs.href;
-                if (href?.indexOf(Utils.prepend("/doc/")) === 0) {
+                if (href ?.indexOf(Utils.prepend("/doc/")) === 0) {
                     const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     linkclicked && DocServer.GetRefField(linkclicked).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
@@ -502,7 +500,7 @@ export class TooltipTextMenu {
         const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, 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 this.view.state.selection.$from.nodeAfter ?.text || "";
     }
 
     // SUMMARIZER TOOL
@@ -512,7 +510,7 @@ export class TooltipTextMenu {
             const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
             const content = tr.selection.content();
             const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
-            dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+            dispatch ?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         }
     }
 
@@ -739,7 +737,7 @@ export class TooltipTextMenu {
             // get marks in the selection
             const selected_marks = new Set<Mark>();
             const { from, to } = state.selection as TextSelection;
-            state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => selected_marks.add(m)));
+            state.doc.nodesBetween(from, to, (node) => node.marks ?.forEach(m => selected_marks.add(m)));
 
             if (this._brushdom && selected_marks.size >= 0) {
                 TooltipTextMenuManager.Instance._brushMarks = selected_marks;
@@ -851,7 +849,7 @@ export class TooltipTextMenu {
     static setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
         if (mark) {
             const node = (state.selection as NodeSelection).node;
-            if (node?.type === schema.nodes.ordered_list) {
+            if (node ?.type === schema.nodes.ordered_list) {
                 let attrs = node.attrs;
                 if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
                 if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index e358d88cf..4625eb92f 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -127,7 +127,7 @@ export default abstract class AntimodeMenu extends React.Component {
     }
 
     protected getDragger = () => {
-        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
+        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />;
     }
 
     protected getElement(buttons: JSX.Element[]) {
-- 
cgit v1.2.3-70-g09d2


From af7e82e1c5314571457724e351aba191b20a0d01 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 16:48:06 -0500
Subject: minor clean

---
 src/client/util/RichTextMenu.tsx | 3 ---
 1 file changed, 3 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 375c10da3..a5de8d7a9 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -714,9 +714,6 @@ export default class RichTextMenu extends AntimodeMenu {
 }
 
 interface ButtonDropdownProps {
-    // Document: Doc;
-    // remove: (self: ImportMetadataEntry) => void;
-    // next: () => void;
     view?: EditorView;
     button: JSX.Element;
     dropdownContent: JSX.Element;
-- 
cgit v1.2.3-70-g09d2


From 579010cc792ce961618406886961d257be2c74db Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 18:13:28 -0500
Subject: condensed the link button and made buttons dark when they are active
 in the selection

---
 package-lock.json                  | 91 +++++++++++++++-----------------------
 src/client/util/RichTextMenu.scss  | 10 +++++
 src/client/util/RichTextMenu.tsx   | 67 +++++++++++++++++++++++-----
 src/client/views/AntimodeMenu.scss |  4 ++
 4 files changed, 105 insertions(+), 67 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/package-lock.json b/package-lock.json
index 6aa0ffc02..cf61e6aab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1978,7 +1978,7 @@
                 },
                 "util": {
                     "version": "0.10.3",
-                    "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+                    "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
                     "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
                     "dev": true,
                     "requires": {
@@ -2561,7 +2561,7 @@
         },
         "browserify-aes": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
                 "buffer-xor": "^1.0.3",
@@ -2595,7 +2595,7 @@
         },
         "browserify-rsa": {
             "version": "4.0.1",
-            "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -2779,7 +2779,7 @@
         },
         "camelcase-keys": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
@@ -3563,7 +3563,7 @@
         },
         "create-hash": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
                 "cipher-base": "^1.0.1",
@@ -3575,7 +3575,7 @@
         },
         "create-hmac": {
             "version": "1.1.7",
-            "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+            "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
                 "cipher-base": "^1.0.3",
@@ -4118,7 +4118,7 @@
         },
         "diffie-hellman": {
             "version": "5.0.3",
-            "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+            "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -5361,8 +5361,7 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5380,13 +5379,11 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -5399,18 +5396,15 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5513,8 +5507,7 @@
                 },
                 "inherits": {
                     "version": "2.0.3",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5524,7 +5517,6 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5537,20 +5529,17 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "minipass": {
                     "version": "2.3.5",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5567,7 +5556,6 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5640,8 +5628,7 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -5651,7 +5638,6 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -5727,8 +5713,7 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -5758,7 +5743,6 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -5776,7 +5760,6 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -5815,13 +5798,11 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "yallist": {
                     "version": "3.0.3",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 }
             }
         },
@@ -7073,7 +7054,7 @@
                 "babel-runtime": "^6.22.0",
                 "bluebird": "^3.4.7",
                 "boolify-string": "^2.0.2",
-                "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name",
+                "emit-logger": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070",
                 "lodash": "^4.17.4",
                 "semver": "^5.0.3",
                 "source-map-support": "^0.5.9"
@@ -7081,7 +7062,7 @@
         },
         "is-accessor-descriptor": {
             "version": "0.1.6",
-            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+            "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7130,7 +7111,7 @@
         },
         "is-data-descriptor": {
             "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+            "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7915,7 +7896,7 @@
         },
         "load-json-file": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8229,7 +8210,7 @@
         },
         "media-typer": {
             "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+            "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
             "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "mem": {
@@ -8261,7 +8242,7 @@
         },
         "meow": {
             "version": "3.7.0",
-            "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+            "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
@@ -8454,7 +8435,7 @@
         },
         "mkdirp": {
             "version": "0.5.1",
-            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
             "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
             "requires": {
                 "minimist": "0.0.8"
@@ -8800,7 +8781,7 @@
         },
         "next-tick": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
             "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
         },
         "nice-try": {
@@ -8964,7 +8945,7 @@
                 },
                 "semver": {
                     "version": "5.3.0",
-                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+                    "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
                     "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "tar": {
@@ -12573,7 +12554,7 @@
         },
         "os-homedir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
             "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-locale": {
@@ -12586,7 +12567,7 @@
         },
         "os-tmpdir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
             "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
@@ -12826,7 +12807,7 @@
         },
         "path-is-absolute": {
             "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
@@ -14491,7 +14472,7 @@
         },
         "safe-regex": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
@@ -14756,7 +14737,7 @@
         },
         "sha.js": {
             "version": "2.4.11",
-            "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+            "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
                 "inherits": "^2.0.1",
@@ -15499,7 +15480,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -15545,7 +15526,7 @@
         },
         "strip-eof": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
             "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
         },
         "strip-indent": {
@@ -16298,7 +16279,7 @@
         },
         "tty-browserify": {
             "version": "0.0.0",
-            "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
             "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
             "dev": true
         },
@@ -18087,4 +18068,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index 6383e0b7b..43cc23ecd 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -9,6 +9,16 @@
         padding-right: 5px;
     }
 
+    .dropdown-button-combined {
+        width: 50px;
+        display: flex;
+        justify-content: space-between;
+
+        svg:nth-child(2) {
+            margin-top: 2px;
+        }
+    }
+
     .dropdown {
         position: absolute;
         top: 35px;
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index a5de8d7a9..ec79fbf85 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -8,7 +8,7 @@ import { EditorView } from "prosemirror-view";
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
 import { MenuItem, Dropdown } from "prosemirror-menu";
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../views/nodes/FieldView";
@@ -40,6 +40,13 @@ export default class RichTextMenu extends AntimodeMenu {
     private fontColors: (string | undefined)[];
     private highlightColors: (string | undefined)[];
 
+    @observable private boldActive: boolean = false;
+    @observable private italicsActive: boolean = false;
+    @observable private underlineActive: boolean = false;
+    @observable private strikethroughActive: boolean = false;
+    @observable private subscriptActive: boolean = false;
+    @observable private superscriptActive: boolean = false;
+
     @observable private activeFontSize: string = "";
     @observable private activeFontFamily: string = "";
     @observable private activeListType: string = "";
@@ -148,6 +155,9 @@ export default class RichTextMenu extends AntimodeMenu {
         // Don't do anything if the document/selection didn't change
         if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
 
+        // update active marks
+        const activeMarks = this.getMarksInSelection(this.view.state);
+        this.setActiveMarkButtons(activeMarks);
 
         // update active font family and size
         const active = this.getActiveFontStylesOnSelection();
@@ -208,14 +218,37 @@ export default class RichTextMenu extends AntimodeMenu {
     getMarksInSelection(state: EditorState<any>) {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
+        console.log(to - from, state.tr.storedMarks);
         state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+        if (to - from === 0) { state.storedMarks && state.storedMarks.forEach(m => found.add(m)); }
         return found;
     }
 
     destroy() {
     }
 
-    createButton(faIcon: string, title: string, command?: any, onclick?: any) {
+    @action
+    setActiveMarkButtons(activeMarks: Set<Mark>) {
+        this.boldActive = false;
+        this.italicsActive = false;
+        this.underlineActive = false;
+        this.strikethroughActive = false;
+        this.subscriptActive = false;
+        this.superscriptActive = false;
+
+        activeMarks.forEach(mark => {
+            switch (mark.type.name) {
+                case "strong": this.boldActive = true; break;
+                case "em": this.italicsActive = true; break;
+                case "underline": this.underlineActive = true; break;
+                case "strikethrough": this.strikethroughActive = true; break;
+                case "subscript": this.subscriptActive = true; break;
+                case "superscript": this.superscriptActive = true; break;
+            }
+        });
+    }
+
+    createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) {
         const self = this;
         function onClick(e: React.PointerEvent) {
             e.preventDefault();
@@ -226,7 +259,7 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         return (
-            <button className="antimodeMenu-button" title={title} onPointerDown={onClick}>
+            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} title={title} onPointerDown={onClick}>
                 <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
             </button>
         );
@@ -528,7 +561,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         const link = this.currentLink ? this.currentLink : "";
 
-        const button = <button className="antimodeMenu-button" title=""><FontAwesomeIcon icon="link" size="lg" /></button>;
+        const button = <FontAwesomeIcon icon="link" size="lg" />
 
         const dropdownContent =
             <div className="dropdown link-menu">
@@ -678,12 +711,12 @@ export default class RichTextMenu extends AntimodeMenu {
     render() {
 
         const row1 = <div className="antimodeMenu-row">{[
-            this.createButton("bold", "Bold", toggleMark(schema.marks.strong)),
-            this.createButton("italic", "Italic", toggleMark(schema.marks.em)),
-            this.createButton("underline", "Underline", toggleMark(schema.marks.underline)),
-            this.createButton("strikethrough", "Strikethrough", toggleMark(schema.marks.strikethrough)),
-            this.createButton("superscript", "Superscript", toggleMark(schema.marks.superscript)),
-            this.createButton("subscript", "Subscript", toggleMark(schema.marks.subscript)),
+            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(),
@@ -761,8 +794,18 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> {
     render() {
         return (
             <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
-                {this.props.openDropdownOnButton ? <div onPointerDown={this.onDropdownClick}>{this.props.button}</div> : this.props.button}
-                <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+                {this.props.openDropdownOnButton ?
+                    <button className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
+                        {this.props.button}
+                        <FontAwesomeIcon icon="caret-down" size="sm" />
+                    </button> :
+                    <>
+                        {this.props.button}
+                        <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}>
+                            <FontAwesomeIcon icon="caret-down" size="sm" />
+                        </button>
+                    </>}
+
                 {this.showDropdown ? this.props.dropdownContent : <></>}
             </div>
         );
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index f78b1dbfb..d4a76ee17 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -21,6 +21,10 @@
         background-color: transparent;
         width: 35px;
         height: 35px;
+
+        &.active {
+            background-color: #121212;
+        }
     }
 
     .antimodeMenu-button:hover {
-- 
cgit v1.2.3-70-g09d2


From eec70e08bf5fa5f3a0a4e3b07bdc05777f71d2ef Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 18:50:08 -0500
Subject: buttons show correct active status when togglign marks at the end of
 text

---
 src/client/util/RichTextMenu.tsx | 51 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 46 insertions(+), 5 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index ec79fbf85..639772faa 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -156,7 +156,7 @@ export default class RichTextMenu extends AntimodeMenu {
         if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
 
         // update active marks
-        const activeMarks = this.getMarksInSelection(this.view.state);
+        const activeMarks = this.getActiveMarksOnSelection();
         this.setActiveMarkButtons(activeMarks);
 
         // update active font family and size
@@ -218,17 +218,57 @@ export default class RichTextMenu extends AntimodeMenu {
     getMarksInSelection(state: EditorState<any>) {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
-        console.log(to - from, state.tr.storedMarks);
         state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
-        if (to - from === 0) { state.storedMarks && state.storedMarks.forEach(m => found.add(m)); }
         return found;
     }
 
+    //finds all active marks on selection in given group
+    getActiveMarksOnSelection() {
+        if (!this.view) return;
+
+        const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
+        //current selection
+        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
+        const state = this.view.state;
+        let activeMarks: MarkType[] = [];
+        if (!empty) {
+            activeMarks = markGroup.filter(mark => {
+                const has = false;
+                for (let i = 0; !has && i < ranges.length; i++) {
+                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
+                }
+                return false;
+            });
+        }
+        else {
+            const pos = this.view.state.selection.$from;
+            const ref_node: ProsNode | null = this.reference_node(pos);
+            if (ref_node !== null && ref_node !== this.view.state.doc) {
+                if (ref_node.isText) {
+                }
+                else {
+                    return [];
+                }
+                activeMarks = markGroup.filter(mark_type => {
+                    if (mark_type === state.schema.marks.pFontSize) {
+                        return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
+                    }
+                    const mark = state.schema.mark(mark_type);
+                    return ref_node.marks.includes(mark);
+                });
+            }
+        }
+        return activeMarks;
+    }
+
     destroy() {
     }
 
     @action
-    setActiveMarkButtons(activeMarks: Set<Mark>) {
+    setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
+        if (!activeMarks) return;
+
         this.boldActive = false;
         this.italicsActive = false;
         this.underlineActive = false;
@@ -237,7 +277,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this.superscriptActive = false;
 
         activeMarks.forEach(mark => {
-            switch (mark.type.name) {
+            switch (mark.name) {
                 case "strong": this.boldActive = true; break;
                 case "em": this.italicsActive = true; break;
                 case "underline": this.underlineActive = true; break;
@@ -256,6 +296,7 @@ export default class RichTextMenu extends AntimodeMenu {
             self.view && self.view.focus();
             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.setActiveMarkButtons(self.getActiveMarksOnSelection());
         }
 
         return (
-- 
cgit v1.2.3-70-g09d2


From 6162c951e07874fbb12717d4bcd2cd649e41d0d2 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 13 Jan 2020 00:00:56 -0500
Subject: deleted old Session folder and fixed linter errors

---
 src/client/util/ProseMirrorEditorView.tsx          |   8 +-
 src/client/util/RichTextMenu.tsx                   |  13 +-
 src/client/views/EditableView.tsx                  |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 src/client/views/linking/LinkMenuGroup.tsx         |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  23 +-
 src/pen-gestures/ndollar.ts                        |  66 +-
 src/server/MemoryDatabase.ts                       |   7 +-
 src/server/RouteManager.ts                         |   3 +-
 src/server/Session/session.ts                      | 686 ---------------------
 11 files changed, 67 insertions(+), 749 deletions(-)
 delete mode 100644 src/server/Session/session.ts

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx
index 3e5fd0604..b42adfbb4 100644
--- a/src/client/util/ProseMirrorEditorView.tsx
+++ b/src/client/util/ProseMirrorEditorView.tsx
@@ -18,23 +18,23 @@ export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorView
     private _editorView?: EditorView;
 
     _createEditorView = (element: HTMLDivElement | null) => {
-        if (element != null) {
+        if (element !== null) {
             this._editorView = new EditorView(element, {
                 state: this.props.editorState,
                 dispatchTransaction: this.dispatchTransaction,
             });
         }
-    };
+    }
 
     dispatchTransaction = (tx: any) => {
         // In case EditorView makes any modification to a state we funnel those
         // modifications up to the parent and apply to the EditorView itself.
         const editorState = this.props.editorState.apply(tx);
-        if (this._editorView != null) {
+        if (this._editorView) {
             this._editorView.updateState(editorState);
         }
         this.props.onEditorState(editorState);
-    };
+    }
 
     focus() {
         if (this._editorView) {
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 639772faa..419d7caf9 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -175,7 +175,7 @@ export default class RichTextMenu extends AntimodeMenu {
     setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
         if (mark) {
             const node = (state.selection as NodeSelection).node;
-            if (node ?.type === schema.nodes.ordered_list) {
+            if (node?.type === schema.nodes.ordered_list) {
                 let attrs = node.attrs;
                 if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
                 if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
@@ -294,8 +294,8 @@ export default class RichTextMenu extends AntimodeMenu {
             e.preventDefault();
             e.stopPropagation();
             self.view && self.view.focus();
-            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.view && command && command(self.view.state, self.view.dispatch, self.view);
+            self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
             self.setActiveMarkButtons(self.getActiveMarksOnSelection());
         }
 
@@ -602,7 +602,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         const link = this.currentLink ? this.currentLink : "";
 
-        const button = <FontAwesomeIcon icon="link" size="lg" />
+        const button = <FontAwesomeIcon icon="link" size="lg" />;
 
         const dropdownContent =
             <div className="dropdown link-menu">
@@ -684,8 +684,9 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             } else {
                 if (node) {
-                    const extension = this.linkExtend(this.view!.state.selection.$anchor, href);
-                    this.view!.dispatch(this.view!.state.tr.removeMark(extension.from, extension.to, this.view!.state.schema.marks.link));
+                    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));
                 }
             }
         }
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 54def38b5..faf02b946 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -36,7 +36,7 @@ export interface EditableProps {
         resetValue: () => void;
         value: string,
         onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void,
-        autosuggestProps: Autosuggest.AutosuggestProps<string>
+        autosuggestProps: Autosuggest.AutosuggestProps<string, any>
 
     };
     oneLine?: boolean;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 936c4413f..e3780261d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -988,8 +988,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         </MarqueeView>;
     }
     @computed get contentScaling() {
-        let hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
-        let wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+        const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
+        const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
         return wscale < hscale ? wscale : hscale;
     }
     render() {
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index abd17ec4d..ace9a9e4c 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
             const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
-            DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets);
+            DragManager.StartLinkTargetsDrag(this._drag.current, e.x, e.y, this.props.sourceDoc, targets);
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 10d2e2b3e..ba35366ff 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -196,7 +196,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY);
             } else if (this.props.Document.isButton === "Selector") {  // this should be moved to an OnClick script
                 FormattedTextBoxComment.Hide();
-                this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0]!, this.props.Document)]));
+                this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)]));
             } else if (this.Document.isButton) {
                 SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
                 this.buttonClick(e.altKey, e.ctrlKey);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 8e28cf928..8b5c24878 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -906,15 +906,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this.tryUpdateHeight();
 
         // see if we need to preserve the insertion point
-        const prosediv = this.ProseRef ?.children ?.[0] as any;
-        const keeplocation = prosediv ?.keeplocation;
+        const prosediv = this.ProseRef?.children?.[0] as any;
+        const keeplocation = prosediv?.keeplocation;
         prosediv && (prosediv.keeplocation = undefined);
-        const pos = this._editorView ?.state.selection.$from.pos || 1;
-        keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+        const pos = this._editorView?.state.selection.$from.pos || 1;
+        keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
 
         // jump rich text menu to this textbox
-        if (this._ref.current) {
-            const x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
+        const { current } = this._ref;
+        if (current) {
+            const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
             const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50;
             RichTextMenu.Instance.jumpTo(x, y);
         }
@@ -933,7 +934,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
             const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
             const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
-            if (pcords && node ?.type === this._editorView!.state.schema.nodes.dashComment) {
+            if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
                 e.preventDefault();
             }
@@ -996,7 +997,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 for (let off = 1; off < 100; off++) {
                     const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
                     const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
-                    if (node ?.type === schema.nodes.list_item) {
+                    if (node?.type === schema.nodes.list_item) {
                         list_node = node;
                         break;
                     }
@@ -1087,7 +1088,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         }
         if (e.key === "Escape") {
             this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-            (document.activeElement as any).blur ?.();
+            (document.activeElement as any).blur?.();
             SelectionManager.DeselectAll();
         }
         e.stopPropagation();
@@ -1109,7 +1110,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     @action
     tryUpdateHeight(limitHeight?: number) {
-        let scrollHeight = this._ref.current ?.scrollHeight;
+        let scrollHeight = this._ref.current?.scrollHeight;
         if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
             getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             if (limitHeight && scrollHeight > limitHeight) {
@@ -1171,7 +1172,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
                     <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
                     <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
-                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc ?.backgroundColor, "transparent")}` }}>
+                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}>
                         <CollectionFreeFormView {...this.props}
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={() => this.sidebarWidth}
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 872c524d6..ef5ca38c6 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -257,16 +257,16 @@ export class NDollarRecognizer {
         {
             if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) // optional -- only attempt match when same # of component strokes
             {
-                for (var j = 0; j < this.Multistrokes[i].Unistrokes.length; j++) // for each unistroke within this multistroke
+                for (const unistroke of this.Multistrokes[i].Unistrokes) // for each unistroke within this multistroke
                 {
-                    if (AngleBetweenUnitVectors(candidate.StartUnitVector, this.Multistrokes[i].Unistrokes[j].StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction
+                    if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction
                     {
                         var d;
                         if (useProtractor) {
-                            d = OptimalCosineDistance(this.Multistrokes[i].Unistrokes[j].Vector, candidate.Vector); // Protractor
+                            d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor
                         }
                         else {
-                            d = DistanceAtBestAngle(candidate.Points, this.Multistrokes[i].Unistrokes[j], -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N)
+                            d = DistanceAtBestAngle(candidate.Points, unistroke, -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N)
                         }
                         if (d < b) {
                             b = d; // best (least) distance
@@ -283,8 +283,8 @@ export class NDollarRecognizer {
     AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => {
         this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes);
         var num = 0;
-        for (var i = 0; i < this.Multistrokes.length; i++) {
-            if (this.Multistrokes[i].Name === name) {
+        for (const multistroke of this.Multistrokes) {
+            if (multistroke.Name === name) {
                 num++;
             }
         }
@@ -322,20 +322,20 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
 
 function MakeUnistrokes(strokes: any, orders: any) {
     const unistrokes = new Array(); // array of point arrays
-    for (var r = 0; r < orders.length; r++) {
-        for (var b = 0; b < Math.pow(2, orders[r].length); b++) // use b's bits for directions
+    for (const order of orders) {
+        for (var b = 0; b < Math.pow(2, order.length); b++) // use b's bits for directions
         {
             const unistroke = new Array(); // array of points
-            for (var i = 0; i < orders[r].length; i++) {
+            for (var i = 0; i < order.length; i++) {
                 var pts;
                 if (((b >> i) & 1) === 1) {// is b's bit at index i on?
-                    pts = strokes[orders[r][i]].slice().reverse(); // copy and reverse
+                    pts = strokes[order[i]].slice().reverse(); // copy and reverse
                 }
                 else {
-                    pts = strokes[orders[r][i]].slice(); // copy
+                    pts = strokes[order[i]].slice(); // copy
                 }
-                for (var p = 0; p < pts.length; p++) {
-                    unistroke[unistroke.length] = pts[p]; // append points
+                for (const point of pts) {
+                    unistroke[unistroke.length] = point; // append points
                 }
             }
             unistrokes[unistrokes.length] = unistroke; // add one unistroke to set
@@ -346,9 +346,9 @@ function MakeUnistrokes(strokes: any, orders: any) {
 
 function CombineStrokes(strokes: any) {
     const points = new Array();
-    for (var s = 0; s < strokes.length; s++) {
-        for (var p = 0; p < strokes[s].length; p++) {
-            points[points.length] = new Point(strokes[s][p].X, strokes[s][p].Y);
+    for (const stroke of strokes) {
+        for (const { X, Y } of stroke) {
+            points[points.length] = new Point(X, Y);
         }
     }
     return points;
@@ -384,9 +384,9 @@ function RotateBy(points: any, radians: any) // rotates points around centroid
     const cos = Math.cos(radians);
     const sin = Math.sin(radians);
     const newpoints = new Array();
-    for (var i = 0; i < points.length; i++) {
-        const qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X;
-        const qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y;
+    for (const point of points) {
+        const qx = (point.X - c.X) * cos - (point.Y - c.Y) * sin + c.X;
+        const qy = (point.X - c.X) * sin + (point.Y - c.Y) * cos + c.Y;
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
@@ -396,9 +396,9 @@ function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniform
     const B = BoundingBox(points);
     const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test
     const newpoints = new Array();
-    for (var i = 0; i < points.length; i++) {
-        const qx = uniformly ? points[i].X * (size / Math.max(B.Width, B.Height)) : points[i].X * (size / B.Width);
-        const qy = uniformly ? points[i].Y * (size / Math.max(B.Width, B.Height)) : points[i].Y * (size / B.Height);
+    for (const { X, Y } of points) {
+        const qx = uniformly ? X * (size / Math.max(B.Width, B.Height)) : X * (size / B.Width);
+        const qy = uniformly ? Y * (size / Math.max(B.Width, B.Height)) : Y * (size / B.Height);
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
@@ -407,9 +407,9 @@ function TranslateTo(points: any, pt: any) // translates points' centroid
 {
     const c = Centroid(points);
     const newpoints = new Array();
-    for (var i = 0; i < points.length; i++) {
-        const qx = points[i].X + pt.X - c.X;
-        const qy = points[i].Y + pt.Y - c.Y;
+    for (const { X, Y } of points) {
+        const qx = X + pt.X - c.X;
+        const qy = Y + pt.Y - c.Y;
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
@@ -478,9 +478,9 @@ function DistanceAtAngle(points: any, T: any, radians: any) {
 }
 function Centroid(points: any) {
     var x = 0.0, y = 0.0;
-    for (var i = 0; i < points.length; i++) {
-        x += points[i].X;
-        y += points[i].Y;
+    for (const point of points) {
+        x += point.X;
+        y += point.Y;
     }
     x /= points.length;
     y /= points.length;
@@ -488,11 +488,11 @@ function Centroid(points: any) {
 }
 function BoundingBox(points: any) {
     var minX = +Infinity, maxX = -Infinity, minY = +Infinity, maxY = -Infinity;
-    for (var i = 0; i < points.length; i++) {
-        minX = Math.min(minX, points[i].X);
-        minY = Math.min(minY, points[i].Y);
-        maxX = Math.max(maxX, points[i].X);
-        maxY = Math.max(maxY, points[i].Y);
+    for (const { X, Y } of points) {
+        minX = Math.min(minX, X);
+        minY = Math.min(minY, Y);
+        maxX = Math.max(maxX, X);
+        maxY = Math.max(maxY, Y);
     }
     return new Rectangle(minX, minY, maxX - minX, maxY - minY);
 }
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index e7396babf..543f96e7f 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -7,7 +7,7 @@ export class MemoryDatabase implements IDatabase {
     private db: { [collectionName: string]: { [id: string]: any } } = {};
 
     private getCollection(collectionName: string) {
-        let collection = this.db[collectionName];
+        const collection = this.db[collectionName];
         if (collection) {
             return collection;
         } else {
@@ -17,9 +17,10 @@ export class MemoryDatabase implements IDatabase {
 
     public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> {
         const collection = this.getCollection(collectionName);
-        if ("$set" in value) {
+        const set = "$set";
+        if (set in value) {
             let currentVal = collection[id] ?? (collection[id] = {});
-            const val = value["$set"];
+            const val = value[set];
             for (const key in val) {
                 const keys = key.split(".");
                 for (let i = 0; i < keys.length - 1; i++) {
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index a7ee405a7..5afd607fd 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -86,7 +86,8 @@ export default class RouteManager {
         const { method, subscription, secureHandler: onValidation, publicHandler: onUnauthenticated, errorHandler: onError } = initializer;
         const isRelease = this._isRelease;
         const supervised = async (req: express.Request, res: express.Response) => {
-            let { user, originalUrl: target } = req;
+            let { user } = req;
+            const { originalUrl: target } = req;
             if (process.env.DB === "MEM" && !user) {
                 user = { id: "guest", email: "", userDocumentId: "guestDocId" };
             }
diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts
deleted file mode 100644
index ec3d46ac1..000000000
--- a/src/server/Session/session.ts
+++ /dev/null
@@ -1,686 +0,0 @@
-import { red, cyan, green, yellow, magenta, blue, white, Color, grey, gray, black } from "colors";
-import { on, fork, setupMaster, Worker, isMaster, isWorker } from "cluster";
-import { get } from "request-promise";
-import { Utils } from "../../Utils";
-import Repl, { ReplAction } from "../repl";
-import { readFileSync } from "fs";
-import { validate, ValidationError } from "jsonschema";
-import { configurationSchema } from "./session_config_schema";
-import { exec, ExecOptions } from "child_process";
-
-/**
- * This namespace relies on NodeJS's cluster module, which allows a parent (master) process to share
- * code with its children (workers). A simple `isMaster` flag indicates who is trying to access
- * the code, and thus determines the functionality that actually gets invoked (checked by the caller, not internally).
- * 
- * Think of the master thread as a factory, and the workers as the helpers that actually run the server.
- * 
- * So, when we run `npm start`, given the appropriate check, initializeMaster() is called in the parent process
- * This will spawn off its own child process (by default, mirrors the execution path of its parent),
- * in which initializeWorker() is invoked.
- */
-export namespace Session {
-
-    type ColorLabel = "yellow" | "red" | "cyan" | "green" | "blue" | "magenta" | "grey" | "gray" | "white" | "black";
-    const colorMapping: Map<ColorLabel, Color> = new Map([
-        ["yellow", yellow],
-        ["red", red],
-        ["cyan", cyan],
-        ["green", green],
-        ["blue", blue],
-        ["magenta", magenta],
-        ["grey", grey],
-        ["gray", gray],
-        ["white", white],
-        ["black", black]
-    ]);
-
-    export abstract class AppliedSessionAgent {
-
-        // the following two methods allow the developer to create a custom
-        // session and use the built in customization options for each thread
-        protected abstract async launchMonitor(): Promise<Session.Monitor>;
-        protected abstract async launchServerWorker(): Promise<Session.ServerWorker>;
-
-        private launched = false;
-
-        public killSession = (reason: string, graceful = true, errorCode = 0) => {
-            const target = isMaster ? this.sessionMonitor : this.serverWorker;
-            target.killSession(reason, graceful, errorCode);
-        }
-
-        private sessionMonitorRef: Session.Monitor | undefined;
-        public get sessionMonitor(): Session.Monitor {
-            if (!isMaster) {
-                this.serverWorker.sendMonitorAction("kill", {
-                    graceful: false,
-                    reason: "Cannot access the session monitor directly from the server worker thread.",
-                    errorCode: 1
-                });
-                throw new Error();
-            }
-            return this.sessionMonitorRef!;
-        }
-
-        private serverWorkerRef: Session.ServerWorker | undefined;
-        public get serverWorker(): Session.ServerWorker {
-            if (isMaster) {
-                throw new Error("Cannot access the server worker directly from the session monitor thread");
-            }
-            return this.serverWorkerRef!;
-        }
-
-        public async launch(): Promise<void> {
-            if (!this.launched) {
-                this.launched = true;
-                if (isMaster) {
-                    this.sessionMonitorRef = await this.launchMonitor();
-                } else {
-                    this.serverWorkerRef = await this.launchServerWorker();
-                }
-            } else {
-                throw new Error("Cannot launch a session thread more than once per process.");
-            }
-        }
-
-    }
-
-    interface Identifier {
-        text: string;
-        color: ColorLabel;
-    }
-
-    interface Identifiers {
-        master: Identifier;
-        worker: Identifier;
-        exec: Identifier;
-    }
-
-    interface Configuration {
-        showServerOutput: boolean;
-        identifiers: Identifiers;
-        ports: { [description: string]: number };
-        polling: {
-            route: string;
-            intervalSeconds: number;
-            failureTolerance: number;
-        };
-    }
-
-    const defaultConfig: Configuration = {
-        showServerOutput: false,
-        identifiers: {
-            master: {
-                text: "__monitor__",
-                color: "yellow"
-            },
-            worker: {
-                text: "__server__",
-                color: "magenta"
-            },
-            exec: {
-                text: "__exec__",
-                color: "green"
-            }
-        },
-        ports: { server: 3000 },
-        polling: {
-            route: "/",
-            intervalSeconds: 30,
-            failureTolerance: 0
-        }
-    };
-
-    export type ExitHandler = (reason: Error | boolean) => void | Promise<void>;
-
-    export namespace Monitor {
-
-        export interface NotifierHooks {
-            key?: (key: string) => (boolean | Promise<boolean>);
-            crash?: (error: Error) => (boolean | Promise<boolean>);
-        }
-
-        export interface Action {
-            message: string;
-            args: any;
-        }
-
-        export type ServerMessageHandler = (action: Action) => void | Promise<void>;
-
-    }
-
-    /**
-     * Validates and reads the configuration file, accordingly builds a child process factory
-     * and spawns off an initial process that will respawn as predecessors die.
-     */
-    export class Monitor {
-
-        private static count = 0;
-        private exitHandlers: ExitHandler[] = [];
-        private readonly notifiers: Monitor.NotifierHooks | undefined;
-        private readonly config: Configuration;
-        private onMessage: { [message: string]: Monitor.ServerMessageHandler[] | undefined } = {};
-        private activeWorker: Worker | undefined;
-        private key: string | undefined;
-        private repl: Repl;
-
-        public static Create(notifiers?: Monitor.NotifierHooks) {
-            if (isWorker) {
-                process.send?.({
-                    action: {
-                        message: "kill",
-                        args: {
-                            reason: "cannot create a monitor on the worker process.",
-                            graceful: false,
-                            errorCode: 1
-                        }
-                    }
-                });
-                process.exit(1);
-            } else if (++Monitor.count > 1) {
-                console.error(red("cannot create more than one monitor."));
-                process.exit(1);
-            } else {
-                return new Monitor(notifiers);
-            }
-        }
-
-        /**
-         * Kill this session and its active child
-         * server process, either gracefully (may wait
-         * indefinitely, but at least allows active networking
-         * requests to complete) or immediately.
-         */
-        public killSession = async (reason: string, graceful = true, errorCode = 0) => {
-            this.mainLog(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`));
-            this.mainLog(`session exit reason: ${(red(reason))}`);
-            await this.executeExitHandlers(true);
-            this.killActiveWorker(graceful, true);
-            process.exit(errorCode);
-        }
-
-        /**
-         * Execute the list of functions registered to be called
-         * whenever the process exits.
-         */
-        public addExitHandler = (handler: ExitHandler) => this.exitHandlers.push(handler);
-
-        /**
-         * Extend the default repl by adding in custom commands
-         * that can invoke application logic external to this module
-         */
-        public addReplCommand = (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => {
-            this.repl.registerCommand(basename, argPatterns, action);
-        }
-
-        public exec = (command: string, options?: ExecOptions) => {
-            return new Promise<void>(resolve => {
-                exec(command, { ...options, encoding: "utf8" }, (error, stdout, stderr) => {
-                    if (error) {
-                        this.execLog(red(`unable to execute ${white(command)}`));
-                        error.message.split("\n").forEach(line => line.length && this.execLog(red(`(error) ${line}`)));
-                    } else {
-                        let outLines: string[], errorLines: string[];
-                        if ((outLines = stdout.split("\n").filter(line => line.length)).length) {
-                            outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`)));
-                        }
-                        if ((errorLines = stderr.split("\n").filter(line => line.length)).length) {
-                            errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`)));
-                        }
-                    }
-                    resolve();
-                });
-            });
-        }
-
-        /**
-         * Add a listener at this message. When the monitor process
-         * receives a message, it will invoke all registered functions.
-         */
-        public addServerMessageListener = (message: string, handler: Monitor.ServerMessageHandler) => {
-            const handlers = this.onMessage[message];
-            if (handlers) {
-                handlers.push(handler);
-            } else {
-                this.onMessage[message] = [handler];
-            }
-        }
-
-        /**
-         * Unregister a given listener at this message.
-         */
-        public removeServerMessageListener = (message: string, handler: Monitor.ServerMessageHandler) => {
-            const handlers = this.onMessage[message];
-            if (handlers) {
-                const index = handlers.indexOf(handler);
-                if (index > -1) {
-                    handlers.splice(index, 1);
-                }
-            }
-        }
-
-        /**
-         * Unregister all listeners at this message.
-         */
-        public clearServerMessageListeners = (message: string) => this.onMessage[message] = undefined;
-
-        private constructor(notifiers?: Monitor.NotifierHooks) {
-            this.notifiers = notifiers;
-
-            console.log(this.timestamp(), cyan("initializing session..."));
-
-            this.config = this.loadAndValidateConfiguration();
-
-            this.initializeSessionKey();
-            // determines whether or not we see the compilation / initialization / runtime output of each child server process
-            const output = this.config.showServerOutput ? "inherit" : "ignore";
-            setupMaster({ stdio: ["ignore", output, output, "ipc"] });
-
-            // handle exceptions in the master thread - there shouldn't be many of these
-            // the IPC (inter process communication) channel closed exception can't seem
-            // to be caught in a try catch, and is inconsequential, so it is ignored
-            process.on("uncaughtException", ({ message, stack }): void => {
-                if (message !== "Channel closed") {
-                    this.mainLog(red(message));
-                    if (stack) {
-                        this.mainLog(`uncaught exception\n${red(stack)}`);
-                    }
-                }
-            });
-
-            // a helpful cluster event called on the master thread each time a child process exits
-            on("exit", ({ process: { pid } }, code, signal) => {
-                const prompt = `server worker with process id ${pid} has exited with code ${code}${signal === null ? "" : `, having encountered signal ${signal}`}.`;
-                this.mainLog(cyan(prompt));
-                // to make this a robust, continuous session, every time a child process dies, we immediately spawn a new one
-                this.spawn();
-            });
-
-            this.repl = this.initializeRepl();
-            this.spawn();
-        }
-
-        /**
-         * Generates a blue UTC string associated with the time
-         * of invocation.
-         */
-        private timestamp = () => blue(`[${new Date().toUTCString()}]`);
-
-        /**
-         * A formatted, identified and timestamped log in color
-         */
-        public mainLog = (...optionalParams: any[]) => {
-            console.log(this.timestamp(), this.config.identifiers.master.text, ...optionalParams);
-        }
-
-        /**
-         * A formatted, identified and timestamped log in color for non-
-         */
-        private execLog = (...optionalParams: any[]) => {
-            console.log(this.timestamp(), this.config.identifiers.exec.text, ...optionalParams);
-        }
-
-        /**
-         * If the caller has indicated an interest
-         * in being notified of this feature, creates
-         * a GUID for this session that can, for example,
-         * be used as authentication for killing the server
-         * (checked externally).
-         */
-        private initializeSessionKey = async (): Promise<void> => {
-            if (this.notifiers?.key) {
-                this.key = Utils.GenerateGuid();
-                const success = await this.notifiers.key(this.key);
-                const statement = success ? green("distributed session key to recipients") : red("distribution of session key failed");
-                this.mainLog(statement);
-            }
-        }
-
-        /**
-         * At any arbitrary layer of nesting within the configuration objects, any single value that
-         * is not specified by the configuration is given the default counterpart. If, within an object,
-         * one peer is given by configuration and two are not, the one is preserved while the two are given
-         * the default value.
-         * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
-         * granularity in the overwriting of nested objects
-         */
-        private preciseAssign = (target: any, ...sources: any[]): any => {
-            for (const source of sources) {
-                this.preciseAssignHelper(target, source);
-            }
-            return target;
-        }
-
-        private preciseAssignHelper = (target: any, source: any) => {
-            Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).map(property => {
-                let targetValue: any, sourceValue: any;
-                if (sourceValue = source[property]) {
-                    if (typeof sourceValue === "object" && typeof (targetValue = target[property]) === "object") {
-                        this.preciseAssignHelper(targetValue, sourceValue);
-                    } else {
-                        target[property] = sourceValue;
-                    }
-                }
-            });
-        }
-
-        /**
-         * Reads in configuration .json file only once, in the master thread
-         * and pass down any variables the pertinent to the child processes as environment variables.
-         */
-        private loadAndValidateConfiguration = (): Configuration => {
-            let config: Configuration;
-            try {
-                console.log(this.timestamp(), cyan("validating configuration..."));
-                config = JSON.parse(readFileSync('./session.config.json', 'utf8'));
-                const options = {
-                    throwError: true,
-                    allowUnknownAttributes: false
-                };
-                // ensure all necessary and no excess information is specified by the configuration file
-                validate(config, configurationSchema, options);
-                config = this.preciseAssign({}, defaultConfig, config);
-            } catch (error) {
-                if (error instanceof ValidationError) {
-                    console.log(red("\nSession configuration failed."));
-                    console.log("The given session.config.json configuration file is invalid.");
-                    console.log(`${error.instance}: ${error.stack}`);
-                    process.exit(0);
-                } else if (error.code === "ENOENT" && error.path === "./session.config.json") {
-                    console.log(cyan("Loading default session parameters..."));
-                    console.log("Consider including a session.config.json configuration file in your project root for customization.");
-                    config = this.preciseAssign({}, defaultConfig);
-                } else {
-                    console.log(red("\nSession configuration failed."));
-                    console.log("The following unknown error occurred during configuration.");
-                    console.log(error.stack);
-                    process.exit(0);
-                }
-            } finally {
-                const { identifiers } = config!;
-                Object.keys(identifiers).forEach(key => {
-                    const resolved = key as keyof Identifiers;
-                    const { text, color } = identifiers[resolved];
-                    identifiers[resolved].text = (colorMapping.get(color) || white)(`${text}:`);
-                });
-                return config!;
-            }
-        }
-
-        /**
-         * Builds the repl that allows the following commands to be typed into stdin of the master thread.
-         */
-        private initializeRepl = (): Repl => {
-            const repl = new Repl({ identifier: () => `${this.timestamp()} ${this.config.identifiers.master.text}` });
-            const boolean = /true|false/;
-            const number = /\d+/;
-            const letters = /[a-zA-Z]+/;
-            repl.registerCommand("exit", [/clean|force/], args => this.killSession("manual exit requested by repl", args[0] === "clean", 0));
-            repl.registerCommand("restart", [/clean|force/], args => this.killActiveWorker(args[0] === "clean"));
-            repl.registerCommand("set", [letters, "port", number, boolean], args => this.setPort(args[0], Number(args[2]), args[3] === "true"));
-            repl.registerCommand("set", [/polling/, number, boolean], args => {
-                const newPollingIntervalSeconds = Math.floor(Number(args[2]));
-                if (newPollingIntervalSeconds < 0) {
-                    this.mainLog(red("the polling interval must be a non-negative integer"));
-                } else {
-                    if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
-                        this.config.polling.intervalSeconds = newPollingIntervalSeconds;
-                        if (args[3] === "true") {
-                            this.activeWorker?.send({ newPollingIntervalSeconds });
-                        }
-                    }
-                }
-            });
-            return repl;
-        }
-
-        private executeExitHandlers = async (reason: Error | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
-
-        /**
-         * Attempts to kill the active worker gracefully, unless otherwise specified.
-         */
-        private killActiveWorker = (graceful = true, isSessionEnd = false): void => {
-            if (this.activeWorker && !this.activeWorker.isDead()) {
-                if (graceful) {
-                    this.activeWorker.send({ manualExit: { isSessionEnd } });
-                } else {
-                    this.activeWorker.process.kill();
-                }
-            }
-        }
-
-        /**
-         * Allows the caller to set the port at which the target (be it the server,
-         * the websocket, some other custom port) is listening. If an immediate restart
-         * is specified, this monitor will kill the active child and re-launch the server
-         * at the port. Otherwise, the updated port won't be used until / unless the child
-         * dies on its own and triggers a restart.
-         */
-        private setPort = (port: "server" | "socket" | string, value: number, immediateRestart: boolean): void => {
-            if (value > 1023 && value < 65536) {
-                this.config.ports[port] = value;
-                if (immediateRestart) {
-                    this.killActiveWorker();
-                }
-            } else {
-                this.mainLog(red(`${port} is an invalid port number`));
-            }
-        }
-
-        /**
-         * Kills the current active worker and proceeds to spawn a new worker,
-         * feeding in configuration information as environment variables.
-         */
-        private spawn = (): void => {
-            const {
-                polling: {
-                    route,
-                    failureTolerance,
-                    intervalSeconds
-                },
-                ports
-            } = this.config;
-            this.killActiveWorker();
-            this.activeWorker = fork({
-                pollingRoute: route,
-                pollingFailureTolerance: failureTolerance,
-                serverPort: ports.server,
-                socketPort: ports.socket,
-                pollingIntervalSeconds: intervalSeconds,
-                session_key: this.key,
-                DB: process.env.DB
-            });
-            this.mainLog(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`));
-            // an IPC message handler that executes actions on the master thread when prompted by the active worker
-            this.activeWorker.on("message", async ({ lifecycle, action }) => {
-                if (action) {
-                    const { message, args } = action as Monitor.Action;
-                    console.log(this.timestamp(), `${this.config.identifiers.worker.text} action requested (${cyan(message)})`);
-                    switch (message) {
-                        case "kill":
-                            const { reason, graceful, errorCode } = args;
-                            this.killSession(reason, graceful, errorCode);
-                            break;
-                        case "notify_crash":
-                            if (this.notifiers?.crash) {
-                                const { error } = args;
-                                const success = await this.notifiers.crash(error);
-                                const statement = success ? green("distributed crash notification to recipients") : red("distribution of crash notification failed");
-                                this.mainLog(statement);
-                            }
-                            break;
-                        case "set_port":
-                            const { port, value, immediateRestart } = args;
-                            this.setPort(port, value, immediateRestart);
-                            break;
-                    }
-                    const handlers = this.onMessage[message];
-                    if (handlers) {
-                        handlers.forEach(handler => handler({ message, args }));
-                    }
-                }
-                if (lifecycle) {
-                    console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${lifecycle})`);
-                }
-            });
-        }
-
-    }
-
-    /**
-     * Effectively, each worker repairs the connection to the server by reintroducing a consistent state
-     * if its predecessor has died. It itself also polls the server heartbeat, and exits with a notification
-     * email if the server encounters an uncaught exception or if the server cannot be reached.
-     */
-    export class ServerWorker {
-
-        private static count = 0;
-        private shouldServerBeResponsive = false;
-        private exitHandlers: ExitHandler[] = [];
-        private pollingFailureCount = 0;
-        private pollingIntervalSeconds: number;
-        private pollingFailureTolerance: number;
-        private pollTarget: string;
-        private serverPort: number;
-
-        public static Create(work: Function) {
-            if (isMaster) {
-                console.error(red("cannot create a worker on the monitor process."));
-                process.exit(1);
-            } else if (++ServerWorker.count > 1) {
-                process.send?.({
-                    action: {
-                        message: "kill", args: {
-                            reason: "cannot create more than one worker on a given worker process.",
-                            graceful: false,
-                            errorCode: 1
-                        }
-                    }
-                });
-                process.exit(1);
-            } else {
-                return new ServerWorker(work);
-            }
-        }
-
-        /**
-         * Allows developers to invoke application specific logic
-         * by hooking into the exiting of the server process.
-         */
-        public addExitHandler = (handler: ExitHandler) => this.exitHandlers.push(handler);
-
-        /**
-         * Kill the session monitor (parent process) from this
-         * server worker (child process). This will also kill
-         * this process (child process).
-         */
-        public killSession = (reason: string, graceful = true, errorCode = 0) => this.sendMonitorAction("kill", { reason, graceful, errorCode });
-
-        /**
-         * A convenience wrapper to tell the session monitor (parent process)
-         * to carry out the action with the specified message and arguments.
-         */
-        public sendMonitorAction = (message: string, args?: any) => process.send!({ action: { message, args } });
-
-        private constructor(work: Function) {
-            this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(" ")}]`)}`));
-
-            const { pollingRoute, serverPort, pollingIntervalSeconds, pollingFailureTolerance } = process.env;
-            this.serverPort = Number(serverPort);
-            this.pollingIntervalSeconds = Number(pollingIntervalSeconds);
-            this.pollingFailureTolerance = Number(pollingFailureTolerance);
-            this.pollTarget = `http://localhost:${serverPort}${pollingRoute}`;
-
-            this.configureProcess();
-            work();
-            this.pollServer();
-        }
-
-        /**
-         * Set up message and uncaught exception handlers for this
-         * server process.
-         */
-        private configureProcess = () => {
-            // updates the local values of variables to the those sent from master
-            process.on("message", async ({ newPollingIntervalSeconds, manualExit }) => {
-                if (newPollingIntervalSeconds !== undefined) {
-                    this.pollingIntervalSeconds = newPollingIntervalSeconds;
-                }
-                if (manualExit !== undefined) {
-                    const { isSessionEnd } = manualExit;
-                    await this.executeExitHandlers(isSessionEnd);
-                    process.exit(0);
-                }
-            });
-
-            // one reason to exit, as the process might be in an inconsistent state after such an exception
-            process.on('uncaughtException', this.proactiveUnplannedExit);
-            process.on('unhandledRejection', reason => {
-                const appropriateError = reason instanceof Error ? reason : new Error(`unhandled rejection: ${reason}`);
-                this.proactiveUnplannedExit(appropriateError);
-            });
-        }
-
-        /**
-         * Execute the list of functions registered to be called
-         * whenever the process exits.
-         */
-        private executeExitHandlers = async (reason: Error | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
-
-        /**
-         * Notify master thread (which will log update in the console) of initialization via IPC.
-         */
-        public lifecycleNotification = (event: string) => process.send?.({ lifecycle: event });
-
-        /**
-         * Called whenever the process has a reason to terminate, either through an uncaught exception
-         * in the process (potentially inconsistent state) or the server cannot be reached.
-         */
-        private proactiveUnplannedExit = async (error: Error): Promise<void> => {
-            this.shouldServerBeResponsive = false;
-            // communicates via IPC to the master thread that it should dispatch a crash notification email
-            this.sendMonitorAction("notify_crash", { error });
-            await this.executeExitHandlers(error);
-            // notify master thread (which will log update in the console) of crash event via IPC
-            this.lifecycleNotification(red(`crash event detected @ ${new Date().toUTCString()}`));
-            this.lifecycleNotification(red(error.message));
-            process.exit(1);
-        }
-
-        /**
-         * This monitors the health of the server by submitting a get request to whatever port / route specified
-         * by the configuration every n seconds, where n is also given by the configuration. 
-         */
-        private pollServer = async (): Promise<void> => {
-            await new Promise<void>(resolve => {
-                setTimeout(async () => {
-                    try {
-                        await get(this.pollTarget);
-                        if (!this.shouldServerBeResponsive) {
-                            // notify monitor thread that the server is up and running
-                            this.lifecycleNotification(green(`listening on ${this.serverPort}...`));
-                        }
-                        this.shouldServerBeResponsive = true;
-                    } catch (error) {
-                        // if we expect the server to be unavailable, i.e. during compilation,
-                        // the listening variable is false, activeExit will return early and the child
-                        // process will continue
-                        if (this.shouldServerBeResponsive) {
-                            if (++this.pollingFailureCount > this.pollingFailureTolerance) {
-                                this.proactiveUnplannedExit(error);
-                            } else {
-                                this.lifecycleNotification(yellow(`the server has encountered ${this.pollingFailureCount} of ${this.pollingFailureTolerance} tolerable failures`));
-                            }
-                        }
-                    } finally {
-                        resolve();
-                    }
-                }, 1000 * this.pollingIntervalSeconds);
-            });
-            // controlled, asynchronous infinite recursion achieves a persistent poll that does not submit a new request until the previous has completed
-            this.pollServer();
-        }
-
-    }
-
-}
-- 
cgit v1.2.3-70-g09d2


From d528b9d19a685d8447a9d54715dc94cd66034852 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 20 Jan 2020 21:18:10 -0500
Subject: fixed linking to text region.  fixed link displays.  removed old
 tooltiptextmenu.  fixed brushes in richTextMenu.

---
 src/client/util/ProsemirrorExampleTransfer.ts      |    2 -
 src/client/util/RichTextMenu.tsx                   |   25 +-
 src/client/util/RichTextRules.ts                   |    4 +-
 src/client/util/RichTextSchema.tsx                 |    3 +-
 src/client/util/TooltipLinkingMenu.tsx             |   68 --
 src/client/util/TooltipTextMenu.scss               |  372 -------
 src/client/util/TooltipTextMenu.tsx                | 1042 --------------------
 src/client/views/DocumentButtonBar.tsx             |    8 +-
 src/client/views/DocumentDecorations.tsx           |    8 -
 src/client/views/nodes/DocumentContentsView.tsx    |    2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |   22 +-
 .../authentication/models/current_user_utils.ts    |    4 +-
 12 files changed, 37 insertions(+), 1523 deletions(-)
 delete mode 100644 src/client/util/TooltipLinkingMenu.tsx
 delete mode 100644 src/client/util/TooltipTextMenu.scss
 delete mode 100644 src/client/util/TooltipTextMenu.tsx

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index c028dbf8b..da3815181 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -5,9 +5,7 @@ import { Schema } from "prosemirror-model";
 import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
 import { splitListItem, wrapInList, } from "prosemirror-schema-list";
 import { EditorState, Transaction, TextSelection } from "prosemirror-state";
-import { TooltipTextMenu } from "./TooltipTextMenu";
 import { SelectionManager } from "./SelectionManager";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
 
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 419d7caf9..f070589ae 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -32,8 +32,9 @@ export default class RichTextMenu extends AntimodeMenu {
     public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
 
     private view?: EditorView;
-    private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+    public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
 
+    public _brushMap: Map<string, Set<Mark>> = new Map();
     private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
     private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
     private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
@@ -142,6 +143,17 @@ 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, 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) {
@@ -417,6 +429,15 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
 
+    // todo: add brushes to brushMap to save with a style name
+    onBrushNameKeyPress = (e: React.KeyboardEvent) => {
+        if (e.key === "Enter") {
+            RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+            this._brushNameRef.current!.style.background = "lightGray";
+        }
+    }
+    _brushNameRef = React.createRef<HTMLInputElement>();
+
     createBrushButton() {
         const self = this;
         function onBrushClick(e: React.PointerEvent) {
@@ -447,7 +468,7 @@ export default class RichTextMenu extends AntimodeMenu {
             <div className="dropdown">
                 <p>{label}</p>
                 <button onPointerDown={this.clearBrush}>Clear brush</button>
-                {/* <input placeholder="Enter URL"></input> */}
+                <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input>
             </div>;
 
         return (
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 29b378299..e20abb04c 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -5,11 +5,11 @@ import { NodeSelection, TextSelection } from "prosemirror-state";
 import { NumCast, Cast } from "../../new_fields/Types";
 import { Doc } from "../../new_fields/Doc";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { TooltipTextMenuManager } from "../util/TooltipTextMenu";
 import { Docs, DocUtils } from "../documents/Documents";
 import { Id } from "../../new_fields/FieldSymbols";
 import { DocServer } from "../DocServer";
 import { returnFalse, Utils } from "../../Utils";
+import RichTextMenu from "./RichTextMenu";
 
 export const inpRules = {
     rules: [
@@ -264,7 +264,7 @@ export const inpRules = {
             new RegExp(/%[a-z]+$/),
             (state, match, start, end) => {
                 const color = match[0].substring(1, match[0].length);
-                const marks = TooltipTextMenuManager.Instance._brushMap.get(color);
+                const marks = RichTextMenu.Instance._brushMap.get(color);
                 if (marks) {
                     const tr = state.tr.deleteRange(start, end);
                     return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index ef90a7294..1f70cafc4 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -855,7 +855,8 @@ export class FootnoteView {
                 }),
                 new Plugin({
                     view(newView) {
-                        return FormattedTextBox.getToolTip(newView);
+                        // TODO -- make this work with RichTextMenu
+                        // return FormattedTextBox.getToolTip(newView);
                     }
                 })
                 ],
diff --git a/src/client/util/TooltipLinkingMenu.tsx b/src/client/util/TooltipLinkingMenu.tsx
deleted file mode 100644
index b46675a04..000000000
--- a/src/client/util/TooltipLinkingMenu.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { EditorState } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { FieldViewProps } from "../views/nodes/FieldView";
-import "./TooltipTextMenu.scss";
-
-//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
-export class TooltipLinkingMenu {
-
-    private tooltip: HTMLElement;
-    private view: EditorView;
-    private editorProps: FieldViewProps;
-
-    constructor(view: EditorView, editorProps: FieldViewProps) {
-        this.view = view;
-        this.editorProps = editorProps;
-        this.tooltip = document.createElement("div");
-        this.tooltip.className = "tooltipMenu linking";
-
-        //add the div which is the tooltip
-        view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
-
-        const target = "https://www.google.com";
-
-        const link = document.createElement("a");
-        link.href = target;
-        link.textContent = target;
-        link.target = "_blank";
-        link.style.color = "white";
-        this.tooltip.appendChild(link);
-
-        this.update(view, undefined);
-    }
-
-    //updates the tooltip menu when the selection changes
-    update(view: EditorView, lastState: EditorState | undefined) {
-        const state = view.state;
-        // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) return;
-
-        // Hide the tooltip if the selection is empty
-        if (state.selection.empty) {
-            this.tooltip.style.display = "none";
-            return;
-        }
-
-        console.log("STORED:");
-        console.log(state.doc.content.firstChild!.content);
-
-        // Otherwise, reposition it and update its content
-        this.tooltip.style.display = "";
-        const { from, to } = state.selection;
-        const start = view.coordsAtPos(from), end = view.coordsAtPos(to);
-        // The box in which the tooltip is positioned, to use as base
-        const box = this.tooltip.offsetParent!.getBoundingClientRect();
-        // Find a center-ish x position from the selection endpoints (when
-        // crossing lines, end may be more to the left)
-        const left = Math.max((start.left + end.left) / 2, start.left + 3);
-        this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
-        const width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
-        const mid = Math.min(start.left, end.left) + width;
-
-        this.tooltip.style.width = "auto";
-        this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
-    }
-
-    destroy() { this.tooltip.remove(); }
-}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
deleted file mode 100644
index 2a38fe118..000000000
--- a/src/client/util/TooltipTextMenu.scss
+++ /dev/null
@@ -1,372 +0,0 @@
-@import "../views/globalCssVariables";
-.ProseMirror-menu-dropdown-wrap {
-    display: inline-block;
-    position: relative;
-}
-  
-.ProseMirror-menu-dropdown {
-    vertical-align: 1px;
-    cursor: pointer;
-    position: relative;
-    padding: 0 15px 0 4px;
-    background: white;
-    border-radius: 2px;
-    text-align: left;
-    font-size: 12px;
-    white-space: nowrap;
-    margin-right: 4px;
-
-    &:after {
-        content: "";
-        border-left: 4px solid transparent;
-        border-right: 4px solid transparent;
-        border-top: 4px solid currentColor;
-        opacity: .6;
-        position: absolute;
-        right: 4px;
-        top: calc(50% - 2px);
-    }
-}
-
-.ProseMirror-menu-submenu-wrap {
-    position: relative;
-    margin-right: -4px;
-}
-
-.ProseMirror-menu-dropdown-menu, 
-.ProseMirror-menu-submenu {
-    font-size: 12px;
-    background: white;
-    border: 1px solid rgb(223, 223, 223);
-    min-width: 40px;
-    z-index: 50000;
-    position: absolute;
-    box-sizing: content-box;
-
-    .ProseMirror-menu-dropdown-item {
-        cursor: pointer;
-        z-index: 100000;
-        text-align: left;
-        padding: 3px;
-
-        &:hover {
-            background-color: $light-color-secondary;
-        }
-    }
-}
-
-
-.ProseMirror-menu-submenu-label:after {
-    content: "";
-    border-top: 4px solid transparent;
-    border-bottom: 4px solid transparent;
-    border-left: 4px solid currentColor;
-    opacity: .6;
-    position: absolute;
-    right: 4px;
-    top: calc(50% - 4px);
-}
-  
-  .ProseMirror-icon {
-    display: inline-block;
-    // line-height: .8;
-    // vertical-align: -2px; /* Compensate for padding */
-    // padding: 2px 8px;
-    cursor: pointer;
-
-    &.ProseMirror-menu-disabled {
-        cursor: default;
-    }
-
-    svg {
-        fill:white;
-        height: 1em;
-    }
-
-    span {
-        vertical-align: text-top;
-      }  
-  }
-
-.wrapper {
-    position: absolute;
-    pointer-events: all;
-    display: flex;
-    align-items: center;
-    transform: translateY(-85px);
-
-    height: 35px;
-    background: #323232;
-    border-radius: 6px;
-    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
-}
-
-.tooltipMenu, .basic-tools {
-    z-index: 20000;
-    pointer-events: all;
-    padding: 3px;
-    padding-bottom: 5px;
-    display: flex;
-    align-items: center;
-
-    .ProseMirror-example-setup-style hr {
-      padding: 2px 10px;
-      border: none;
-      margin: 1em 0;
-    }
-    
-    .ProseMirror-example-setup-style hr:after {
-      content: "";
-      display: block;
-      height: 1px;
-      background-color: silver;
-      line-height: 2px;
-    }
-}
-
-.menuicon {
-    width: 25px;
-    height: 25px;
-    cursor: pointer;
-    text-align: center;
-    line-height: 25px;
-    margin: 0 2px;
-    border-radius: 3px;
-
-    &:hover {
-        background-color: black;
-
-        #link-drag {
-            background-color: black;
-        }
-    }
-
-    &> * {
-        margin-top: 50%;
-        margin-left: 50%;
-        transform: translate(-50%, -50%);
-    }
-
-    svg {
-        fill: inherit;
-        width: 18px;
-        height: 18px;
-    }
-}
-
-.menuicon-active {
-    width: 25px;
-    height: 25px;
-    cursor: pointer;
-    text-align: center;
-    line-height: 25px;
-    margin: 0 2px;
-    border-radius: 3px;
-
-    &:hover {
-        background-color: black;
-    }
-
-    &> * {
-        margin-top: 50%;
-        margin-left: 50%;
-        transform: translate(-50%, -50%);
-    }
-
-    svg {
-        fill: greenyellow;
-        width: 18px;
-        height: 18px;
-    }
-}
-
-.colorPicker {
-    position: relative;
-
-    svg {
-        width: 18px;
-        height: 18px;
-        // margin-top: 11px;
-    }
-
-    .buttonColor {
-        position: absolute;
-        top: 24px;
-        left: 1px;
-        width: 24px;
-        height: 4px;
-        margin-top: 0;
-    }
-}
-
-#link-drag {
-    background-color: #323232;
-}
-
-.underline svg {
-    margin-top: 13px;
-}
-
-  .font-size-indicator {
-      font-size: 12px;
-      padding-right: 0px;
-  }
-  .summarize{
-      color: white;
-      height: 20px;
-      text-align: center;
-  }
-  
-
-.brush{
-    display: inline-block;
-    width: 1em;
-    height: 1em;
-    stroke-width: 0;
-    stroke: currentColor;
-    fill: currentColor;
-    margin-right: 15px;
-}
-
-.brush-active{
-    display: inline-block;
-    width: 1em;
-    height: 1em;
-    stroke-width: 3;
-    fill: greenyellow;
-    margin-right: 15px;
-}
-
-.dragger-wrapper {
-    color: #eee;
-    height: 22px;
-    padding: 0 5px;
-    box-sizing: content-box;
-    cursor: grab;
-
-    .dragger {
-        width: 18px;
-        height: 100%;
-        display: flex;
-        justify-content: space-evenly;
-    }
-
-    .dragger-line {
-        width: 2px;
-        height: 100%;
-        background-color: black;
-    }
-}
-
-.button-dropdown-wrapper {
-    display: flex;
-    align-content: center;
-
-    &:hover {
-        background-color: black;
-    }
-}
-
-.buttonSettings-dropdown {
-
-    &.ProseMirror-menu-dropdown {
-        width: 10px;
-        height: 25px;
-        margin: 0;
-        padding: 0 2px;
-        background-color: #323232;
-        text-align: center;
-
-        &:after {
-            border-top: 4px solid white;
-            right: 2px;
-        }
-
-        &:hover {
-            background-color: black;
-        }
-    }
-
-    &.ProseMirror-menu-dropdown-menu {
-        min-width: 150px;
-        left: -27px;
-        top: 31px;
-        background-color: #323232;
-        border: 1px solid #4d4d4d;
-        color: $light-color-secondary;
-        // border: none;
-        // border: 1px solid $light-color-secondary;
-        border-radius: 0 6px 6px 6px;
-        padding: 3px;
-        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
-        .ProseMirror-menu-dropdown-item{
-            cursor: default;
-
-            &:last-child {
-                border-bottom: none;
-            }
-
-            &:hover {
-                background-color: #323232;
-            }
-
-            .button-setting, .button-setting-disabled {
-                padding: 2px;
-                border-radius: 2px;
-            }
-
-            .button-setting:hover {
-                cursor: pointer;
-                background-color: black;
-            }
-
-            .separated-button {
-                border-top: 1px solid $light-color-secondary;
-                padding-top: 6px;
-            }
-
-            input {
-               color: black;
-               border: none;
-               border-radius: 1px;
-               padding: 3px;
-            }
-
-            button {
-               padding: 6px;
-               background-color: #323232;
-               border: 1px solid black;
-               border-radius: 1px;
-
-                &:hover {
-                   background-color: black;
-                }
-            }
-        }
-
-
-    }
-}
-
-.colorPicker-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-    margin-top: 3px;
-    margin-left: -3px;
-    width: calc(100% + 6px);
-}
-
-button.colorPicker {
-    width: 20px;
-    height: 20px;
-    border-radius: 15px !important;
-    margin: 3px;
-    border: none !important;
-
-    &.active {
-        border: 2px solid white !important;
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
deleted file mode 100644
index 1c15dca7f..000000000
--- a/src/client/util/TooltipTextMenu.tsx
+++ /dev/null
@@ -1,1042 +0,0 @@
-import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
-import { wrapInList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc, Field, Opt } from "../../new_fields/Doc";
-import { Utils } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { FieldViewProps } from "../views/nodes/FieldView";
-import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { LinkManager } from "./LinkManager";
-import { schema } from "./RichTextSchema";
-import "./TooltipTextMenu.scss";
-import { Cast, NumCast, StrCast } from '../../new_fields/Types';
-import { updateBullets } from './ProsemirrorExampleTransfer';
-import { DocumentDecorations } from '../views/DocumentDecorations';
-import { SelectionManager } from './SelectionManager';
-import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-const { toggleMark } = require("prosemirror-commands");
-
-//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
-export class TooltipTextMenu {
-    public static Toolbar: HTMLDivElement | undefined;
-
-    // editor state properties
-    private view: EditorView;
-    private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
-
-    private fontStyles: Mark[] = [];
-    private fontSizes: Mark[] = [];
-    private _marksToDoms: Map<MarkType, HTMLSpanElement> = new Map();
-    private _collapsed: boolean = false;
-
-    // editor doms
-    public tooltip: HTMLElement = document.createElement("div");
-    private wrapper: HTMLDivElement = document.createElement("div");
-
-    // editor button doms
-    private colorDom?: Node;
-    private colorDropdownDom?: Node;
-    private linkDom?: Node;
-    private highighterDom?: Node;
-    private highlighterDropdownDom?: Node;
-    private linkDropdownDom?: Node;
-    private _brushdom?: Node;
-    private _brushDropdownDom?: Node;
-    private fontSizeDom?: Node;
-    private fontStyleDom?: Node;
-    private basicTools?: HTMLElement;
-
-    static createDiv(className: string) { const div = document.createElement("div"); div.className = className; return div; }
-    static createSpan(className: string) { const div = document.createElement("span"); div.className = className; return div; }
-    constructor(view: EditorView) {
-        this.view = view;
-
-        // initialize the tooltip -- sets this.tooltip
-        this.initTooltip(view);
-
-        // initialize the wrapper
-        this.wrapper = TooltipTextMenu.createDiv("wrapper");
-        this.wrapper.appendChild(this.tooltip);
-
-        TooltipTextMenu.Toolbar = this.wrapper;
-    }
-
-    private async initTooltip(view: EditorView) {
-        const self = this;
-        this.tooltip = TooltipTextMenu.createDiv("tooltipMenu");
-        this.basicTools = TooltipTextMenu.createDiv("basic-tools");
-
-        const svgIcon = (name: string, title: string = name, dpath: string) => {
-            const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
-            svg.setAttribute("viewBox", "-100 -100 650 650");
-            const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
-            path.setAttributeNS(null, "d", dpath);
-            svg.appendChild(path);
-
-            const span = TooltipTextMenu.createSpan(name + " menuicon");
-            span.title = title;
-            span.appendChild(svg);
-
-            return span;
-        };
-
-        const basicItems = [ // init basicItems in minimized toolbar -- paths to svgs are obtained from fontawesome
-            { mark: schema.marks.strong, dom: svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
-            { mark: schema.marks.em, dom: svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") },
-            { mark: schema.marks.underline, dom: svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") },
-        ];
-        const items = [ // init items in full size toolbar
-            { mark: schema.marks.strikethrough, dom: svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
-            { mark: schema.marks.superscript, dom: svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
-            { mark: schema.marks.subscript, dom: svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
-        ];
-
-        basicItems.map(({ dom, mark }) => this.basicTools ?.appendChild(dom.cloneNode(true)));
-        basicItems.concat(items).forEach(({ dom, mark }) => {
-            this.tooltip.appendChild(dom);
-            this._marksToDoms.set(mark, dom);
-
-            //pointer down handler to activate button effects
-            dom.addEventListener("pointerdown", e => {
-                this.view.focus();
-                if (dom.contains(e.target as Node)) {
-                    e.preventDefault();
-                    e.stopPropagation();
-                    toggleMark(mark)(this.view.state, this.view.dispatch, this.view);
-                    this.updateHighlightStateOfButtons();
-                }
-            });
-        });
-
-        // summarize menu
-        this.highighterDom = this.createHighlightTool().render(this.view).dom;
-        this.highlighterDropdownDom = this.createHighlightDropdown().render(this.view).dom;
-        this.tooltip.appendChild(this.highighterDom);
-        this.tooltip.appendChild(this.highlighterDropdownDom);
-
-        // color menu
-        this.colorDom = this.createColorTool().render(this.view).dom;
-        this.colorDropdownDom = this.createColorDropdown().render(this.view).dom;
-        this.tooltip.appendChild(this.colorDom);
-        this.tooltip.appendChild(this.colorDropdownDom);
-
-        // link menu
-        this.linkDom = this.createLinkTool().render(this.view).dom;
-        this.linkDropdownDom = this.createLinkDropdown("").render(this.view).dom;
-        this.tooltip.appendChild(this.linkDom);
-        this.tooltip.appendChild(this.linkDropdownDom);
-
-        // list of font styles
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 7 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 8 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 9 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 10 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 12 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 14 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 16 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 18 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 20 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 24 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 }));
-        this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 }));
-
-        // font sizes
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" }));
-
-
-        // init brush tool
-        this._brushdom = this.createBrushTool().render(this.view).dom;
-        this.tooltip.appendChild(this._brushdom);
-        this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
-        this.tooltip.appendChild(this._brushDropdownDom);
-
-        // summarizer tool
-        const summarizer = new MenuItem({
-            title: "Summarize",
-            label: "Summarize",
-            icon: icons.join,
-            css: "fill:white;",
-            class: "menuicon",
-            execEvent: "",
-            run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
-        });
-        this.tooltip.appendChild(summarizer.render(this.view).dom);
-
-        // list types dropdown
-        const listDropdownTypes = [{ mapStyle: "bullet", label: ":" }, { mapStyle: "decimal", label: "1.1" }, { mapStyle: "multi", label: "A.1" }, { label: "X" }];
-        const listTypes = new Dropdown(listDropdownTypes.map(({ mapStyle, label }) =>
-            new MenuItem({
-                title: "Set Bullet Style",
-                label: label,
-                execEvent: "",
-                class: "dropdown-item",
-                css: "color: black; width: 40px;",
-                enable() { return true; },
-                run() {
-                    const marks = self.view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
-                    if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
-                        const tx3 = updateBullets(tx2, schema, mapStyle);
-                        marks && tx3.ensureMarks([...marks]);
-                        marks && tx3.setStoredMarks([...marks]);
-
-                        view.dispatch(tx2);
-                    })) {
-                        const tx2 = view.state.tr;
-                        const tx3 = updateBullets(tx2, schema, mapStyle);
-                        marks && tx3.ensureMarks([...marks]);
-                        marks && tx3.setStoredMarks([...marks]);
-
-                        view.dispatch(tx3);
-                    }
-                }
-            })), { label: ":", css: "color:black; width: 40px;" });
-        this.tooltip.appendChild(listTypes.render(this.view).dom);
-
-        await this.updateFromDash(view, undefined, undefined);
-
-        const draggerWrapper = TooltipTextMenu.createDiv("dragger-wrapper");
-        const dragger = TooltipTextMenu.createDiv("dragger");
-        dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
-        dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
-        dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
-        draggerWrapper.appendChild(dragger);
-        this.wrapper.appendChild(draggerWrapper);
-        this.setupDragElementInteractions(draggerWrapper);
-    }
-
-    setupDragElementInteractions(elmnt: HTMLElement) {
-        var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
-        if (elmnt) {
-            // if present, the header is where you move the DIV from:
-            elmnt.onpointerdown = dragPointerDown;
-            elmnt.ondblclick = onClick;
-        }
-        const self = this;
-
-        function dragPointerDown(e: PointerEvent) {
-            e = e || window.event;
-            e.preventDefault();
-            // get the mouse cursor position at startup:
-            pos3 = e.clientX;
-            pos4 = e.clientY;
-            document.onpointerup = closeDragElement;
-            // call a function whenever the cursor moves:
-            document.onpointermove = elementDrag;
-        }
-
-        function onClick(e: MouseEvent) {
-            self._collapsed = !self._collapsed;
-            const children = self.wrapper.childNodes;
-            if (self._collapsed && children.length > 0) {
-                self.wrapper.removeChild(self.tooltip);
-                self.basicTools && self.wrapper.prepend(self.basicTools);
-            }
-            else {
-                self.wrapper.prepend(self.tooltip);
-                self.basicTools && self.wrapper.removeChild(self.basicTools);
-            }
-        }
-
-        function elementDrag(e: PointerEvent) {
-            e = e || window.event;
-            //e.preventDefault();
-            // calculate the new cursor position:
-            pos1 = pos3 - e.clientX;
-            pos2 = pos4 - e.clientY;
-            pos3 = e.clientX;
-            pos4 = e.clientY;
-            // set the element's new position:
-            // elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
-            // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
-
-            self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px";
-            self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px";
-        }
-
-        function closeDragElement() {
-            // stop moving when mouse button is released:
-            document.onpointerup = null;
-            document.onpointermove = null;
-        }
-    }
-
-    //label of dropdown will change to given label
-    updateFontSizeDropdown(label: string) {
-        //font SIZES
-        const fontSizeBtns: MenuItem[] = [];
-        const self = this;
-        this.fontSizes.forEach(mark =>
-            fontSizeBtns.push(new MenuItem({
-                title: "Set Font Size",
-                label: String(mark.attrs.fontSize),
-                execEvent: "",
-                class: "dropdown-item",
-                css: "color: black; width: 50px;",
-                enable() { return true; },
-                run() {
-                    const size = mark.attrs.fontSize;
-                    if (size) { self.updateFontSizeDropdown(String(size) + " pt"); }
-                    if (self.editorProps) {
-                        const ruleProvider = self.editorProps.ruleProvider;
-                        const heading = NumCast(self.editorProps.Document.heading);
-                        if (ruleProvider && heading) {
-                            ruleProvider["ruleSize_" + heading] = size;
-                        }
-                    }
-                    TooltipTextMenu.setMark(self.view.state.schema.marks.pFontSize.create({ fontSize: size }), self.view.state, self.view.dispatch);
-                }
-            })));
-
-        const newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom;
-        if (this.fontSizeDom) {
-            this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom);
-        }
-        else {
-            this.tooltip.appendChild(newfontSizeDom);
-        }
-        this.fontSizeDom = newfontSizeDom;
-    }
-
-    //label of dropdown will change to given label
-    updateFontStyleDropdown(label: string) {
-        //font STYLES
-        const fontBtns: MenuItem[] = [];
-        const self = this;
-        this.fontStyles.forEach(mark =>
-            fontBtns.push(new MenuItem({
-                title: "Set Font Family",
-                label: mark.attrs.family,
-                execEvent: "",
-                class: "dropdown-item",
-                css: "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;",
-                enable() { return true; },
-                run() {
-                    const fontName = mark.attrs.family;
-                    if (fontName) { self.updateFontStyleDropdown(fontName); }
-                    if (self.editorProps) {
-                        const ruleProvider = self.editorProps.ruleProvider;
-                        const heading = NumCast(self.editorProps.Document.heading);
-                        if (ruleProvider && heading) {
-                            ruleProvider["ruleFont_" + heading] = fontName;
-                        }
-                    }
-                    TooltipTextMenu.setMark(self.view.state.schema.marks.pFontFamily.create({ family: fontName }), self.view.state, self.view.dispatch);
-                }
-            })));
-
-        const newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom;
-        if (this.fontStyleDom) {
-            this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom);
-        }
-        else {
-            this.tooltip.appendChild(newfontStyleDom);
-        }
-        this.fontStyleDom = newfontStyleDom;
-    }
-    async getTextLinkTargetTitle() {
-        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.href;
-            if (href) {
-                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    if (linkclicked) {
-                        const linkDoc = await DocServer.GetRefField(linkclicked);
-                        if (linkDoc instanceof Doc) {
-                            const anchor1 = await Cast(linkDoc.anchor1, Doc);
-                            const anchor2 = await Cast(linkDoc.anchor2, Doc);
-                            const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
-                            if (currentDoc && anchor1 && anchor2) {
-                                if (Doc.AreProtosEqual(currentDoc, anchor1)) {
-                                    return StrCast(anchor2.title);
-                                }
-                                if (Doc.AreProtosEqual(currentDoc, anchor2)) {
-                                    return StrCast(anchor1.title);
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    return href;
-                }
-            } else {
-                return link.attrs.title;
-            }
-        }
-    }
-
-    // LINK TOOL
-    createLinkTool(active: boolean = false) {
-        return new MenuItem({
-            title: "Link tool",
-            label: "Link tool",
-            icon: icons.link,
-            css: "fill:white;",
-            class: active ? "menuicon-active" : "menuicon",
-            execEvent: "",
-            run: async (state, dispatch) => { },
-            active: (state) => true
-        });
-    }
-
-    createLinkDropdown(targetTitle: string) {
-        const input = document.createElement("input");
-
-        // menu item for input for hyperlink url 
-        // TODO: integrate search to allow users to search for a doc to link to
-        const linkInfo = new MenuItem({
-            title: "",
-            execEvent: "",
-            class: "button-setting-disabled",
-            css: "",
-            render() {
-                const p = document.createElement("p");
-                p.textContent = "Linked to:";
-
-                input.type = "text";
-                input.placeholder = "Enter URL";
-                if (targetTitle) input.value = targetTitle;
-                input.onclick = (e: MouseEvent) => {
-                    input.select();
-                    input.focus();
-                };
-
-                const div = document.createElement("div");
-                div.appendChild(p);
-                div.appendChild(input);
-                return div;
-            },
-            enable() { return false; },
-            run(p1, p2, p3, event) { event.stopPropagation(); }
-        });
-
-        // menu item to update/apply the hyperlink to the selected text
-        const linkApply = new MenuItem({
-            title: "",
-            execEvent: "",
-            class: "",
-            css: "",
-            render() {
-                const button = document.createElement("button");
-                button.className = "link-url-button";
-                button.textContent = "Apply hyperlink";
-                return button;
-            },
-            enable() { return false; },
-            run: async (state, dispatch, view, event) => {
-                event.stopPropagation();
-                let node = this.view.state.selection.$from.nodeAfter;
-                let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: input.value, location: "onRight" });
-                this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
-                this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-                node = this.view.state.selection.$from.nodeAfter;
-                link = node && node.marks.find(m => m.type.name === "link");
-
-                // update link menu
-                const linkDom = self.createLinkTool(true).render(self.view).dom;
-                const linkDropdownDom = self.createLinkDropdown(await self.getTextLinkTargetTitle()).render(self.view).dom;
-                self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
-                self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
-                self.linkDom = linkDom;
-                self.linkDropdownDom = linkDropdownDom;
-            }
-        });
-
-        // menu item to remove the link
-        // TODO: allow this to be undoable
-        const self = this;
-        const deleteLink = new MenuItem({
-            title: "Delete link",
-            execEvent: "",
-            class: "separated-button",
-            css: "",
-            render() {
-                const button = document.createElement("button");
-                button.textContent = "Remove link";
-
-                const wrapper = document.createElement("div");
-                wrapper.appendChild(button);
-                return wrapper;
-            },
-            enable() { return true; },
-            async run() {
-                // delete the link
-                const node = self.view.state.selection.$from.nodeAfter;
-                const link = node && node.marks.find(m => m.type === self.view.state.schema.marks.link);
-                const href = link!.attrs.href;
-                if (href ?.indexOf(Utils.prepend("/doc/")) === 0) {
-                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    linkclicked && DocServer.GetRefField(linkclicked).then(async linkDoc => {
-                        if (linkDoc instanceof Doc) {
-                            LinkManager.Instance.deleteLink(linkDoc);
-                            self.view.dispatch(self.view.state.tr.removeMark(self.view.state.selection.from, self.view.state.selection.to, self.view.state.schema.marks.link));
-                        }
-                    });
-                }
-                // update link menu
-                const linkDom = self.createLinkTool(false).render(self.view).dom;
-                const linkDropdownDom = self.createLinkDropdown("").render(self.view).dom;
-                self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
-                self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
-                self.linkDom = linkDom;
-                self.linkDropdownDom = linkDropdownDom;
-            }
-        });
-
-        return new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
-    }
-
-    public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
-        const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, 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 || "";
-    }
-
-    // SUMMARIZER TOOL
-    static insertSummarizer(state: EditorState<any>, dispatch: any) {
-        if (!state.selection.empty) {
-            const mark = state.schema.marks.summarize.create();
-            const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
-            const content = tr.selection.content();
-            const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
-            dispatch ?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
-        }
-    }
-
-    // HIGHLIGHTER TOOL
-    createHighlightTool() {
-        return new MenuItem({
-            title: "Highlight",
-            css: "fill:white;",
-            class: "menuicon",
-            execEvent: "",
-            render() {
-                const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
-                svg.setAttribute("viewBox", "-100 -100 650 650");
-                const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
-                path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z");
-                svg.appendChild(path);
-
-                const color = TooltipTextMenu.createDiv("buttonColor");
-                color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
-
-                const wrapper = TooltipTextMenu.createDiv("colorPicker");
-                wrapper.appendChild(svg);
-                wrapper.appendChild(color);
-                return wrapper;
-            },
-            run: (state, dispatch) => TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, state, dispatch)
-        });
-    }
-
-    static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
-        if (!state.selection.empty) {
-            toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
-        }
-    }
-
-    createHighlightDropdown() {
-        // menu item for color picker
-        const self = this;
-        const colors = new MenuItem({
-            title: "",
-            execEvent: "",
-            class: "button-setting-disabled",
-            css: "",
-            render() {
-                const p = document.createElement("p");
-                p.textContent = "Change highlight:";
-
-                const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
-
-                const colors = [
-                    PastelSchemaPalette.get("pink2"),
-                    PastelSchemaPalette.get("purple4"),
-                    PastelSchemaPalette.get("bluegreen1"),
-                    PastelSchemaPalette.get("yellow4"),
-                    PastelSchemaPalette.get("red2"),
-                    PastelSchemaPalette.get("bluegreen7"),
-                    PastelSchemaPalette.get("bluegreen5"),
-                    PastelSchemaPalette.get("orange1"),
-                    "white",
-                    "transparent"
-                ];
-
-                colors.forEach(color => {
-                    const button = document.createElement("button");
-                    button.className = color === TooltipTextMenuManager.Instance.highlighter ? "colorPicker active" : "colorPicker";
-                    if (color) {
-                        button.style.backgroundColor = color;
-                        button.textContent = color === "transparent" ? "X" : "";
-                        button.onclick = e => {
-                            TooltipTextMenuManager.Instance.highlighter = color;
-
-                            TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, self.view.state, self.view.dispatch);
-
-                            // update color menu
-                            const highlightDom = self.createHighlightTool().render(self.view).dom;
-                            const highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom;
-                            self.highighterDom && self.tooltip.replaceChild(highlightDom, self.highighterDom);
-                            self.highlighterDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlighterDropdownDom);
-                            self.highighterDom = highlightDom;
-                            self.highlighterDropdownDom = highlightDropdownDom;
-                        };
-                    }
-                    colorsWrapper.appendChild(button);
-                });
-
-                const div = document.createElement("div");
-                div.appendChild(p);
-                div.appendChild(colorsWrapper);
-                return div;
-            },
-            enable() { return false; },
-            run(p1, p2, p3, event) {
-                event.stopPropagation();
-            }
-        });
-
-        return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
-    }
-
-    // COLOR TOOL
-    createColorTool() {
-        return new MenuItem({
-            title: "Color",
-            css: "fill:white;",
-            class: "menuicon",
-            execEvent: "",
-            render() {
-                const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
-                svg.setAttribute("viewBox", "-100 -100 650 650");
-                const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
-                path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z");
-                svg.appendChild(path);
-
-                const color = TooltipTextMenu.createDiv("buttonColor");
-                color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString();
-
-                const wrapper = TooltipTextMenu.createDiv("colorPicker");
-                wrapper.appendChild(svg);
-                wrapper.appendChild(color);
-                return wrapper;
-            },
-            run: (state, dispatch) => TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
-        });
-    }
-
-    static insertColor(color: String, state: EditorState<any>, dispatch: any) {
-        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
-        if (state.selection.empty) {
-            dispatch(state.tr.addStoredMark(colorMark));
-        } else {
-            this.setMark(colorMark, state, dispatch);
-        }
-    }
-
-    createColorDropdown() {
-        // menu item for color picker
-        const self = this;
-        const colors = new MenuItem({
-            title: "",
-            execEvent: "",
-            class: "button-setting-disabled",
-            css: "",
-            render() {
-                const p = document.createElement("p");
-                p.textContent = "Change color:";
-
-                const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
-
-                const colors = [
-                    DarkPastelSchemaPalette.get("pink2"),
-                    DarkPastelSchemaPalette.get("purple4"),
-                    DarkPastelSchemaPalette.get("bluegreen1"),
-                    DarkPastelSchemaPalette.get("yellow4"),
-                    DarkPastelSchemaPalette.get("red2"),
-                    DarkPastelSchemaPalette.get("bluegreen7"),
-                    DarkPastelSchemaPalette.get("bluegreen5"),
-                    DarkPastelSchemaPalette.get("orange1"),
-                    "#757472",
-                    "#000"
-                ];
-
-                colors.forEach(color => {
-                    const button = document.createElement("button");
-                    button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker";
-                    if (color) {
-                        button.style.backgroundColor = color;
-                        button.onclick = e => {
-                            TooltipTextMenuManager.Instance.color = color;
-
-                            TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
-
-                            // update color menu
-                            const colorDom = self.createColorTool().render(self.view).dom;
-                            const colorDropdownDom = self.createColorDropdown().render(self.view).dom;
-                            self.colorDom && self.tooltip.replaceChild(colorDom, self.colorDom);
-                            self.colorDropdownDom && self.tooltip.replaceChild(colorDropdownDom, self.colorDropdownDom);
-                            self.colorDom = colorDom;
-                            self.colorDropdownDom = colorDropdownDom;
-                        };
-                    }
-                    colorsWrapper.appendChild(button);
-                });
-
-                const div = document.createElement("div");
-                div.appendChild(p);
-                div.appendChild(colorsWrapper);
-                return div;
-            },
-            enable() { return false; },
-            run(p1, p2, p3, event) { event.stopPropagation(); }
-        });
-
-        return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
-    }
-
-    // BRUSH TOOL
-    createBrushTool(active: boolean = false) {
-        const icon = {
-            height: 32, width: 32,
-            path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z"
-        };
-        const self = this;
-        return new MenuItem({
-            title: "Brush tool",
-            label: "Brush tool",
-            icon: icon,
-            css: "fill:white;",
-            class: active ? "menuicon-active" : "menuicon",
-            execEvent: "",
-            run: (state, dispatch) => {
-                this.brush_function(state, dispatch);
-
-                // update dropdown with marks
-                const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
-                self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
-                self._brushDropdownDom = newBrushDropdowndom;
-            },
-            active: (state) => true
-        });
-    }
-
-    brush_function(state: EditorState<any>, dispatch: any) {
-        if (TooltipTextMenuManager.Instance._brushIsEmpty) {
-            // get marks in the selection
-            const selected_marks = new Set<Mark>();
-            const { from, to } = state.selection as TextSelection;
-            state.doc.nodesBetween(from, to, (node) => node.marks ?.forEach(m => selected_marks.add(m)));
-
-            if (this._brushdom && selected_marks.size >= 0) {
-                TooltipTextMenuManager.Instance._brushMarks = selected_marks;
-                const newbrush = this.createBrushTool(true).render(this.view).dom;
-                this.tooltip.replaceChild(newbrush, this._brushdom);
-                this._brushdom = newbrush;
-                TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
-            }
-        }
-        else {
-            const { from, to, $from } = this.view.state.selection;
-            if (this._brushdom) {
-                if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
-                    if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) {
-                        this.view.dispatch(this.view.state.tr.removeMark(from, to));
-                        Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
-                            TooltipTextMenu.setMark(mark, this.view.state, this.view.dispatch);
-                        });
-                    }
-                }
-                else {
-                    const newbrush = this.createBrushTool(false).render(this.view).dom;
-                    this.tooltip.replaceChild(newbrush, this._brushdom);
-                    this._brushdom = newbrush;
-                    TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
-                }
-            }
-        }
-    }
-
-    createBrushDropdown(active: boolean = false) {
-        let label = "Stored marks: ";
-        if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) {
-            TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => label += mark.type.name + ", ");
-            label = label.substring(0, label.length - 2);
-        } else {
-            label = "No marks are currently stored";
-        }
-
-        const brushInfo = new MenuItem({
-            title: "",
-            label: label,
-            execEvent: "",
-            class: "button-setting-disabled",
-            css: "",
-            enable() { return false; },
-            run(p1, p2, p3, event) { event.stopPropagation(); }
-        });
-
-        const self = this;
-        const input = document.createElement("input");
-        const clearBrush = new MenuItem({
-            title: "Clear brush",
-            execEvent: "",
-            class: "separated-button",
-            css: "",
-            render() {
-                const button = document.createElement("button");
-                button.textContent = "Clear brush";
-
-                input.textContent = "editme";
-                input.style.width = "75px";
-                input.style.height = "30px";
-                input.style.background = "white";
-                input.setAttribute("contenteditable", "true");
-                input.style.whiteSpace = "nowrap";
-                input.type = "text";
-                input.placeholder = "Enter URL";
-                input.onpointerdown = (e: PointerEvent) => {
-                    e.stopPropagation();
-                    e.preventDefault();
-                };
-                input.onclick = (e: MouseEvent) => {
-                    input.select();
-                    input.focus();
-                };
-                input.onkeypress = (e: KeyboardEvent) => {
-                    if (e.key === "Enter") {
-                        TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMap.set(input.value, TooltipTextMenuManager.Instance._brushMarks);
-                        input.style.background = "lightGray";
-                    }
-                };
-
-                const wrapper = document.createElement("div");
-                wrapper.appendChild(input);
-                wrapper.appendChild(button);
-                return wrapper;
-            },
-            enable() { return true; },
-            run() {
-                TooltipTextMenuManager.Instance._brushIsEmpty = true;
-                TooltipTextMenuManager.Instance._brushMarks = new Set();
-
-                // update brush tool
-                // TODO: this probably isn't very clean
-                const newBrushdom = self.createBrushTool().render(self.view).dom;
-                self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom);
-                self._brushdom = newBrushdom;
-                const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
-                self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
-                self._brushDropdownDom = newBrushDropdowndom;
-            }
-        });
-
-        const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
-        return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
-    }
-
-    static setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
-        if (mark) {
-            const node = (state.selection as NodeSelection).node;
-            if (node ?.type === schema.nodes.ordered_list) {
-                let attrs = node.attrs;
-                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
-                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
-                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
-                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
-            } else {
-                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
-                    const { from, $from, to, empty } = tx.selection;
-                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
-                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
-                    } else dispatch(tx);
-                });
-            }
-        }
-    }
-
-    // called by Prosemirror
-    update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); }
-    //updates the tooltip menu when the selection changes
-    public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
-        if (!view) {
-            console.log("no editor?  why?");
-            return;
-        }
-        this.view = view;
-        DocumentDecorations.Instance.showTextBar();
-        props && (this.editorProps = props);
-
-        // Don't do anything if the document/selection didn't change
-        if (!lastState || !lastState.doc.eq(view.state.doc) || !lastState.selection.eq(view.state.selection)) {
-
-            // UPDATE LINK DROPDOWN
-            const linkTarget = await this.getTextLinkTargetTitle();
-            const linkDom = this.createLinkTool(linkTarget ? true : false).render(this.view).dom;
-            const linkDropdownDom = this.createLinkDropdown(linkTarget).render(this.view).dom;
-            this.linkDom && this.tooltip.replaceChild(linkDom, this.linkDom);
-            this.linkDropdownDom && this.tooltip.replaceChild(linkDropdownDom, this.linkDropdownDom);
-            this.linkDom = linkDom;
-            this.linkDropdownDom = linkDropdownDom;
-
-            //UPDATE FONT STYLE DROPDOWN
-            const activeStyles = this.activeFontFamilyOnSelection();
-            this.updateFontStyleDropdown(activeStyles.length === 1 ? activeStyles[0] : activeStyles.length ? "various" : "default");
-
-            //UPDATE FONT SIZE DROPDOWN
-            const activeSizes = this.activeFontSizeOnSelection();
-            this.updateFontSizeDropdown(activeSizes.length === 1 ? String(activeSizes[0]) + " pt" : activeSizes.length ? "various" : "default");
-
-            //UPDATE ALL OTHER BUTTONS
-            this.updateHighlightStateOfButtons();
-        }
-    }
-
-    updateHighlightStateOfButtons() {
-        Array.from(this._marksToDoms.values()).forEach(val => val.style.fill = "white");
-        this.activeMarksOnSelection().filter(mark => this._marksToDoms.has(mark)).forEach(mark =>
-            this._marksToDoms.get(mark)!.style.fill = "greenyellow");
-
-        // keeps brush tool highlighted if active when switching between textboxes
-        if (!TooltipTextMenuManager.Instance._brushIsEmpty && this._brushdom) {
-            const newbrush = this.createBrushTool(true).render(this.view).dom;
-            this.tooltip.replaceChild(newbrush, this._brushdom);
-            this._brushdom = newbrush;
-        }
-    }
-
-    //finds fontSize at start of selection
-    activeFontSizeOnSelection() {
-        //current selection
-        const state = this.view.state;
-        const activeSizes: number[] = [];
-        const pos = this.view.state.selection.$from;
-        const ref_node: ProsNode = this.reference_node(pos);
-        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
-            ref_node.marks.forEach(m => m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize));
-        }
-        return activeSizes;
-    }
-    //finds fontSize at start of selection
-    activeFontFamilyOnSelection() {
-        //current selection
-        const state = this.view.state;
-        const activeFamilies: string[] = [];
-        const pos = this.view.state.selection.$from;
-        const ref_node: ProsNode = this.reference_node(pos);
-        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
-            ref_node.marks.forEach(m => m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family));
-        }
-        return activeFamilies;
-    }
-    //finds all active marks on selection in given group
-    activeMarksOnSelection() {
-        const markGroup = Array.from(this._marksToDoms.keys());
-        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
-        //current selection
-        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
-        const state = this.view.state;
-        let activeMarks: MarkType[] = [];
-        if (!empty) {
-            activeMarks = markGroup.filter(mark => {
-                const has = false;
-                for (let i = 0; !has && i < ranges.length; i++) {
-                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
-                }
-                return false;
-            });
-        }
-        else {
-            const pos = this.view.state.selection.$from;
-            const ref_node: ProsNode = this.reference_node(pos);
-            if (ref_node !== null && ref_node !== this.view.state.doc) {
-                if (ref_node.isText) {
-                }
-                else {
-                    return [];
-                }
-                activeMarks = markGroup.filter(mark_type => {
-                    if (mark_type === state.schema.marks.pFontSize) {
-                        return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
-                    }
-                    const mark = state.schema.mark(mark_type);
-                    return ref_node.marks.includes(mark);
-                });
-            }
-        }
-        return activeMarks;
-    }
-
-    reference_node(pos: ResolvedPos<any>): ProsNode {
-        let ref_node: ProsNode = this.view.state.doc;
-        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
-            ref_node = pos.nodeBefore;
-        }
-        else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
-            ref_node = pos.nodeAfter;
-        }
-        else if (pos.pos > 0) {
-            let skip = false;
-            for (let i: number = pos.pos - 1; i > 0; i--) {
-                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
-                    if (node.isLeaf && !skip) {
-                        ref_node = node;
-                        skip = true;
-                    }
-
-                });
-            }
-        }
-        if (!ref_node.isLeaf && ref_node.childCount > 0) {
-            ref_node = ref_node.child(0);
-        }
-        return ref_node;
-    }
-
-    destroy() {
-        // this.wrapper.remove();
-    }
-}
-
-
-export class TooltipTextMenuManager {
-    private static _instance: TooltipTextMenuManager;
-    private _isPinned: boolean = false;
-
-    public pinnedX: number = 0;
-    public pinnedY: number = 0;
-    public unpinnedX: number = 0;
-    public unpinnedY: number = 0;
-
-    public _brushMarks: Set<Mark> | undefined;
-    public _brushMap: Map<string, Set<Mark>> = new Map();
-    public _brushIsEmpty: boolean = true;
-
-    public color: String = "#000";
-    public highlighter: String = "transparent";
-
-    public activeMenu: TooltipTextMenu | undefined;
-
-    static get Instance() {
-        if (!TooltipTextMenuManager._instance) {
-            TooltipTextMenuManager._instance = new TooltipTextMenuManager();
-        }
-        return TooltipTextMenuManager._instance;
-    }
-
-    public get isPinned() { return this._isPinned; }
-
-    public toggleIsPinned() { this._isPinned = !this._isPinned; }
-}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 202bfe400..4441356dc 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -20,6 +20,7 @@ import React = require("react");
 import { DocumentView } from './nodes/DocumentView';
 import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import { CollectionDockingView } from './collections/CollectionDockingView';
+import RichTextMenu from '../util/RichTextMenu';
 import { Id } from '../../new_fields/FieldSymbols';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -112,14 +113,15 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
             const linkDrag = UndoManager.StartBatch("Drag Link");
             this.view0 && DragManager.StartLinkDrag(this._linkButton.current, this.view0.props.Document, e.pageX, e.pageY, {
                 dragComplete: dropEv => {
-                    const linkDoc = dropEv.linkDragData?.linkDocument; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
-                    if (this.view0 && linkDoc && FormattedTextBox.ToolTipTextMenu) {
+                    const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+                    if (this.view0 && linkDoc) {
                         const proto = Doc.GetProto(linkDoc);
                         proto.sourceContext = this.view0.props.ContainingCollectionDoc;
 
                         const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
+                        const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
+                        const text = RichTextMenu.Instance.MakeLinkToSelection(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", anchor2Id);
                         if (linkDoc.anchor2 instanceof Doc) {
-                            const text = FormattedTextBox.ToolTipTextMenu.MakeLinkToSelection(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", linkDoc.anchor2[Id]);
                             proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link
                         }
                     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 799b3695c..32fea15fb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -14,7 +14,6 @@ import { Docs, DocUtils } from "../documents/Documents";
 import { DocumentManager } from "../util/DocumentManager";
 import { DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
-import { TooltipTextMenu } from '../util/TooltipTextMenu';
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
 import { CollectionView } from "./collections/CollectionView";
@@ -26,8 +25,6 @@ import { IconBox } from "./nodes/IconBox";
 import React = require("react");
 import { DocumentType } from '../documents/DocumentTypes';
 import { ScriptField } from '../../new_fields/ScriptField';
-import { render } from 'react-dom';
-import RichTextMenu from '../util/RichTextMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -546,11 +543,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             this.TextBar = ele;
         }
     }
-    public showTextBar = () => {
-        if (this.TextBar && TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1) {
-            this.TextBar.appendChild(TooltipTextMenu.Toolbar);
-        }
-    }
     render() {
         const bounds = this.Bounds;
         const seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 867be287f..b9f601a9a 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -61,7 +61,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     @computed get layout(): string {
         TraceMobx();
         if (!this.layoutDoc) return "<p>awaiting layout</p>";
-        const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")], "string");
+        const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string");
         if (layout === undefined) {
             return this.props.Document.data ?
                 "<FieldView {...props} fieldKey='data' />" :
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 60842bcb0..1cd5cdcea 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -29,8 +29,6 @@ import buildKeymap from "../../util/ProsemirrorExampleTransfer";
 import { inpRules } from "../../util/RichTextRules";
 import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema";
 import { SelectionManager } from "../../util/SelectionManager";
-import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
-import { TooltipTextMenu } from "../../util/TooltipTextMenu";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocAnnotatableComponent } from "../DocComponent";
 import { DocumentButtonBar } from '../DocumentButtonBar';
@@ -77,7 +75,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
     public static Instance: FormattedTextBox;
-    public static ToolTipTextMenu: TooltipTextMenu | undefined = undefined;
     public ProseRef?: HTMLDivElement;
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -127,10 +124,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         return "";
     }
 
-    public static getToolTip(ev: EditorView) {
-        return this.ToolTipTextMenu ? this.ToolTipTextMenu : this.ToolTipTextMenu = new TooltipTextMenu(ev);
-    }
-
     @undoBatch
     public setFontColor(color: string) {
         const view = this._editorView!;
@@ -485,11 +478,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             schema,
             plugins: [
                 inputRules(inpRules),
-                this.tooltipTextMenuPlugin(),
+                this.richTextMenuPlugin(),
                 history(),
                 keymap(this._keymap),
                 keymap(baseKeymap),
-                // this.tooltipLinkingMenuPlugin(),
                 new Plugin({
                     props: {
                         attributes: { class: "ProseMirror-example-setup-style" }
@@ -1038,25 +1030,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         }
     }
 
-    tooltipTextMenuPlugin() {
+    richTextMenuPlugin() {
         const self = FormattedTextBox;
         return new Plugin({
             view(newView) {
-                // return self.ToolTipTextMenu = FormattedTextBox.getToolTip(newView);
                 RichTextMenu.Instance.changeView(newView);
                 return RichTextMenu.Instance;
             }
         });
     }
 
-    tooltipLinkingMenuPlugin() {
-        const myprops = this.props;
-        return new Plugin({
-            view(_editorView) {
-                return new TooltipLinkingMenu(_editorView, myprops);
-            }
-        });
-    }
     onBlur = (e: any) => {
         //DictationManager.Controls.stop(false);
         if (this._undoTyping) {
@@ -1138,7 +1121,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (this.props.isSelected()) {
             // TODO: ftong --> update from dash in richtextmenu
             RichTextMenu.Instance.updateFromDash(this._editorView!, undefined, this.props);
-            // FormattedTextBox.ToolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
         } else if (FormattedTextBoxComment.textBox === this) {
             FormattedTextBoxComment.Hide();
         }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 5375c8ac8..e502a06b8 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -151,14 +151,14 @@ export class CurrentUserUtils {
         });
         // setup a color picker
         const color = Docs.Create.ColorDocument({
-            title: "color picker", width: 400, dropAction: "alias", forceActive: true, removeDropProperties: new List<string>(["dropAction", "forceActive"])
+            title: "color picker", width: 300, dropAction: "alias", forceActive: true, removeDropProperties: new List<string>(["dropAction", "forceActive"])
         });
 
         return Docs.Create.ButtonDocument({
             width: 35, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Tools", fontSize: 10, targetContainer: sidebarContainer,
             letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.StackingDocument([dragCreators, color], {
-                width: 500, height: 800, lockedPosition: true, chromeStatus: "disabled", title: "tools stack"
+                width: 500, lockedPosition: true, chromeStatus: "disabled", title: "tools stack"
             }),
             onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"),
         });
-- 
cgit v1.2.3-70-g09d2


From bfebed91e12abca324d7a638adeb1da9755a9057 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 22 Jan 2020 15:41:04 -0500
Subject: basic cleanup for warnings

---
 src/client/apis/youtube/YoutubeBox.tsx             |  2 +-
 src/client/util/ProseMirrorEditorView.tsx          | 74 ----------------------
 src/client/util/RichTextMenu.tsx                   | 68 ++++++++++----------
 src/client/util/RichTextSchema.tsx                 |  2 +-
 src/client/views/EditableView.tsx                  |  2 +-
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionPivotView.tsx      | 11 ++--
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 .../views/collections/CollectionViewChromes.tsx    |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |  3 +
 src/client/views/linking/LinkMenu.tsx              |  2 +-
 src/client/views/nodes/WebBox.tsx                  |  2 +-
 src/client/views/search/SearchItem.tsx             |  1 -
 src/new_fields/Doc.ts                              | 10 +--
 src/new_fields/documentSchemas.ts                  |  1 +
 src/server/Websocket/Websocket.ts                  |  2 -
 17 files changed, 59 insertions(+), 132 deletions(-)
 delete mode 100644 src/client/util/ProseMirrorEditorView.tsx

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index fd3d9e2f1..d654e2530 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -46,7 +46,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
      * When component mounts, last search's results are laoded in based on the back up stored
      * in the document of the props.
      */
-    async componentWillMount() {
+    async componentDidMount() {
         //DocServer.getYoutubeChannels();
         const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc);
         const awaitedBackUp = await castedSearchBackUp;
diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx
deleted file mode 100644
index b42adfbb4..000000000
--- a/src/client/util/ProseMirrorEditorView.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from "react";
-import { EditorView } from "prosemirror-view";
-import { EditorState } from "prosemirror-state";
-
-export interface ProseMirrorEditorViewProps {
-    /* EditorState instance to use. */
-    editorState: EditorState;
-    /* Called when EditorView produces new EditorState. */
-    onEditorState: (editorState: EditorState) => any;
-}
-
-/**
- * This wraps ProseMirror's EditorView into React component.
- * This code was found on https://discuss.prosemirror.net/t/using-with-react/904
- */
-export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorViewProps>  {
-
-    private _editorView?: EditorView;
-
-    _createEditorView = (element: HTMLDivElement | null) => {
-        if (element !== null) {
-            this._editorView = new EditorView(element, {
-                state: this.props.editorState,
-                dispatchTransaction: this.dispatchTransaction,
-            });
-        }
-    }
-
-    dispatchTransaction = (tx: any) => {
-        // In case EditorView makes any modification to a state we funnel those
-        // modifications up to the parent and apply to the EditorView itself.
-        const editorState = this.props.editorState.apply(tx);
-        if (this._editorView) {
-            this._editorView.updateState(editorState);
-        }
-        this.props.onEditorState(editorState);
-    }
-
-    focus() {
-        if (this._editorView) {
-            this._editorView.focus();
-        }
-    }
-
-    componentWillReceiveProps(nextProps: { editorState: EditorState<any>; }) {
-        // In case we receive new EditorState through props — we apply it to the
-        // EditorView instance.
-        if (this._editorView) {
-            if (nextProps.editorState !== this.props.editorState) {
-                this._editorView.updateState(nextProps.editorState);
-            }
-        }
-    }
-
-    componentWillUnmount() {
-        if (this._editorView) {
-            this._editorView.destroy();
-        }
-    }
-
-    shouldComponentUpdate() {
-        // Note that EditorView manages its DOM itself so we'd ratrher don't mess
-        // with it.
-        return false;
-    }
-
-    render() {
-        // Render just an empty div which is then used as a container for an
-        // EditorView instance.
-        return (
-            <div ref={this._createEditorView} />
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index f070589ae..eb5c90654 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -73,7 +73,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this.fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
@@ -312,22 +312,22 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         return (
-            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} title={title} onPointerDown={onClick}>
+            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
                 <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
             </button>
         );
     }
 
-    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[]): JSX.Element {
+    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
-                    <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
             }
             return label === activeOption ?
-                <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} style={style ? style : {}}>{label}</option>;
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
@@ -340,19 +340,19 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        return <select onChange={onChange}>{items}</select>;
+        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?: {} }[]): JSX.Element {
+    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
-                    <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>;
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
             }
             return label === activeOption ?
-                <option value={label} title={title} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} style={style ? style : {}}>{label}</option>;
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
@@ -363,7 +363,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        return <select onChange={e => onChange(e.target.value)}>{items}</select>;
+        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
@@ -472,7 +472,7 @@ export default class RichTextMenu extends AntimodeMenu {
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+            <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -535,21 +535,21 @@ export default class RichTextMenu extends AntimodeMenu {
             </button>;
 
         const dropdownContent =
-            <div className="dropdown">
+            <div className="dropdown" >
                 <p>Change font color:</p>
                 <div className="color-wrapper">
                     {this.fontColors.map(color => {
                         if (color) {
                             return this.activeFontColor === color ?
-                                <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
-                                <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+                                <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+                                <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
                         }
                     })}
                 </div>
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+            <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -582,7 +582,7 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         const button =
-            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}>
+            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
                 <FontAwesomeIcon icon="highlighter" size="lg" />
                 <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
             </button>;
@@ -594,15 +594,15 @@ export default class RichTextMenu extends AntimodeMenu {
                     {this.highlightColors.map(color => {
                         if (color) {
                             return this.activeHighlightColor === color ?
-                                <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
-                                <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
                         }
                     })}
                 </div>
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} />
+            <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
         );
     }
 
@@ -635,7 +635,7 @@ export default class RichTextMenu extends AntimodeMenu {
             </div>;
 
         return (
-            <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
+            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
         );
     }
 
@@ -773,7 +773,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     render() {
 
-        const row1 = <div className="antimodeMenu-row">{[
+        const row1 = <div className="antimodeMenu-row" key="row1">{[
             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)),
@@ -787,14 +787,14 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
         ]}</div>;
 
-        const row2 = <div className="antimodeMenu-row row-2">
-            <div>
-                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions),
-                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions),
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions)]}
+        const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+            <div key="row">
+                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
+                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
             </div>
-            <div>
-                <button className="antimodeMenu-button" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+            <div key="button">
+                <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
                     <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
                 </button>
                 {this.getDragger()}
@@ -864,12 +864,12 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> {
                     </button> :
                     <>
                         {this.props.button}
-                        <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}>
+                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
                             <FontAwesomeIcon icon="caret-down" size="sm" />
                         </button>
                     </>}
 
-                {this.showDropdown ? this.props.dropdownContent : <></>}
+                {this.showDropdown ? this.props.dropdownContent : (null)}
             </div>
         );
     }
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 1f70cafc4..5751c8c7e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -306,7 +306,7 @@ export const marks: { [index: string]: MarkSpec } = {
             }
         }],
         toDOM(node: any) {
-            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }];
+            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
         }
     },
 
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 0d677b8ce..394caba72 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -66,7 +66,7 @@ export class EditableView extends React.Component<EditableProps> {
     }
 
     @action
-    componentWillReceiveProps(nextProps: EditableProps) {
+    componentDidUpdate(nextProps: EditableProps) {
         // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
         // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
         // to false. this will no longer do so -syip
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0958c434a..79e0cfea5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -64,7 +64,7 @@ export class MainView extends React.Component {
 
     public isPointerDown = false;
 
-    componentWillMount() {
+    componentDidMount() {
         const tag = document.createElement('script');
 
         tag.src = "https://www.youtube.com/iframe_api";
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index f23540529..aa68cc18f 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -28,6 +28,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         if (!this.props.Document.facetCollection) {
             const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true });
             facetCollection.target = this.props.Document;
+            facetCollection.dontCopyOnAlias = true;
 
             const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
             const script = CompileScript(scriptText, {
@@ -122,8 +123,8 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null);
         const flyout = (
             <div className="collectionPivotView-flyout" style={{ width: `${this._facetWidth}` }}>
-                {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" onClick={e => this.facetClick(facet)}>
-                    <input type="checkbox" checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
+                {this._allFacets.map(facet => <label className="collectionPivotView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+                    <input type="checkbox" onChange={e => { }} checked={this.props.Document.facetCollection instanceof Doc && DocListCast(this.props.Document.facetCollection.data).some(d => {
                         return d.title === facet;
                     })} />
                     <span className="checkmark" />
@@ -133,7 +134,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
         );
         return !facetCollection ? (null) :
             <div className="collectionPivotView">
-                <div className="collectionPivotView-dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+                <div className="collectionPivotView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
                     <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
                 </div>
                 <div className="collectionPivotView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
@@ -145,11 +146,11 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
                             </div>
                         </Flyout>
                     </div>
-                    <div className="collectionPivotView-tree">
+                    <div className="collectionPivotView-tree" key="tree">
                         <CollectionTreeView {...this.props} Document={facetCollection} />
                     </div>
                 </div>
-                <div className="collectionPivotView-pivot" style={{ width: this.bodyPanelWidth() }}>
+                <div className="collectionPivotView-pivot" key="pivot" style={{ width: this.bodyPanelWidth() }}>
                     <CollectionFreeFormView  {...this.props} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
                 </div>
             </div>;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index bef587786..9776085c9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -720,7 +720,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey
     const facetValues = new Set<string>();
     DocListCast(dataDoc[dataKey]).forEach(child => {
         Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facetHeader]?.toString() || "(null)"));
-        facetValues.add(child[facetHeader]?.toString() || "(null)");
+        facetValues.add(Field.toString(child[facetHeader] as Field));
     });
     const text = "determineCheckedState(layoutDoc, facetHeader, facetValue)";
     const params = {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 075c48134..42c563fe2 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -416,8 +416,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                 placeholder="FILTER"
                                 value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
                                 onChange={(e) => { }}
-                                onPointerDown={this.openViewSpecs}
-                                id="viewSpecsInput" />
+                                onPointerDown={this.openViewSpecs} />
                             {this.getPivotInput()}
                             <div className="collectionViewBaseChrome-viewSpecsMenu"
                                 onPointerDown={this.openViewSpecs}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index db5259bdc..a7e4fce4e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -3,7 +3,7 @@ import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
@@ -737,7 +737,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const fontSize = Cast(viewDef.fontSize, "number");
             return [text, x, y, width, height].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+                    ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z} style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
                         {text}
                     </div>,
                     bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 32e39d25e..71f265484 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -25,18 +25,21 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
             <button
                 className="antimodeMenu-button"
                 title="Create a Collection"
+                key="group"
                 onPointerDown={this.createCollection}>
                 <FontAwesomeIcon icon="object-group" size="lg" />
             </button>,
             <button
                 className="antimodeMenu-button"
                 title="Summarize Documents"
+                key="summarize"
                 onPointerDown={this.summarize}>
                 <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
             </button>,
             <button
                 className="antimodeMenu-button"
                 title="Delete Documents"
+                key="delete"
                 onPointerDown={this.delete}>
                 <FontAwesomeIcon icon="trash-alt" size="lg" />
             </button>,
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 52628ba4c..1a40f0c55 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -25,7 +25,7 @@ export class LinkMenu extends React.Component<Props> {
     @observable private _editingLink?: Doc;
 
     @action
-    componentWillReceiveProps() {
+    componentDidMount() {
         this._editingLink = undefined;
     }
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index b35ea0bb0..e9118e3ab 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -34,7 +34,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
-    componentWillMount() {
+    componentDidMount() {
 
         const field = Cast(this.props.Document[this.props.fieldKey], WebField);
         if (field && field.url.href.indexOf("youtube") !== -1) {
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 88a4d4c50..30fcc7908 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -260,7 +260,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
     onPointerMoved = (e: PointerEvent) => {
         if (Math.abs(e.clientX - this._downX) > Utils.DRAG_THRESHOLD ||
             Math.abs(e.clientY - this._downY) > Utils.DRAG_THRESHOLD) {
-            console.log("DRAGGIGNG");
             document.removeEventListener("pointermove", this.onPointerMoved);
             document.removeEventListener("pointerup", this.onPointerUp);
             const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 9620f753f..a7ab33008 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -38,7 +38,7 @@ export namespace Field {
     }
     export function toString(field: Field): string {
         if (typeof field === "string") {
-            return `"${field}"`;
+            return field;
         } else if (typeof field === "number" || typeof field === "boolean") {
             return String(field);
         } else if (field instanceof ObjectField) {
@@ -544,6 +544,7 @@ export namespace Doc {
                     copy[key] = field;
                 } else if (cfield instanceof ComputedField) {
                     copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
+                } else if (doc[key] instanceof Doc && (doc[key] as Doc).dontCopyOnAlias) {
                 } else if (field instanceof ObjectField) {
                     copy[key] = ObjectField.MakeCopy(field);
                 } else if (field instanceof Promise) {
@@ -800,13 +801,12 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
 });
 Scripting.addGlobal(function matchFieldValue(doc: Doc, key: string, value: any) {
     const fieldVal = doc[key] ? doc[key] : doc[key + "_ext"];
-    if (StrCast(fieldVal, null) !== undefined) return StrCast(fieldVal) === value;
-    if (NumCast(fieldVal, null) !== undefined) return NumCast(fieldVal) === value;
     if (Cast(fieldVal, listSpec("string"), []).length) {
         const vals = Cast(fieldVal, listSpec("string"), []);
         return vals.some(v => v === value);
     }
-    return false;
+    const fieldStr = Field.toString(fieldVal as Field);
+    return fieldStr == value;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) {
     const docFilters = Cast(container.docFilter, listSpec("string"), []);
@@ -818,7 +818,7 @@ Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: an
     }
     if (modifiers !== undefined) {
         docFilters.push(key);
-        docFilters.push(value);
+        docFilters.push(value)
         docFilters.push(modifiers);
         container.docFilter = new List<string>(docFilters);
     }
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 3683e5820..3b641fc8e 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -18,6 +18,7 @@ export const documentSchema = createSchema({
     creationDate: DateField,     // when the document was created
     links: listSpec(Doc),       // computed (readonly) list of links associated with this document
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias" or "copy")
+    dontCopyOnAlias: "boolean", // whether the document should not be copied when it is a field of a document that is being aliased
     removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerDown: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 9e6ad1c72..a2fdc7c89 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -124,8 +124,6 @@ export namespace WebSocket {
             socket.broadcast.emit(MessageStore.SetField.Message, newValue));
         if (newValue.type === Types.Text) {
             Search.updateDocument({ id: newValue.id, data: (newValue as any).data });
-            console.log("set field");
-            console.log("checking in");
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 0142f4e14cc8e291ee5acbae3cc4b81c95c4634a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Jan 2020 00:40:55 -0500
Subject: got rid of rule provider stuff.  fixed panning in freeform view with
 pivot layouts

---
 src/client/util/RichTextMenu.tsx                   | 20 ++---------
 src/client/util/RichTextRules.ts                   | 24 -------------
 src/client/util/RichTextSchema.tsx                 |  1 -
 src/client/views/DocumentDecorations.tsx           |  9 +----
 src/client/views/GestureOverlay.tsx                |  1 -
 src/client/views/InkingControl.tsx                 | 29 +--------------
 src/client/views/MainView.tsx                      |  4 ---
 src/client/views/OverlayView.tsx                   |  1 -
 src/client/views/Palette.tsx                       |  1 -
 .../views/collections/CollectionDockingView.tsx    |  1 -
 .../views/collections/CollectionLinearView.tsx     |  1 -
 .../views/collections/CollectionSchemaCells.tsx    |  1 -
 .../views/collections/CollectionSchemaView.tsx     |  1 -
 .../views/collections/CollectionStackingView.tsx   |  3 +-
 src/client/views/collections/CollectionSubView.tsx |  1 -
 .../views/collections/CollectionTreeView.tsx       |  3 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 42 +++++-----------------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  4 +--
 .../views/nodes/ContentFittingDocumentView.tsx     |  2 --
 src/client/views/nodes/DocumentBox.tsx             |  1 -
 src/client/views/nodes/DocumentView.tsx            |  8 ++---
 src/client/views/nodes/FieldView.tsx               |  1 -
 src/client/views/nodes/FormattedTextBox.tsx        | 33 -----------------
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 -
 src/client/views/nodes/ImageBox.tsx                |  1 -
 src/client/views/nodes/KeyValuePair.tsx            |  1 -
 src/client/views/nodes/VideoBox.tsx                |  1 -
 src/client/views/nodes/WebBox.tsx                  |  1 -
 src/client/views/pdf/PDFViewer.tsx                 |  1 -
 .../views/presentationview/PresElementBox.tsx      |  1 -
 src/client/views/search/SearchItem.tsx             |  1 -
 src/mobile/MobileInterface.tsx                     |  1 -
 32 files changed, 16 insertions(+), 185 deletions(-)

(limited to 'src/client/util/RichTextMenu.tsx')

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index eb5c90654..e07efe056 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -367,27 +367,11 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
-        const size = mark.attrs.fontSize;
-        if (this.editorProps) {
-            const ruleProvider = this.editorProps.ruleProvider;
-            const heading = NumCast(this.editorProps.Document.heading);
-            if (ruleProvider && heading) {
-                ruleProvider["ruleSize_" + heading] = size;
-            }
-        }
-        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
     }
 
     changeFontFamily = (mark: Mark, view: EditorView) => {
-        const fontName = mark.attrs.family;
-        if (this.editorProps) {
-            const ruleProvider = this.editorProps.ruleProvider;
-            const heading = NumCast(this.editorProps.Document.heading);
-            if (ruleProvider && heading) {
-                ruleProvider["ruleFont_" + heading] = fontName;
-            }
-        }
-        this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
     }
 
     // TODO: remove doesn't work
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index e20abb04c..0ee9481f0 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -67,12 +67,6 @@ export const inpRules = {
             new RegExp(/^%([0-9]+)\s$/),
             (state, match, start, end) => {
                 const size = Number(match[1]);
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    (Cast(FormattedTextBox.FocusedBox!.props.Document, Doc) as Doc).heading = size;
-                    return state.tr.deleteRange(start, end);
-                }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
 
@@ -175,12 +169,6 @@ export const inpRules = {
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    ruleProvider["ruleAlign_" + heading] = "center";
-                    return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                }
                 const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
@@ -191,12 +179,6 @@ export const inpRules = {
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    ruleProvider["ruleAlign_" + heading] = "left";
-                    return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                }
                 const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
@@ -207,12 +189,6 @@ export const inpRules = {
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
-                const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
-                if (ruleProvider && heading) {
-                    ruleProvider["ruleAlign_" + heading] = "right";
-                    return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                }
                 const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 5751c8c7e..fe7ab31a8 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -761,7 +761,6 @@ export class DashDocView {
                     fitToBox={BoolCast(dashDoc.fitToBox)}
                     addDocument={returnFalse}
                     removeDocument={removeDoc}
-                    ruleProvider={undefined}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={self._textBox.props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 322536b7e..e6a119d1f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -362,14 +362,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     onRadiusMove = (e: PointerEvent): void => {
         let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
         dist = dist < 3 ? 0 : dist;
-        let usingRule = false;
-        SelectionManager.SelectedDocuments().map(dv => {
-            const ruleProvider = dv.props.ruleProvider;
-            const heading = NumCast(dv.props.Document.heading);
-            ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
-            usingRule = usingRule || (ruleProvider && heading ? true : false);
-        });
-        !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplateForField ? dv.props.Document : Doc.GetProto(dv.props.Document)).
+        SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplateForField ? dv.props.Document : Doc.GetProto(dv.props.Document)).
             map(d => d.borderRounding = `${Math.min(100, dist)}%`);
         e.stopPropagation();
         e.preventDefault();
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 84d089b47..580c53a37 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -462,7 +462,6 @@ export default class GestureOverlay extends Touchable {
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 onClick={undefined}
-                ruleProvider={undefined}
                 removeDocument={undefined}
                 ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)}
                 ContentScaling={returnOne}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index ab53b76be..10d013865 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -55,34 +55,7 @@ export class InkingControl {
                 }
                 const oldColor = StrCast(targetDoc.backgroundColor);
                 let matchedColor = this._selectedColor;
-                const cvd = view.props.ContainingCollectionDoc;
-                let ruleProvider = view.props.ruleProvider;
-                // if (cvd) {
-                //     if (!cvd.colorPalette) {
-                //         const defaultPalette = ["rg(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
-                //             "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
-                //         const colorPalette = Cast(cvd.colorPalette, listSpec("string"));
-                //         if (!colorPalette) cvd.colorPalette = new List<string>(defaultPalette);
-                //     }
-                //     const cp = Cast(cvd.colorPalette, listSpec("string")) as string[];
-                //     let closest = 0;
-                //     let dist = 10000000;
-                //     const ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor));
-                //     for (let i = 0; i < cp.length; i++) {
-                //         const cpcol = Utils.fromRGBAstr(cp[i]);
-                //         const d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g));
-                //         if (d < dist) {
-                //             dist = d;
-                //             closest = i;
-                //         }
-                //     }
-                //     cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")";
-                //     cvd.colorPalette = new List(cp);
-                //     matchedColor = cp[closest];
-                //     ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined;
-                //     ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));
-                // }
-                (!ruleProvider && targetDoc) && (Doc.Layout(view.props.Document).backgroundColor = matchedColor);
+                targetDoc && (Doc.Layout(view.props.Document).backgroundColor = matchedColor);
 
                 return {
                     target: targetDoc,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 79e0cfea5..cd4e9a1a6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -276,7 +276,6 @@ export class MainView extends React.Component {
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
             onClick={undefined}
-            ruleProvider={undefined}
             removeDocument={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
@@ -377,7 +376,6 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={undefined}
-                    ruleProvider={undefined}
                     onClick={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     ContentScaling={returnOne}
@@ -404,7 +402,6 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={returnFalse}
-                    ruleProvider={undefined}
                     onClick={undefined}
                     ScreenToLocalTransform={this.mainContainerXf}
                     ContentScaling={returnOne}
@@ -500,7 +497,6 @@ export class MainView extends React.Component {
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={this.remButtonDoc}
-                    ruleProvider={undefined}
                     onClick={undefined}
                     ScreenToLocalTransform={this.buttonBarXf}
                     ContentScaling={returnOne}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 350a75d29..295cd7c6e 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -178,7 +178,6 @@ export class OverlayView extends React.Component {
                     // select={emptyFunction}
                     // layoutKey={"layout"}
                     bringToFront={emptyFunction}
-                    ruleProvider={undefined}
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={returnOne}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 811c24f53..10aac96a0 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -56,7 +56,6 @@ export default class Palette extends React.Component<PaletteProps> {
                             addDocTab={returnFalse}
                             pinToPres={emptyFunction}
                             removeDocument={undefined}
-                            ruleProvider={undefined}
                             onClick={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5f9b4e0c7..125cd828f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -723,7 +723,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             bringToFront={emptyFunction}
             addDocument={undefined}
             removeDocument={undefined}
-            ruleProvider={undefined}
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 77af2dc0e..8bf6b4202 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -108,7 +108,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 addDocTab={this.props.addDocTab}
                                 pinToPres={emptyFunction}
                                 removeDocument={this.props.removeDocument}
-                                ruleProvider={undefined}
                                 onClick={undefined}
                                 ScreenToLocalTransform={this.getTransform(dref)}
                                 ContentScaling={returnOne}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 79a34bc00..7e990ab4d 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -145,7 +145,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             DataDoc: this.props.rowProps.original,
             LibraryPath: [],
             fieldKey: this.props.rowProps.column.id as string,
-            ruleProvider: undefined,
             ContainingCollectionView: this.props.CollectionView,
             ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
             isSelected: returnFalse,
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b466d9511..994c7b351 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -144,7 +144,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 LibraryPath={this.props.LibraryPath}
                 childDocs={this.childDocs}
                 renderDepth={this.props.renderDepth}
-                ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
                 PanelWidth={this.previewWidth}
                 PanelHeight={this.previewHeight}
                 getTransform={this.getPreviewTransform}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b2e4b9d90..ec22bb813 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -39,7 +39,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
     @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
-    @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized).map(d => (Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, d).layout as Doc) || d); }
+    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => !pair.layout.isMinimized).map(pair => pair.layout); }
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return Math.max(this.props.Document.showTitle && !this.props.Document.showTitleHover ? 30 : 0, NumCast(this.props.Document.yMargin, 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
@@ -167,7 +167,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             LibraryPath={this.props.LibraryPath}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth + 1}
-            ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
             fitToBox={this.props.fitToBox}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6c4f940b6..063b5c5df 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -40,7 +40,6 @@ export interface CollectionViewProps extends FieldViewProps {
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
-    ruleProvider: Doc | undefined;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
     isAnnotationOverlay?: boolean;
     annotationsKey: string;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 2c8c53f97..ec348e74b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -43,7 +43,6 @@ export interface TreeViewProps {
     prevSibling?: Doc;
     renderDepth: number;
     deleteDoc: (doc: Doc) => boolean;
-    ruleProvider: Doc | undefined;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string, libraryPath?: Doc[]) => boolean;
@@ -352,7 +351,6 @@ class TreeView extends React.Component<TreeViewProps> {
                     LibraryPath={emptyPath}
                     renderDepth={this.props.renderDepth + 1}
                     showOverlays={this.noOverlays}
-                    ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     PanelWidth={this.docWidth}
                     PanelHeight={this.docHeight}
@@ -559,7 +557,6 @@ class TreeView extends React.Component<TreeViewProps> {
                 containingCollection={containingCollection}
                 prevSibling={docs[i]}
                 treeViewId={treeViewId}
-                ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
                 key={child[Id]}
                 indentDocument={indent}
                 outdentDocument={outdent}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aa1559416..65953dbdc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -54,7 +54,6 @@ export const panZoomSchema = createSchema({
     arrangeScript: ScriptField,
     arrangeInit: ScriptField,
     useClusters: "boolean",
-    isRuleProvider: "boolean",
     fitToBox: "boolean",
     xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
@@ -103,14 +102,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
-        const maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
-        let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
-        if (heading === 0) {
-            const sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
-                DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
-            heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
-        }
-        !this.Document.isRuleProvider && (newBox.heading = heading);
         this.addDocument(newBox);
     }
     private addDocument = (newBox: Doc) => {
@@ -412,16 +403,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay) {
             PDFMenu.Instance.fadeOut(true);
-            const minx = docs.length ? NumCast(docs[0].x) : 0;
-            const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
-            const miny = docs.length ? NumCast(docs[0].y) : 0;
-            const maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+            const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
+            const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
+            const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
+            const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
             const ranges = docs.filter(doc => doc).reduce((range, doc) => {
                 const layoutDoc = Doc.Layout(doc);
-                const x = NumCast(doc.x);
-                const xe = x + NumCast(layoutDoc.width);
-                const y = NumCast(doc.y);
-                const ye = y + NumCast(layoutDoc.height);
+                const x = this.childDataProvider(doc).x;//NumCast(doc.x);
+                const y = this.childDataProvider(doc).y;//NumCast(doc.y);
+                const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
+                const ye = this.childDataProvider(doc).height + y; //y + NumCast(layoutDoc.height);
                 return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
                 [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
             }, [[minx, maxx], [miny, maxy]]);
@@ -694,7 +685,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             Document: childLayout,
             LibraryPath: this.libraryPath,
             layoutKey: undefined,
-            ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves
             //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
@@ -779,7 +769,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             computedElementData.elements.push({
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
-                    ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || this.Document.freeformLayoutEngine === "pivot"} />,
                 bounds: this.childDataProvider(pair.layout)
@@ -827,20 +816,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }, "arrange contents");
     }
 
-    autoFormat = () => {
-        this.Document.isRuleProvider = !this.Document.isRuleProvider;
-        // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading.
-        this.Document.isRuleProvider && this.childLayoutPairs.map(pair =>
-            // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template)
-            DocListCast(Doc.Layout(pair.layout).data).map(heading => {
-                const headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, heading);
-                const headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout;
-                if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) {
-                    Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor;
-                }
-            })
-        );
-    }
 
     analyzeStrokes = async () => {
         const children = await DocListCastAsync(this.dataDoc.data);
@@ -908,7 +883,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" });
         layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
-        layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
         layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 614a68e7a..cd78f5d49 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -73,10 +73,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         .scale(1 / this.contentScaling())
 
     borderRounding = () => {
-        const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
         const ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined;
-        const br = StrCast((ld || this.props.Document).borderRounding);
-        return !br && ruleRounding ? ruleRounding : br;
+        return StrCast((ld || this.props.Document).borderRounding);
     }
 
     @computed
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index e97445f27..96102e0e8 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -24,7 +24,6 @@ interface ContentFittingDocumentViewProps {
     fitToBox?: boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    ruleProvider: Doc | undefined;
     focus?: (doc: Doc) => void;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
     CollectionView?: CollectionView;
@@ -97,7 +96,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         LibraryPath={this.props.LibraryPath}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
-                        ruleProvider={this.props.ruleProvider}
                         showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 863ea748b..6b7b652c6 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -96,7 +96,6 @@ export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocB
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
-                ruleProvider={this.props.ruleProvider}
                 addDocTab={this.props.addDocTab}
                 pinToPres={this.props.pinToPres}
                 getTransform={this.getTransform}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b491acb8d..2570d232a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -72,7 +72,6 @@ export interface DocumentViewProps {
     renderDepth: number;
     showOverlays?: (doc: Doc) => { title?: string, titleHover?: string, caption?: string };
     ContentScaling: () => number;
-    ruleProvider: Doc | undefined;
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
@@ -875,7 +874,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             renderDepth={this.props.renderDepth}
             showOverlays={this.props.showOverlays}
             ContentScaling={this.childScaling}
-            ruleProvider={this.props.ruleProvider}
             PanelWidth={this.props.PanelWidth}
             PanelHeight={this.props.PanelHeight}
             focus={this.props.focus}
@@ -967,16 +965,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
-        const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
-        const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
         const colorSet = this.setsLayoutProp("backgroundColor");
         const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
         const backgroundColor = (clusterCol && !colorSet) ?
             this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
-            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
+            StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
 
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
+        const borderRounding = this.getLayoutPropStr("borderRounding");
         const localScale = fullDegree;
 
         const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 6e6ee1712..538e433a9 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -27,7 +27,6 @@ export interface FieldViewProps {
     fitToBox?: boolean;
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
-    ruleProvider: Doc | undefined;
     Document: Doc;
     DataDoc?: Doc;
     LibraryPath: Doc[];
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 3814f02da..0a1a79bc4 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -89,7 +89,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
     private _reactionDisposer: Opt<IReactionDisposer>;
     private _heightReactionDisposer: Opt<IReactionDisposer>;
-    private _rulesReactionDisposer: Opt<IReactionDisposer>;
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
     private _pullReactionDisposer: Opt<IReactionDisposer>;
     private _pushReactionDisposer: Opt<IReactionDisposer>;
@@ -549,36 +548,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
             { fireImmediately: true });
 
-        this._rulesReactionDisposer = reaction(() => {
-            const ruleProvider = this.props.ruleProvider;
-            const heading = NumCast(this.layoutDoc.heading);
-            if (ruleProvider instanceof Doc) {
-                return {
-                    align: StrCast(ruleProvider["ruleAlign_" + heading], ""),
-                    font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"),
-                    size: NumCast(ruleProvider["ruleSize_" + heading], 13)
-                };
-            }
-            return undefined;
-        },
-            action((rules: any) => {
-                this._ruleFontFamily = rules ? rules.font : "Arial";
-                this._ruleFontSize = rules ? rules.size : 0;
-                rules && setTimeout(() => {
-                    const view = this._editorView!;
-                    if (this.ProseRef) {
-                        const n = new NodeSelection(view.state.doc.resolve(0));
-                        if (this._editorView!.state.doc.textContent === "") {
-                            view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))).
-                                replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true));
-                        } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) {
-                            view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }));
-                        }
-                        this.tryUpdateHeight();
-                    }
-                }, 0);
-            }), { fireImmediately: true }
-        );
         this._scrollToRegionReactionDisposer = reaction(
             () => StrCast(this.layoutDoc.scrollToLinkID),
             async (scrollToLinkID) => {
@@ -849,7 +818,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     componentWillUnmount() {
         this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer();
-        this._rulesReactionDisposer && this._rulesReactionDisposer();
         this._reactionDisposer && this._reactionDisposer();
         this._proxyReactionDisposer && this._proxyReactionDisposer();
         this._pushReactionDisposer && this._pushReactionDisposer();
@@ -1171,7 +1139,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                             addDocument={(doc: Doc) => { doc.hideSidebar = true; return this.addDocument(doc); }}
                             CollectionView={undefined}
                             ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)}
-                            ruleProvider={undefined}
                             renderDepth={this.props.renderDepth + 1}
                             ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                             chromeCollapsed={true}>
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index f7a530790..8dedfda9b 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -185,7 +185,6 @@ export class FormattedTextBoxComment {
                                     active={returnFalse}
                                     addDocument={returnFalse}
                                     removeDocument={returnFalse}
-                                    ruleProvider={undefined}
                                     addDocTab={returnFalse}
                                     pinToPres={returnFalse}
                                     dontRegisterView={true}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index db427710e..b7e904283 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -364,7 +364,6 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 addDocument={this.addDocument}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                ruleProvider={undefined}
                 renderDepth={this.props.renderDepth + 1}
                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                 chromeCollapsed={true}>
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 91f8bb3b0..46466aa0f 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -58,7 +58,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             LibraryPath: [],
             ContainingCollectionView: undefined,
             ContainingCollectionDoc: undefined,
-            ruleProvider: undefined,
             fieldKey: this.props.keyName,
             isSelected: returnFalse,
             select: emptyFunction,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 376d27380..52b4f391a 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -353,7 +353,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
                     addDocument={this.addDocumentWithTimestamp}
                     CollectionView={undefined}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                    ruleProvider={undefined}
                     renderDepth={this.props.renderDepth + 1}
                     ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                     chromeCollapsed={true}>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e9118e3ab..f21f9ff46 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -211,7 +211,6 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 addDocument={this.addDocument}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                ruleProvider={undefined}
                 renderDepth={this.props.renderDepth + 1}
                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}
                 chromeCollapsed={true}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 62467ce4d..d6829937e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -650,7 +650,6 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 addDocument={this.addDocument}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.overlayTransform}
-                ruleProvider={undefined}
                 renderDepth={this.props.renderDepth + 1}
                 ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}
                 chromeCollapsed={true}>
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index c02042380..92dc0f3d2 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -175,7 +175,6 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
                     fitToBox={StrCast(this.targetDoc.type).indexOf(DocumentType.COL) !== -1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
-                    ruleProvider={undefined}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
                     PanelWidth={() => this.props.PanelWidth() - 20}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 30fcc7908..21a4560cf 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -161,7 +161,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
-                    ruleProvider={undefined}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
                     getTransform={Transform.Identity}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index ea2fc917f..b1eaeaa0a 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -38,7 +38,6 @@ export default class MobileInterface extends React.Component {
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
-                ruleProvider={undefined}
                 onClick={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={returnOne}
-- 
cgit v1.2.3-70-g09d2

