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

