From b625719e71bcfc09e905b8f164b3ed76994b1d59 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 10 Nov 2019 19:16:21 -0500
Subject: fixed changing bullet type

---
 src/client/util/TooltipTextMenu.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 31d98887f..b1bd73e54 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -531,8 +531,9 @@ export class TooltipTextMenu {
     }
 
     //remove all node typeand apply the passed-in one to the selected text
-    changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => {
+    changeToNodeType = (nodeType: NodeType | undefined) => {
         //remove oldif (nodeType) { //add new
+        let view = this.view;
         if (nodeType === schema.nodes.bullet_list) {
             wrapInList(nodeType)(view.state, view.dispatch);
         } else {
-- 
cgit v1.2.3-70-g09d2


From f4ca517725714d770096d683478a6f5cc995c0c3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 10 Nov 2019 19:46:32 -0500
Subject: fixed switching between ordered list and bullets.

---
 src/client/util/RichTextSchema.tsx  | 3 ++-
 src/client/util/TooltipTextMenu.tsx | 7 ++++---
 2 files changed, 6 insertions(+), 4 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index cc3548e1a..a224c57eb 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -241,6 +241,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         },
         toDOM(node: Node<any>) {
             const bs = node.attrs.bulletStyle;
+            if (node.attrs.mapStyle === "bullet") return ['ul', 0];
             const decMap = bs ? "decimal" + bs : "";
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
             let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
@@ -273,7 +274,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             const bs = node.attrs.bulletStyle;
             const decMap = bs ? "decimal" + bs : "";
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
-            let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
+            let map = node.attrs.mapStyle === "decimal" ? decMap : node.attrs.mapStyle === "multi" ? multiMap : "";
             return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
             //return ["li", { class: `${map}` }, 0];
         }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index b1bd73e54..5cbf8b761 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -171,7 +171,8 @@ export class TooltipTextMenu {
 
         //list types
         this.listTypeToIcon = new Map();
-        this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
+        //this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
+        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":");
         this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1");
         this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A");
         // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜");
@@ -539,14 +540,14 @@ export class TooltipTextMenu {
         } else {
             var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
             if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
-                let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle);
+                let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
                 marks && tx3.ensureMarks([...marks]);
                 marks && tx3.setStoredMarks([...marks]);
 
                 view.dispatch(tx2);
             })) {
                 let tx2 = view.state.tr;
-                let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2;
+                let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
                 marks && tx3.ensureMarks([...marks]);
                 marks && tx3.setStoredMarks([...marks]);
 
-- 
cgit v1.2.3-70-g09d2


From d220b23867050fb6565fe639c3be28080f589aa2 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 11 Nov 2019 18:39:23 -0500
Subject: made font size and font style text marks be parametrized to simplify
 api.

---
 src/client/util/RichTextSchema.tsx          | 154 ++--------------
 src/client/util/TooltipTextMenu.tsx         | 277 +++++++++++++++++-----------
 src/client/views/nodes/FormattedTextBox.tsx |  15 +-
 3 files changed, 190 insertions(+), 256 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a224c57eb..a5e0ca720 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -483,72 +483,26 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM() { return codeDOM; }
     },
 
-    // pFontFamily: {
-    //     attrs: {
-    //         style: { default: 'font-family: "Times New Roman", Times, serif;' },
-    //     },
-    //     parseDOM: [{
-    //         tag: "span", getAttrs(dom: any) {
-    //             if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` };
-    //             if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` };
-    //             if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` };
-    //             if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` };
-    //             if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` };
-    //         }
-    //     }],
-    //     toDOM: (node: any) => ['span', {
-    //         style: node.attrs.style
-    //     }]
-    // },
-
-
     /* FONTS */
-    timesNewRoman: {
-        parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }],
-        toDOM: () => ['span', {
-            style: 'font-family: "Times New Roman", Times, serif;'
-        }]
-    },
-
-    arial: {
-        parseDOM: [{ style: 'font-family: Arial, Helvetica, sans-serif;' }],
-        toDOM: () => ['span', {
-            style: 'font-family: Arial, Helvetica, sans-serif;'
-        }]
-    },
-
-    georgia: {
-        parseDOM: [{ style: 'font-family: Georgia, serif;' }],
-        toDOM: () => ['span', {
-            style: 'font-family: Georgia, serif;'
-        }]
-    },
-
-    comicSans: {
-        parseDOM: [{ style: 'font-family: "Comic Sans MS", cursive, sans-serif;' }],
-        toDOM: () => ['span', {
-            style: 'font-family: "Comic Sans MS", cursive, sans-serif;'
-        }]
-    },
-
-    tahoma: {
-        parseDOM: [{ style: 'font-family: Tahoma, Geneva, sans-serif;' }],
-        toDOM: () => ['span', {
-            style: 'font-family: Tahoma, Geneva, sans-serif;'
-        }]
-    },
-
-    impact: {
-        parseDOM: [{ style: 'font-family: Impact, Charcoal, sans-serif;' }],
-        toDOM: () => ['span', {
-            style: 'font-family: Impact, Charcoal, sans-serif;'
-        }]
-    },
-
-    crimson: {
-        parseDOM: [{ style: 'font-family: "Crimson Text", sans-serif;' }],
-        toDOM: () => ['span', {
-            style: 'font-family: "Crimson Text", sans-serif;'
+    pFontFamily: {
+        attrs: {
+            family: { default: "Crimson Text" },
+        },
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                let cstyle = getComputedStyle(dom);
+                if (cstyle.font) {
+                    if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
+                    if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
+                    if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
+                    if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
+                    if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
+                    if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
+                }
+            }
+        }],
+        toDOM: (node) => ['span', {
+            style: `font-family: "${node.attrs.family}";`
         }]
     },
 
@@ -574,76 +528,6 @@ export const marks: { [index: string]: MarkSpec } = {
             style: `font-size: ${node.attrs.fontSize}px;`
         }]
     },
-
-    p10: {
-        parseDOM: [{ style: 'font-size: 10px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 10px;'
-        }]
-    },
-
-    p12: {
-        parseDOM: [{ style: 'font-size: 12px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 12px;'
-        }]
-    },
-
-    p14: {
-        parseDOM: [{ style: 'font-size: 14px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 14px;'
-        }]
-    },
-
-    p16: {
-        parseDOM: [{ style: 'font-size: 16px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 16px;'
-        }]
-    },
-
-    p18: {
-        parseDOM: [{ style: 'font-size: 18px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 18px;'
-        }]
-    },
-
-    p20: {
-        parseDOM: [{ style: 'font-size: 20px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 20px;'
-        }]
-    },
-
-    p24: {
-        parseDOM: [{ style: 'font-size: 24px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 24px;'
-        }]
-    },
-
-    p32: {
-        parseDOM: [{ style: 'font-size: 32px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 32px;'
-        }]
-    },
-
-    p48: {
-        parseDOM: [{ style: 'font-size: 48px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 48px;'
-        }]
-    },
-
-    p72: {
-        parseDOM: [{ style: 'font-size: 72px;' }],
-        toDOM: () => ['span', {
-            style: 'font-size: 72px;'
-        }]
-    },
 };
 
 export class ImageResizeView {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 5cbf8b761..9ce7acec8 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -29,11 +29,9 @@ export class TooltipTextMenu {
     public tooltip: HTMLElement;
     private view: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
-    private fontStyles: MarkType[];
-    private fontSizes: MarkType[];
     private listTypes: (NodeType | any)[];
-    private fontSizeToNum: Map<MarkType, number>;
-    private fontStylesToName: Map<MarkType, string>;
+    private fontSizes: Mark[] = [];
+    private fontStyles: Mark[] = [];
     private listTypeToIcon: Map<NodeType | any, string>;
     //private link: HTMLAnchorElement;
     private wrapper: HTMLDivElement;
@@ -135,46 +133,32 @@ export class TooltipTextMenu {
 
 
         //list of font styles
-        this.fontStylesToName = new Map();
-        this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman");
-        this.fontStylesToName.set(schema.marks.arial, "Arial");
-        this.fontStylesToName.set(schema.marks.georgia, "Georgia");
-        this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans MS");
-        this.fontStylesToName.set(schema.marks.tahoma, "Tahoma");
-        this.fontStylesToName.set(schema.marks.impact, "Impact");
-        this.fontStylesToName.set(schema.marks.crimson, "Crimson Text");
-        this.fontStyles = Array.from(this.fontStylesToName.keys());
+        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" }));
 
         //font sizes
-        this.fontSizeToNum = new Map();
-        this.fontSizeToNum.set(schema.marks.p10, 10);
-        this.fontSizeToNum.set(schema.marks.p12, 12);
-        this.fontSizeToNum.set(schema.marks.p14, 14);
-        this.fontSizeToNum.set(schema.marks.p16, 16);
-        this.fontSizeToNum.set(schema.marks.p18, 18);
-        this.fontSizeToNum.set(schema.marks.p20, 20);
-        this.fontSizeToNum.set(schema.marks.p24, 24);
-        this.fontSizeToNum.set(schema.marks.p32, 32);
-        this.fontSizeToNum.set(schema.marks.p48, 48);
-        this.fontSizeToNum.set(schema.marks.p72, 72);
-        this.fontSizeToNum.set(schema.marks.pFontSize, 10);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 12);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 14);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 16);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 18);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 20);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 24);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 32);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 48);
-        // this.fontSizeToNum.set(schema.marks.pFontSize, 72);
-        this.fontSizes = Array.from(this.fontSizeToNum.keys());
+        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 }));
 
         //list types
         this.listTypeToIcon = new Map();
         //this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
         this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":");
-        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1");
-        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A");
+        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1)");
+        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A)");
         // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜");
         this.listTypes = Array.from(this.listTypeToIcon.keys());
 
@@ -195,16 +179,13 @@ export class TooltipTextMenu {
 
     //label of dropdown will change to given label
     updateFontSizeDropdown(label: string) {
-        //filtering function - might be unecessary
-        let cut = (arr: MenuItem[]) => arr.filter(x => x);
-
         //font SIZES
         let fontSizeBtns: MenuItem[] = [];
-        this.fontSizeToNum.forEach((number, mark) => {
-            fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+        this.fontSizes.forEach(mark => {
+            fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize));
         });
 
-        let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), {
+        let newfontSizeDom = (new Dropdown(fontSizeBtns, {
             label: label,
             css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;"
         }) as MenuItem).render(this.view).dom;
@@ -215,20 +196,15 @@ export class TooltipTextMenu {
         this.fontSizeDom = newfontSizeDom;
     }
 
-    // Make the DIV element draggable
-
     //label of dropdown will change to given label
     updateFontStyleDropdown(label: string) {
-        //filtering function - might be unecessary
-        let cut = (arr: MenuItem[]) => arr.filter(x => x);
-
         //font STYLES
         let fontBtns: MenuItem[] = [];
-        this.fontStylesToName.forEach((name, mark) => {
-            fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
+        this.fontStyles.forEach((mark) => {
+            fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily));
         });
 
-        let newfontStyleDom = (new Dropdown(cut(fontBtns), {
+        let newfontStyleDom = (new Dropdown(fontBtns, {
             label: label,
             css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;"
         }) as MenuItem).render(this.view).dom;
@@ -468,8 +444,7 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(listTypeBtn);
         return listTypeBtn;
     }
-
-    //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
+    //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
     changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
         let { $cursor, ranges } = view.state.selection as TextSelection;
         let state = view.state;
@@ -498,29 +473,6 @@ export class TooltipTextMenu {
         });
 
         if (markType) {
-            // fontsize
-            if (markType.name[0] === 'p') {
-                let size = this.fontSizeToNum.get(markType);
-                if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-                if (this.editorProps) {
-                    let ruleProvider = this.editorProps.ruleProvider;
-                    let heading = NumCast(this.editorProps.Document.heading);
-                    if (ruleProvider && heading) {
-                        ruleProvider["ruleSize_" + heading] = size;
-                    }
-                }
-            }
-            else {
-                let fontName = this.fontStylesToName.get(markType);
-                if (fontName) { this.updateFontStyleDropdown(fontName); }
-                if (this.editorProps) {
-                    let ruleProvider = this.editorProps.ruleProvider;
-                    let heading = NumCast(this.editorProps.Document.heading);
-                    if (ruleProvider && heading) {
-                        ruleProvider["ruleFont_" + heading] = fontName;
-                    }
-                }
-            }
             //actually apply font
             if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
                 let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
@@ -531,6 +483,90 @@ export class TooltipTextMenu {
         }
     }
 
+    changeToFontFamily = (mark: Mark, view: EditorView) => {
+        let { $cursor, ranges } = view.state.selection as TextSelection;
+        let state = view.state;
+        let dispatch = view.dispatch;
+
+        //remove all other active font marks
+        if ($cursor) {
+            if (view.state.schema.marks.pFontFamily.isInSet(state.storedMarks || $cursor.marks())) {
+                dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontFamily));
+            }
+        } else {
+            let has = false;
+            for (let i = 0; !has && i < ranges.length; i++) {
+                let { $from, $to } = ranges[i];
+                has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily);
+            }
+            for (let i of ranges) {
+                if (has) {
+                    toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view);
+                }
+            }
+        }
+
+        let fontName = mark.attrs.family;
+        if (fontName) { this.updateFontStyleDropdown(fontName); }
+        if (this.editorProps) {
+            let ruleProvider = this.editorProps.ruleProvider;
+            let heading = NumCast(this.editorProps.Document.heading);
+            if (ruleProvider && heading) {
+                ruleProvider["ruleFont_" + heading] = fontName;
+            }
+        }
+        //actually apply font
+        if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
+            let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
+                { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: fontName }), view.state.schema);
+            view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
+        }
+        else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontFamily.create({ family: fontName })));
+        view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontFamily.create({ family: fontName })];
+    }
+
+    changeToFontSize = (mark: Mark, view: EditorView) => {
+        let { $cursor, ranges } = view.state.selection as TextSelection;
+        let state = view.state;
+        let dispatch = view.dispatch;
+
+        //remove all other active font marks
+        if ($cursor) {
+            if (view.state.schema.marks.pFontSize.isInSet(state.storedMarks || $cursor.marks())) {
+                dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontSize));
+            }
+        } else {
+            let has = false;
+            for (let i = 0; !has && i < ranges.length; i++) {
+                let { $from, $to } = ranges[i];
+                has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize);
+            }
+            for (let i of ranges) {
+                if (has) {
+                    toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view);
+                }
+            }
+        }
+
+        let size = mark.attrs.fontSize;
+        if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+        if (this.editorProps) {
+            let ruleProvider = this.editorProps.ruleProvider;
+            let heading = NumCast(this.editorProps.Document.heading);
+            if (ruleProvider && heading) {
+                ruleProvider["ruleSize_" + heading] = size;
+            }
+        }
+        //actually apply font
+        if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
+            let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
+                { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: size }), view.state.schema);
+            view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
+        }
+        else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontSize.create({ fontSize: size })));
+        view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontSize.create({ fontSize: size })];
+    }
+
     //remove all node typeand apply the passed-in one to the selected text
     changeToNodeType = (nodeType: NodeType | undefined) => {
         //remove oldif (nodeType) { //add new
@@ -558,7 +594,7 @@ export class TooltipTextMenu {
 
     //makes a button for the drop down FOR MARKS
     //css is the style you want applied to the button
-    dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType<any>, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) {
+    dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark<any>, view: EditorView) => any) {
         return new MenuItem({
             title: "",
             label: label,
@@ -567,7 +603,22 @@ export class TooltipTextMenu {
             css: css,
             enable() { return true; },
             run() {
-                changeToMarkInGroup(markType, view, groupMarks);
+                changeFontFamily(mark, view);
+            }
+        });
+    }
+    //makes a button for the drop down FOR MARKS
+    //css is the style you want applied to the button
+    dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark<any>, view: EditorView) => any) {
+        return new MenuItem({
+            title: "",
+            label: label,
+            execEvent: "",
+            class: "menuicon",
+            css: css,
+            enable() { return true; },
+            run() {
+                changeFontSize(mark, view);
             }
         });
     }
@@ -649,7 +700,9 @@ export class TooltipTextMenu {
                         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) => {
                             const markType = mark.type;
-                            this.changeToMarkInGroup(markType, this.view, []);
+                            if (mark.type === schema.marks.pFontFamily) this.changeToFontFamily(mark, this.view);
+                            else if (mark.type === schema.marks.pFontSize) this.changeToFontSize(mark, this.view);
+                            else this.changeToMarkInGroup(markType, this.view, []);
                         });
                     }
                 }
@@ -868,50 +921,23 @@ export class TooltipTextMenu {
         //UPDATE LIST ITEM DROPDOWN
 
         //UPDATE FONT STYLE DROPDOWN
-        let activeStyles = this.activeMarksOnSelection(this.fontStyles);
+        let activeStyles = this.activeFontFamilyOnSelection();
         if (activeStyles !== undefined) {
-            // activeStyles.forEach((markType) => {
-            //     this._activeMarks.push(this.view.state.schema.mark(markType));
-            // });
             if (activeStyles.length === 1) {
-                // if we want to update something somewhere with active font name
-                let fontName = this.fontStylesToName.get(activeStyles[0]);
-                if (fontName) { this.updateFontStyleDropdown(fontName); }
-            } else if (activeStyles.length === 0) {
-                //crimson on default
-                this.updateFontStyleDropdown("Crimson Text");
-            } else {
-                this.updateFontStyleDropdown("Various");
-            }
+                activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]);
+            } else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default");
         }
 
         //UPDATE FONT SIZE DROPDOWN
-        let activeSizes = this.activeMarksOnSelection(this.fontSizes);
+        let activeSizes = this.activeFontSizeOnSelection();
         if (activeSizes !== undefined) {
             if (activeSizes.length === 1) { //if there's only one active font size
-                // activeSizes.forEach((markType) => {
-                //     this._activeMarks.push(this.view.state.schema.mark(markType));
-                // });
-                let size = this.fontSizeToNum.get(activeSizes[0]);
-                if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-            } else if (activeSizes.length === 0) {
-                //should be 14 on default  
-                this.updateFontSizeDropdown("14 pt");
-            } else { //multiple font sizes selected
-                this.updateFontSizeDropdown("Various");
-            }
+                activeSizes[0] && this.updateFontSizeDropdown(String(activeSizes[0]) + " pt");
+            } else this.updateFontSizeDropdown(activeSizes.length ? "various" : "default");
         }
 
         this.update_mark_doms();
     }
-
-    public mark_key_pressed(marks: Mark<any>[]) {
-        if (this.view.state.selection.empty) {
-            if (marks) this._activeMarks = marks;
-            this.update_mark_doms();
-        }
-    }
-
     update_mark_doms() {
         this.reset_mark_doms();
         let foundlink = false;
@@ -946,6 +972,30 @@ export class TooltipTextMenu {
 
     }
 
+    //finds fontSize at start of selection
+    activeFontSizeOnSelection() {
+        //current selection
+        let state = this.view.state;
+        let 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
+        let state = this.view.state;
+        let 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(markGroup: MarkType[]) {
         //current selection
@@ -980,6 +1030,9 @@ export class TooltipTextMenu {
                 this._activeMarks = ref_node.marks;
                 activeMarks = markGroup.filter(mark_type => {
                     if (dispatch) {
+                        if (mark_type === state.schema.marks.pFontSize) {
+                            return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
+                        }
                         let mark = state.schema.mark(mark_type);
                         return ref_node.marks.includes(mark);
                     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 037c92a62..abf26826c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -93,8 +93,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
     private _pushReactionDisposer: Opt<IReactionDisposer>;
     private dropDisposer?: DragManager.DragDropDisposer;
 
-    @observable private _fontSize = 13;
-    @observable private _fontFamily = "Arial";
+    @observable private _ruleFontSize = 0;
+    @observable private _ruleFontFamily = "Arial";
     @observable private _fontAlign = "";
     @observable private _entered = false;
     public static SelectOnLoad = "";
@@ -187,9 +187,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
             }
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
-            if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
-                FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
-            }
 
             let tsel = this._editorView.state.selection.$from;
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000)));
@@ -552,8 +549,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
             return undefined;
         },
             action((rules: any) => {
-                this._fontFamily = rules ? rules.font : "Arial";
-                this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13);
+                this._ruleFontFamily = rules ? rules.font : "Arial";
+                this._ruleFontSize = rules ? rules.size : 0;
                 rules && setTimeout(() => {
                     const view = this._editorView!;
                     if (this._proseRef) {
@@ -1035,8 +1032,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
                     opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                     color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
                     pointerEvents: interactive,
-                    fontSize: this._fontSize,
-                    fontFamily: this._fontFamily,
+                    fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13),
+                    fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
                 }}
                 onContextMenu={this.specificContextMenu}
                 onKeyDown={this.onKeyPress}
-- 
cgit v1.2.3-70-g09d2


From d7150995d62c498ab8435de986b90d98bdca020c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 12 Nov 2019 15:07:03 -0500
Subject: a bunch of cleanup and bug fixes to text links and doculink buttons

---
 src/client/documents/Documents.ts                  |  5 +-
 src/client/util/LinkManager.ts                     |  2 -
 src/client/util/RichTextSchema.tsx                 |  4 +-
 src/client/util/TooltipTextMenu.tsx                | 23 +++---
 src/client/views/DocumentDecorations.tsx           | 10 ++-
 src/client/views/GlobalKeyHandler.ts               |  2 +-
 src/client/views/Main.tsx                          | 24 -------
 src/client/views/MainView.scss                     |  2 +-
 src/client/views/MainView.tsx                      |  8 +--
 src/client/views/linking/LinkMenuItem.tsx          |  1 -
 src/client/views/nodes/FormattedTextBox.tsx        |  9 ++-
 src/client/views/nodes/FormattedTextBoxComment.tsx | 83 ++++++++++++----------
 src/client/views/nodes/PDFBox.scss                 |  2 +
 src/client/views/nodes/PDFBox.tsx                  | 76 +++++++++++---------
 src/client/views/pdf/Annotation.tsx                |  6 +-
 src/client/views/pdf/PDFViewer.tsx                 | 14 +---
 src/new_fields/Doc.ts                              |  2 +-
 .../authentication/models/current_user_utils.ts    |  1 -
 18 files changed, 127 insertions(+), 147 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d1fcabc4a..ba9f87025 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -257,6 +257,9 @@ export namespace Docs {
             return PrototypeMap.get(type)!;
         }
 
+        /**
+         * A collection of all links in the database.  Ideally, this would be a search, but for now all links are cached here.
+         */
         export function MainLinkDocument() {
             return Prototypes.get(DocumentType.LINKDOC);
         }
@@ -703,6 +706,7 @@ export namespace DocUtils {
 
             linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title;
             linkDocProto.linkDescription = description;
+            linkDocProto.isPrototype = true;
 
             linkDocProto.anchor1 = source.doc;
             linkDocProto.anchor2 = target.doc;
@@ -714,7 +718,6 @@ export namespace DocUtils {
             linkDocProto.anchor2Timecode = target.doc.currentTimecode;
             linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1");
             linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2");
-            linkDocProto.borderRounding = "20";
             linkDocProto.width = linkDocProto.height = 0;
             linkDocProto.isBackground = true;
             linkDocProto.isButton = true;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ee2f2dadc..eedc4967d 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -34,8 +34,6 @@ export class LinkManager {
     // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes'
     // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type 
     public get LinkManagerDoc(): Doc | undefined {
-        // return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc));
-
         return Docs.Prototypes.MainLinkDocument();
     }
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a5e0ca720..76b8aeaa1 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -305,8 +305,8 @@ export const marks: { [index: string]: MarkSpec } = {
         }],
         toDOM(node: any) {
             return node.attrs.docref && node.attrs.title ?
-                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
-                ["a", { ...node.attrs }, 0];
+                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
+                ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0];
         }
     },
 
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 9ce7acec8..38471a955 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -17,7 +17,7 @@ import { DragManager } from "./DragManager";
 import { LinkManager } from "./LinkManager";
 import { schema } from "./RichTextSchema";
 import "./TooltipTextMenu.scss";
-import { Cast, NumCast } from '../../new_fields/Types';
+import { Cast, NumCast, StrCast } from '../../new_fields/Types';
 import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 const { toggleMark, setBlockType } = require("prosemirror-commands");
@@ -284,7 +284,7 @@ export class TooltipTextMenu {
                                     if (proto && docView) {
                                         proto.sourceContext = docView.props.ContainingCollectionDoc;
                                     }
-                                    let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab");
+                                    let text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab");
                                     if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) {
                                         proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link
                                     }
@@ -374,25 +374,20 @@ export class TooltipTextMenu {
     //     let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
     // }
 
-    makeLink = (targetDoc: Doc, location: string): string => {
-        let target = Utils.prepend("/doc/" + targetDoc[Id]);
+    makeLink = (targetDoc: Doc, title: string, location: string): string => {
+        let link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, 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).
+            addMark(this.view.state.selection.from, this.view.state.selection.to, link));
         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, guid: targetDoc[Id] });
-        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");
-        if (node) {
-            if (node.text) {
-                return node.text;
-            }
+        if (node && node.text) {
+            return node.text;
         }
         return "";
     }
 
     deleteLink = () => {
         let node = this.view.state.selection.$from.nodeAfter;
-        let link = node && node.marks.find(m => m.type.name === "link");
+        let link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link);
         let href = link!.attrs.href;
         if (href) {
             if (href.indexOf(Utils.prepend("/doc/")) === 0) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 0336440d5..55c211d1d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,6 +24,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { FieldView } from "./nodes/FieldView";
 import { IconBox } from "./nodes/IconBox";
 import React = require("react");
+import { DocumentType } from '../documents/DocumentTypes';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -112,7 +113,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             }
             else {
                 if (SelectionManager.SelectedDocuments().length > 0) {
-                    SelectionManager.SelectedDocuments()[0].props.Document.customTitle = true;
+                    SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._title.startsWith("-");
                     let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
                     if (typeof field === "number") {
                         SelectionManager.SelectedDocuments().forEach(d => {
@@ -174,6 +175,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
             var [sptX, sptY] = transform.transformPoint(0, 0);
             let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
+            if (documentView.props.Document.type === DocumentType.LINK) {
+                let rect = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont")[0].getBoundingClientRect();
+                sptX = rect.left;
+                sptY = rect.top;
+                bptX = rect.right;
+                bptY = rect.bottom;
+            }
             return {
                 x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
                 r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 9ca9fc163..8f397e331 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -165,7 +165,7 @@ export default class KeyManager {
                     }
                 }
                 break;
-            case "c":
+            case "t":
                 PromiseValue(Cast(CurrentUserUtils.UserDocument.Create, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 if (MainView.Instance.flyoutWidth === 240) {
                     MainView.Instance.flyoutWidth = 0;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index a91a2b69e..b21eb9c8f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -3,34 +3,11 @@ import { Docs } from "../documents/Documents";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import * as ReactDOM from 'react-dom';
 import * as React from 'react';
-import { Cast } from "../../new_fields/Types";
-import { Doc, DocListCastAsync } from "../../new_fields/Doc";
-import { List } from "../../new_fields/List";
 import { DocServer } from "../DocServer";
 import { AssignAllExtensions } from "../../extensions/General/Extensions";
 
 AssignAllExtensions();
 
-let swapDocs = async () => {
-    let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc);
-    // Docs.Prototypes.MainLinkDocument().allLinks = new List<Doc>();
-    if (oldDoc) {
-        let links = await DocListCastAsync(oldDoc.allLinks);
-        // if (links && DocListCast(links)) {
-        if (links && links.length) {
-            let data = await DocListCastAsync(Docs.Prototypes.MainLinkDocument().allLinks);
-            if (data) {
-                data.push(...links.filter(i => data!.indexOf(i) === -1));
-                Docs.Prototypes.MainLinkDocument().allLinks = new List<Doc>(data.filter((i, idx) => data!.indexOf(i) === idx));
-            }
-            else {
-                Docs.Prototypes.MainLinkDocument().allLinks = new List<Doc>(links);
-            }
-        }
-        CurrentUserUtils.UserDocument.linkManagerDoc = undefined;
-    }
-};
-
 (async () => {
     const info = await CurrentUserUtils.loadCurrentUser();
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
@@ -38,7 +15,6 @@ let swapDocs = async () => {
     if (info.id !== "__guest__") {
         // a guest will not have an id registered
         await CurrentUserUtils.loadUserDocument(info);
-        await swapDocs();
     }
     document.getElementById('root')!.addEventListener('wheel', event => {
         if (event.ctrlKey) {
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 21b135c49..a858a73c7 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -13,7 +13,7 @@
     left: 250px;
 }
 
-.mainView-container {
+#mainView-container {
     width: 100%;
     height: 100%;
     position: absolute;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 773da05df..83dbb433b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -37,7 +37,6 @@ import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { Scripting } from '../util/Scripting';
-import { LinkManager } from '../util/LinkManager';
 import { AudioBox } from './nodes/AudioBox';
 
 @observer
@@ -197,11 +196,6 @@ export class MainView extends React.Component {
         var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
         let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), {}, id);
         if (this.userDoc && ((workspaces = Cast(this.userDoc.workspaces, Doc)) instanceof Doc)) {
-            if (!this.userDoc.linkManagerDoc) {
-                let linkManagerDoc = new Doc();
-                linkManagerDoc.allLinks = new List<Doc>([]);
-                this.userDoc.linkManagerDoc = linkManagerDoc;
-            }
             Doc.AddDocToList(workspaces, "data", mainDoc);
             mainDoc.title = `Workspace ${DocListCast(workspaces.data).length}`;
         }
@@ -504,7 +498,7 @@ export class MainView extends React.Component {
     }
 
     render() {
-        return (<div className="mainView-container">
+        return (<div id="mainView-container">
             <DictationOverlay />
             <SharingManager />
             <GoogleAuthenticationManager />
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a6ee9c2c6..238660de3 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types';
 import { DragLinkAsDocument } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ContextMenu } from '../ContextMenu';
-import { MainView } from '../MainView';
 import { LinkFollowBox } from './LinkFollowBox';
 import './LinkMenu.scss';
 import React = require("react");
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index abf26826c..a0f8523a2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -842,6 +842,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
         this._editorView && this._editorView.destroy();
     }
     onPointerDown = (e: React.PointerEvent): void => {
+        FormattedTextBoxComment.textBox = this;
         let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
         pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
         if (this.props.onClick && e.button === 0) {
@@ -1018,11 +1019,13 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
     }
 
     render() {
+        trace();
         let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
-        let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground
-            ? "none" : "all";
+        let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
         if (this.props.isSelected()) {
             FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
+        } else if (FormattedTextBoxComment.textBox === this) {
+            FormattedTextBoxComment.Hide();
         }
         return (
             <div className={`formattedTextBox-cont`} ref={this._ref}
@@ -1031,7 +1034,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
                     background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
                     opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                     color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
-                    pointerEvents: interactive,
+                    pointerEvents: interactive ? "none" : "all",
                     fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13),
                     fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
                 }}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index bde278be3..2a7467108 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -6,8 +6,9 @@ import { Doc } from "../../../new_fields/Doc";
 import { schema } from "../../util/RichTextSchema";
 import { DocServer } from "../../DocServer";
 import { Utils } from "../../../Utils";
-import { StrCast } from "../../../new_fields/Types";
+import { StrCast, Cast, FieldValue } from "../../../new_fields/Types";
 import { FormattedTextBox } from "./FormattedTextBox";
+import { DocUtils } from "../../documents/Documents";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -61,6 +62,7 @@ export class FormattedTextBoxComment {
             FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
             FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
             FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
+            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
             FormattedTextBoxComment.tooltip.appendChild(input);
             FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
                 let keep = e.target && (e.target as any).type === "checkbox" ? true : false;
@@ -91,62 +93,67 @@ export class FormattedTextBoxComment {
         let 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;
+            lastState.selection.eq(state.selection)) {
+            return;
+        }
 
-        if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return;
+        const textBox = FormattedTextBoxComment.textBox;
+        if (!textBox || !textBox.props) {
+            return;
+        }
         let set = "none";
-        if (FormattedTextBoxComment.textBox && state.selection.$from) {
-            let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+        let nbef = 0;
+        // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
+        if (state.selection.$from) {
+            nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
-            const spos = state.selection.$from.pos - nbef;
-            const epos = state.selection.$from.pos + naft;
-            let child = state.selection.$from.nodeBefore;
-            let mark = child && findOtherUserMark(child.marks);
             let noselection = view.state.selection.$from === view.state.selection.$to;
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            let mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark);
+                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
             }
-            if (mark && child && nbef && naft) {
-                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified;
-                // These are in screen coordinates
-                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
-                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-                // The box in which the tooltip is positioned, to use as base
-                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
-                // Find a center-ish x position from the selection endpoints (when
-                // crossing lines, end may be more to the left)
-                let left = Math.max((start.left + end.left) / 2, start.left + 3);
-                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
-                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+            if (mark && child && ((nbef && naft) || !noselection)) {
+                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
                 set = "";
             }
         }
+        // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
         if (set === "none" && state.selection.$from) {
-            FormattedTextBoxComment.textBox = undefined;
-            let nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+            nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             let naft = findEndOfMark(state.selection.$from, view, findLinkMark);
-            let child = state.selection.$from.nodeBefore;
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             let mark = child && findLinkMark(child.marks);
             if (mark && child && nbef && naft) {
-                FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href);
+                FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
                 if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
                     let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    docTarget && DocServer.GetRefField(docTarget).then(linkDoc =>
-                        (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title)));
+                    docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
+                        if (linkDoc instanceof Doc) {
+                            let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc));
+                            let ext = (target && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field.  probably need an 'overviewLayout' and then just display the target Document ....
+                            let text = ext && StrCast(ext.text);
+                            ext && (FormattedTextBoxComment.tooltipText.textContent = "=> " + (text || StrCast(ext.title)));
+                        }
+                    });
                 }
-                // These are in screen coordinates
-                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
-                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-                // The box in which the tooltip is positioned, to use as base
-                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
-                // Find a center-ish x position from the selection endpoints (when
-                // crossing lines, end may be more to the left)
-                let left = Math.max((start.left + end.left) / 2, start.left + 3);
-                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
-                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
                 set = "";
             }
         }
+        if (set !== "none") {
+            // These are in screen coordinates
+            // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+            let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+            // The box in which the tooltip is positioned, to use as base
+            let box = (document.getElementById("mainView-container") as any).getBoundingClientRect();
+            // Find a center-ish x position from the selection endpoints (when
+            // crossing lines, end may be more to the left)
+            let left = Math.max((start.left + end.left) / 2, start.left + 3);
+            FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+            FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+        }
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
     }
 
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 963205206..2d92c9581 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -48,6 +48,7 @@
     }
     .pdfViewer-text {
         .textLayer {
+            will-change: transform;
             span {
                 user-select: none;
             }
@@ -59,6 +60,7 @@
     pointer-events: all;
     .pdfViewer-text {
         .textLayer {
+            will-change: transform;
             span {
                 user-select: text;
             }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3baa6eb09..8e0515f8a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, runInAction, reaction, IReactionDisposer, trace } from 'mobx';
+import { action, observable, runInAction, reaction, IReactionDisposer, trace, untracked, computed } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
@@ -32,7 +32,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     private _valueValue: string = "";
     private _scriptValue: string = "";
     private _searchString: string = "";
-    private _initialScale: number | undefined;  // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not.  Getting bigger after startup won't make it automatically be live.
+    private _initialScale: number = 0;  // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not.  Getting bigger after startup won't make it automatically be live.
     private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title
     private _pdfViewer: PDFViewer | undefined;
     private _searchRef = React.createRef<HTMLInputElement>();
@@ -46,6 +46,11 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
     @observable private _pageControls = false;
 
+    constructor(props: any) {
+        super(props);
+        this._initialScale = this.props.ScreenToLocalTransform().Scale;
+    }
+
     componentWillUnmount() {
         this._selectReactionDisposer && this._selectReactionDisposer();
     }
@@ -190,39 +195,46 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
     }
 
-    render() {
+    @computed get renderTitleBox() {
+        let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
+        return <div className="pdfBox-title-outer" >
+            <div className={classname} >
+                <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
+            </div>
+        </div>;
+    }
+
+    @computed get renderPdfView() {
         trace();
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
-        let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf;
-        if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale;
+        return <div className={classname} style={{
+            width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
+            height: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
+            transform: `scale(${this.props.Document.fitWidth ? this.props.ContentScaling() : 1})`
+        }} onContextMenu={this.specificContextMenu} onPointerDown={e => {
+            let hit = document.elementFromPoint(e.clientX, e.clientY);
+            if (hit && hit.localName === "span" && this.props.isSelected()) {  // drag selecting text stops propagation
+                e.button === 0 && e.stopPropagation();
+            }
+        }}>
+            <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
+                setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
+                renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
+                Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
+                addDocTab={this.props.addDocTab} focus={this.props.focus}
+                pinToPres={this.props.pinToPres} addDocument={this.addDocument}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
+                isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
+                fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
+            {this.settingsPanel()}
+        </div>;
+    }
+
+    render() {
+        const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
         if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
-        return (!this.extensionDoc || noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
-            <div className="pdfBox-title-outer" >
-                <div className={classname} >
-                    <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
-                </div>
-            </div> :
-            <div className={classname} style={{
-                width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
-                height: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
-                transform: `scale(${this.props.Document.fitWidth ? this.props.ContentScaling() : 1})`
-            }} onContextMenu={this.specificContextMenu} onPointerDown={e => {
-                let hit = document.elementFromPoint(e.clientX, e.clientY);
-                if (hit && hit.localName === "span" && this.props.isSelected()) {  // drag selecting text stops propagation
-                    e.button === 0 && e.stopPropagation();
-                }
-            }}>
-                <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
-                    setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
-                    renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
-                    Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
-                    addDocTab={this.props.addDocTab} focus={this.props.focus}
-                    pinToPres={this.props.pinToPres} addDocument={this.addDocument}
-                    ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
-                    isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
-                    fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
-                {this.settingsPanel()}
-            </div>);
+        return !pdfUrl || !this._pdf || !this.extensionDoc || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
+            this.renderTitleBox : this.renderPdfView;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 2d8f47666..936af9ab8 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -52,11 +52,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
 
         this._brushDisposer = reaction(
             () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.isBrushedHighlightedDegree(FieldValue(Cast(this.props.document.group, Doc))!),
-            (brushed) => {
-                if (brushed !== undefined) {
-                    runInAction(() => this._brushed = brushed !== 0);
-                }
-            }
+            brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0)
         );
     }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 060ba8613..0cb671156 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -199,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
 
         this._annotationReactionDisposer = reaction(
             () => this.extensionDoc && DocListCast(this.extensionDoc.annotations),
-            annotations => annotations && annotations.length && this.renderAnnotations(annotations, true),
+            annotations => annotations && annotations.length && (this._annotations = annotations),
             { fireImmediately: true });
 
         this._filterReactionDisposer = reaction(
@@ -297,18 +297,6 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         this.Index = -1;
         return mainAnnoDoc;
     }
-
-    @action
-    renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
-        if (removeOldAnnotations) {
-            this._annotations = annotations;
-        }
-        else {
-            this._annotations.push(...annotations);
-            this._annotations = new Array<Doc>(...this._annotations);
-        }
-    }
-
     @action
     prevAnnotation = () => {
         this.Index = Math.max(this.Index - 1, 0);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6aad4a6be..3bf1129b5 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -669,8 +669,8 @@ export namespace Doc {
         return doc;
     }
 
-    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; }
 
+    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; }
     export function linkFollowUnhighlight() {
         Doc.UnhighlightAll();
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 56ea5bfe1..833e44bf6 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -15,7 +15,6 @@ import { RouteStore } from "../../RouteStore";
 import { InkingControl } from "../../../client/views/InkingControl";
 import { DragManager } from "../../../client/util/DragManager";
 import { nullAudio } from "../../../new_fields/URLField";
-import { LinkManager } from "../../../client/util/LinkManager";
 
 export class CurrentUserUtils {
     private static curr_id: string;
-- 
cgit v1.2.3-70-g09d2

