From 968d678e8aeb6f57cc892e3fe789067a8246c5fe Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 4 Dec 2019 17:47:28 -0500
Subject: started to add paths

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

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3b786e61d..4612f2885 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -761,8 +761,9 @@ export class DashDocView {
                     this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px";
                 });
                 ReactDOM.render(<DocumentView
-                    fitToBox={BoolCast(dashDoc.fitToBox)}
                     Document={dashDoc}
+                    LibraryPath={tbox.props.LibraryPath}
+                    fitToBox={BoolCast(dashDoc.fitToBox)}
                     addDocument={returnFalse}
                     removeDocument={removeDoc}
                     ruleProvider={undefined}
-- 
cgit v1.2.3-70-g09d2


From 6ee91f4f5044465dfe33113dd84b7ffc8308bd2a Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 5 Dec 2019 11:11:07 -0500
Subject: compile warning fixes.

---
 src/client/util/RichTextSchema.tsx                 |  6 +++---
 .../views/collections/CollectionDockingView.tsx    |  6 +++---
 .../views/collections/CollectionSchemaCells.tsx    |  1 +
 src/client/views/nodes/DocumentView.tsx            |  9 +++++++--
 src/client/views/nodes/FormattedTextBox.tsx        |  4 ++--
 src/client/views/nodes/ImageBox.scss               | 23 +++++++++++-----------
 src/client/views/nodes/ImageBox.tsx                |  7 ++++---
 src/client/views/nodes/KeyValuePair.tsx            |  1 +
 src/client/views/pdf/PDFViewer.tsx                 |  1 +
 src/new_fields/documentSchemas.ts                  |  3 +++
 src/new_fields/util.ts                             |  2 +-
 11 files changed, 37 insertions(+), 26 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 4612f2885..189bf08f7 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -671,15 +671,15 @@ export class DashDocCommentView {
         this._collapsed.className = "formattedTextBox-inlineComment";
         this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
         this._view = view;
-        let targetNode = () => {
+        const targetNode = () => {
             for (let i = getPos() + 1; i < view.state.doc.nodeSize; i++) {
-                let m = view.state.doc.nodeAt(i);
+                const m = view.state.doc.nodeAt(i);
                 if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
                     return { node: m, pos: i } as { node: any, pos: number };
                 }
             }
             return undefined;
-        }
+        };
         this._collapsed.onpointerdown = (e: any) => {
             const target = targetNode();
             if (target) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e35dc4c00..4374cde3c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -529,7 +529,7 @@ interface DockedFrameProps {
     documentId: FieldId;
     dataDocumentId: FieldId;
     glContainer: any;
-    libraryPath: (FieldId[])
+    libraryPath: (FieldId[]);
     //collectionDockingView: CollectionDockingView
 }
 @observer
@@ -558,8 +558,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     async setupLibraryPath() {
         Promise.all(this.props.libraryPath.map(async docid => {
-            let d = await DocServer.GetRefField(docid);
-            return d instanceof Doc ? d : undefined
+            const d = await DocServer.GetRefField(docid);
+            return d instanceof Doc ? d : undefined;
         })).then(action((list: (Doc | undefined)[]) => this._libraryPath = list.filter(d => d).map(d => d as Doc)));
     }
 
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 1700c14cf..171dc4606 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -143,6 +143,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         const props: FieldViewProps = {
             Document: this.props.rowProps.original,
             DataDoc: this.props.rowProps.original,
+            LibraryPath: [],
             fieldKey: this.props.rowProps.column.id as string,
             ruleProvider: undefined,
             ContainingCollectionView: this.props.CollectionView,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fef365bf3..9f5b86e8d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -528,8 +528,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 SelectionManager.SelectDoc(this, false);
             }
         });
-        let path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc().LibraryBtn as Doc).sourcePanel as Doc) ? "" : d.title), "");
-        cm.addItem({ description: `path: ${path}`, event: () => { }, icon: "check" })
+        const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc().LibraryBtn as Doc).sourcePanel as Doc) ? "" : d.title), "");
+        cm.addItem({
+            description: `path: ${path}`, event: () => {
+                this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
+                Doc.BrushDoc(this.props.Document);
+            }, icon: "check"
+        });
     }
 
     // does Document set a layout prop 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 481ae441e..e7c59ccb4 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -376,8 +376,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         e.preventDefault(); // prevents text from being selected during drag
     }
     sidebarMove = (e: PointerEvent) => {
-        let bounds = this.CurrentDiv.getBoundingClientRect();
-        this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY))
+        const bounds = this.CurrentDiv.getBoundingClientRect();
+        this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY));
         this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
     }
     sidebarUp = (e: PointerEvent) => {
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 3b42c2352..cf5d999a7 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,13 +1,22 @@
-.imageBox {
+.imageBox, .imageBox-dragging{
     pointer-events: all;
     border-radius: inherit;
     width:100%;
     height:100%;
     position: absolute;
     transform-origin: top left;
+    .imageBox-fader {
+        pointer-events: all;
+    }
 }
 
-.imageBox-cont, .imageBox-cont-dragging {
+.imageBox-dragging {
+    .imageBox-fader {
+        pointer-events: none;
+    }
+}
+
+.imageBox-cont {
     padding: 0vw;
     position: absolute;
     text-align: center;
@@ -22,15 +31,6 @@
         width: 100%;
         pointer-events: all;
     }
-    .imageBox-fader {
-        pointer-events: all;
-    }
-}
-
-.imageBox-cont-dragging {
-    .imageBox-fader {
-        pointer-events: none;
-    }
 }
 
 .imageBox-dot {
@@ -43,7 +43,6 @@
     background: gray;
 }
 
-
 #google-photos {
     transition: all 0.5s ease 0s;
     width: 30px;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b4a51657f..f60888929 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -308,7 +308,6 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         if (field instanceof ImageField) paths = [[this.choosePath(field.url), nativeWidth / nativeHeight]];
         paths.push(...altpaths);
         // }
-        const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
         const rotation = NumCast(this.Document.rotation, 0);
         const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
         const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
@@ -318,7 +317,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
         !this.Document.ignoreAspect && this.resize(srcpath);
 
-        return <div className={`imageBox-cont${dragging}`} key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+        return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
             <div className="imageBox-fader" >
                 <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
                     src={srcpath}
@@ -350,7 +349,9 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
     contentFunc = () => [this.content];
     render() {
-        return (<div className="imageBox" onContextMenu={this.specificContextMenu}
+        TraceMobx();
+        const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
+        return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
             style={{
                 transform: `scale(${this.props.ContentScaling()})`,
                 width: `${100 / this.props.ContentScaling()}%`,
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index bca68cdd9..3e253b83a 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -56,6 +56,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         const props: FieldViewProps = {
             Document: this.props.doc,
             DataDoc: this.props.doc,
+            LibraryPath: [],
             ContainingCollectionView: undefined,
             ContainingCollectionDoc: undefined,
             ruleProvider: undefined,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 69aacc902..3aa5d1d2c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -634,6 +634,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     @computed get overlayLayer() {
         return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
             <CollectionFreeFormView {...this.props}
+                LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? []}
                 annotationsKey={this.annotationsKey}
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelWidth}
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 4c2f061a6..5c2ba6111 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -27,6 +27,9 @@ export const documentSchema = createSchema({
     isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed
     isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee)
     type: "string",             // enumerated type of document
+    treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
+    treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
+    preventTreeViewOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
     currentTimecode: "number",   // current play back time of a temporal document (video / audio)
     summarizedDocs: listSpec(Doc), // documents that are summarized by this document (and which will typically be opened by clicking this document)
     maximizedDocs: listSpec(Doc), // documents to maximize when clicking this document (generally this document will be an icon)
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 4147be278..7bd9ef8da 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -13,7 +13,7 @@ function _readOnlySetter(): never {
 }
 
 export function TraceMobx() {
-    //trace();
+    trace();
 }
 
 export interface GetterResult {
-- 
cgit v1.2.3-70-g09d2


From ed1698dc1b225ebc79c1dbe5b831c3e164a312e7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 5 Dec 2019 14:25:02 -0500
Subject: added style shortcut for text

---
 src/client/util/RichTextRules.ts    | 22 ++++++++++++++++++++++
 src/client/util/RichTextSchema.tsx  |  3 ++-
 src/client/util/TooltipTextMenu.tsx | 31 +++++++++++++++++++++++++++++--
 3 files changed, 53 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index cef1011cc..4e3192dd4 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -5,6 +5,7 @@ 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 } from "../documents/Documents";
 import { Id } from "../../new_fields/FieldSymbols";
 
@@ -71,6 +72,27 @@ export const inpRules = {
                 }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
+        new InputRule(
+            new RegExp(/%[a-z]*\s$/),
+            (state, match, start, end) => {
+                const color = match[0].substring(1, match[0].length - 1);
+                let marks = TooltipTextMenuManager.Instance._brushMap.get(color);
+                if (marks) {
+                    let tr = state.tr.deleteRange(start, end);
+                    return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+                }
+                if (color) {
+                    return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+                }
+                return state.tr.deleteRange(start, end);
+            }),
+        new InputRule(
+            new RegExp(/%%$/),
+            (state, match, start, end) => {
+                let tr = state.tr.deleteRange(start, end);
+                let marks = state.tr.selection.$anchor.nodeBefore?.marks;
+                return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
+            }),
         new InputRule(
             new RegExp(/t$/),
             (state, match, start, end) => {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 189bf08f7..5fcf15cb7 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -324,8 +324,9 @@ export const marks: { [index: string]: MarkSpec } = {
         }
     },
 
+
     // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
-    color: {
+    pFontColor: {
         attrs: {
             color: { default: "#000" }
         },
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 35a099c48..89037dcb2 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -20,6 +20,7 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
+import { Keys } from "../views/search/FilterBox";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
@@ -840,7 +841,7 @@ export class TooltipTextMenu {
     public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
         if (state.selection.empty) return false;
 
-        const colorMark = state.schema.mark(state.schema.marks.color, { color: color });
+        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
         dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
     }
 
@@ -998,6 +999,7 @@ export class TooltipTextMenu {
         });
 
         const self = this;
+        const input = document.createElement("input");
         const clearBrush = new MenuItem({
             title: "Clear brush",
             execEvent: "",
@@ -1007,7 +1009,31 @@ export class TooltipTextMenu {
                 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;
             },
@@ -1488,7 +1514,7 @@ export class TooltipTextMenu {
 }
 
 
-class TooltipTextMenuManager {
+export class TooltipTextMenuManager {
     private static _instance: TooltipTextMenuManager;
 
     public pinnedX: number = 0;
@@ -1498,6 +1524,7 @@ class TooltipTextMenuManager {
     private _isPinned: boolean = false;
 
     public _brushMarks: Set<Mark> | undefined;
+    public _brushMap: Map<string, Set<Mark>> = new Map();
     public _brushIsEmpty: boolean = true;
 
     public color: String = "#000";
-- 
cgit v1.2.3-70-g09d2


From fef12d4ca96d35c230a3a5e7c053b9b90eb38243 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Dec 2019 15:00:33 -0500
Subject: more fixes for inline comments.

---
 src/client/util/RichTextRules.ts             |  2 +-
 src/client/util/RichTextSchema.tsx           | 25 ++++++++++++++++++-------
 src/client/views/nodes/FormattedTextBox.scss |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx  | 14 ++++++++++++++
 4 files changed, 34 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 4e3192dd4..cd4bfdc63 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -174,7 +174,7 @@ export const inpRules = {
                 const newNode = schema.nodes.dashComment.create({ docid: target[Id] });
                 const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: target[Id], float: "right" });
                 const sm = state.storedMarks || undefined;
-                const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end)));
             }),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 5fcf15cb7..8c74c3f68 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -672,23 +672,34 @@ export class DashDocCommentView {
         this._collapsed.className = "formattedTextBox-inlineComment";
         this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
         this._view = view;
-        const targetNode = () => {
-            for (let i = getPos() + 1; i < view.state.doc.nodeSize; i++) {
+        const targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+            for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
                 const m = view.state.doc.nodeAt(i);
                 if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
-                    return { node: m, pos: i } as { node: any, pos: number };
+                    return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
                 }
             }
+            const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
+            view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
+            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))) } catch (e) { } }, 0);
             return undefined;
         };
         this._collapsed.onpointerdown = (e: any) => {
+            e.stopPropagation();
+        };
+        this._collapsed.onpointerup = (e: any) => {
             const target = targetNode();
             if (target) {
-                view.dispatch(view.state.tr.
-                    setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true })); // update the attrs 
-                setTimeout(() => node.attrs.hidden && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)), 100);
+                let expand = target.hidden;
+                let tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+                view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs 
+                setTimeout(() => {
+                    expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))) } catch (e) { }
+                }, 0);
             }
-        };
+            e.stopPropagation();
+        }
         this._collapsed.onpointerenter = (e: any) => {
             DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
             e.preventDefault();
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index f3a14169a..baf758c0e 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -195,7 +195,7 @@ footnote::after {
     position: relative;
     width: 40px;
     height: 20px;
-    &::after {
+    &::before {
         content: "→";
     }
     &:hover {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index e7c59ccb4..3302d2e54 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -913,6 +913,20 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     static _userStyleSheet: any = addStyleSheet();
 
     onClick = (e: React.MouseEvent): void => {
+        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.
+            let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+            let 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) {
+                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
+                e.preventDefault();
+            }
+            if (!node && this._proseRef) {
+                let lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+                if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+                    this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
+                }
+            }
+        }
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
         (e.nativeEvent as any).formattedHandled = true;
         // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
-- 
cgit v1.2.3-70-g09d2


From e5ca273b70c2c41f953ad2a534afabdb313f3e99 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 9 Dec 2019 10:16:55 -0500
Subject: changed styles to allow multi-character names using %.  switched
 default "styles" of todo, ignore, etc to use % style notation

---
 src/client/util/RichTextRules.ts            | 25 +++++++++++++++++--------
 src/client/util/RichTextSchema.tsx          |  1 +
 src/client/views/nodes/FormattedTextBox.tsx | 13 ++++++++++++-
 3 files changed, 30 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index cd4bfdc63..c9414fb0b 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -73,18 +73,23 @@ export const inpRules = {
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
         new InputRule(
-            new RegExp(/%[a-z]*\s$/),
+            new RegExp(/%[a-z]+$/),
             (state, match, start, end) => {
-                const color = match[0].substring(1, match[0].length - 1);
+                const color = match[0].substring(1, match[0].length);
                 let marks = TooltipTextMenuManager.Instance._brushMap.get(color);
                 if (marks) {
                     let tr = state.tr.deleteRange(start, end);
                     return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
                 }
-                if (color) {
+                let isValidColor = (strColor: string) => {
+                    var s = new Option().style;
+                    s.color = strColor;
+                    return s.color == strColor.toLowerCase(); // 'false' if color wasn't assigned
+                }
+                if (isValidColor(color)) {
                     return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
                 }
-                return state.tr.deleteRange(start, end);
+                return null;
             }),
         new InputRule(
             new RegExp(/%%$/),
@@ -96,29 +101,33 @@ export const inpRules = {
         new InputRule(
             new RegExp(/t$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
                 const node = (state.doc.resolve(start) as any).nodeAfter;
+                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
                 return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
         new InputRule(
             new RegExp(/i$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
                 const node = (state.doc.resolve(start) as any).nodeAfter;
+                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
                 return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
         new InputRule(
             new RegExp(/!$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
                 const node = (state.doc.resolve(start) as any).nodeAfter;
+                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
                 return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
         new InputRule(
             new RegExp(/x$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
                 const node = (state.doc.resolve(start) as any).nodeAfter;
+                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
                 return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
         new InputRule(
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 8c74c3f68..2102ea85b 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -514,6 +514,7 @@ export const marks: { [index: string]: MarkSpec } = {
             tag: { default: "" }
         },
         group: "inline",
+        inclusive: false,
         toDOM(node: any) {
             const uid = node.attrs.userid.replace(".", "").replace("@", "");
             return node.attrs.opened ?
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 3302d2e54..11fececd7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -351,7 +351,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
         }
         if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "0" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
         }
         if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
@@ -1040,7 +1040,18 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         }
         this.doLinkOnDeselect();
     }
+
     onKeyPress = (e: React.KeyboardEvent) => {
+        if (!this._editorView!.state.selection.empty && e.key === "%") {
+            (this._editorView!.state as any).EnteringStyle = true;
+            e.preventDefault();
+            e.stopPropagation();
+            return;
+        }
+
+        if (this._editorView!.state.selection.empty || !(this._editorView!.state as any).EnteringStyle) {
+            (this._editorView!.state as any).EnteringStyle = false;
+        }
         if (e.key === "Escape") {
             SelectionManager.DeselectAll();
         }
-- 
cgit v1.2.3-70-g09d2


From 78a65a6d871ce89f3dfed76035e379df631757a8 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 9 Dec 2019 15:55:17 -0500
Subject: fixed up bulleted list formatting.  disabled auto-flyout of sidebar.

---
 src/client/util/RichTextSchema.tsx           | 12 ++----
 src/client/views/MainView.tsx                | 14 +++---
 src/client/views/nodes/FormattedTextBox.scss | 64 +++++++++++++++++-----------
 src/client/views/nodes/FormattedTextBox.tsx  | 57 ++++++++++++++-----------
 4 files changed, 80 insertions(+), 67 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2102ea85b..9e95314a9 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -249,16 +249,13 @@ export const nodes: { [index: string]: NodeSpec } = {
             bulletStyle: { default: 0 },
             mapStyle: { default: "decimal" },
             setFontSize: { default: undefined },
-            setFontFamily: { default: undefined },
+            setFontFamily: { default: "inherit" },
             inheritedFontSize: { default: undefined },
             visibility: { default: true }
         },
         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" : "";
-            const map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
+            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
             const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
             const ffam = node.attrs.setFontFamily;
             return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] :
@@ -285,10 +282,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         ...listItem,
         content: 'paragraph block*',
         toDOM(node: any) {
-            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" : "";
-            const map = node.attrs.mapStyle === "decimal" ? decMap : node.attrs.mapStyle === "multi" ? multiMap : "";
+            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
             return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
             //return ["li", { class: `${map}` }, 0];
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bcdc3a453..01cd7957c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -315,11 +315,11 @@ export class MainView extends React.Component {
 
     @action
     pointerOverDragger = () => {
-        if (this.flyoutWidth === 0) {
-            this.flyoutWidth = 250;
-            this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
-            this._flyoutTranslate = false;
-        }
+        // if (this.flyoutWidth === 0) {
+        //     this.flyoutWidth = 250;
+        //     this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30;
+        //     this._flyoutTranslate = false;
+        // }
     }
 
     @action
@@ -338,7 +338,7 @@ export class MainView extends React.Component {
     @action
     onPointerUp = (e: PointerEvent) => {
         if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4) {
-            this.flyoutWidth = this.flyoutWidth < 5 ? 250 : 0;
+            this.flyoutWidth = this.flyoutWidth < 15 ? 250 : 0;
             this.flyoutWidth && (this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30);
         }
         document.removeEventListener("pointermove", this.onPointerMove);
@@ -429,7 +429,7 @@ export class MainView extends React.Component {
                         style={{ backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} >
                         <span title="library View Dragger" style={{
                             width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
-                            height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
+                            //height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
                             position: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "absolute" : "fixed",
                             top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "0"
                         }} />
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index baf758c0e..8256cffac 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -225,33 +225,47 @@ footnote::after {
 
 .ProseMirror {
     touch-action: none;
+    span {
+        font-family: inherit;
+    }
 
+    ol, ul {
+        counter-reset: deci1 0 multi1 0;
+        padding-left: 1em;
+        font-family: inherit;
+    }
     ol {
-        counter-reset: deci1 0;
-        padding-left: 0px;
+        margin-left: 1em;
+        font-family: inherit;
     }
-
-    .decimal1-ol { counter-reset: deci1; p { display: inline };                     ul, ol { padding-left: 30px; } }
-    .decimal2-ol { counter-reset: deci2; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } }
-    .decimal3-ol { counter-reset: deci3; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } }
-    .decimal4-ol { counter-reset: deci4; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } }
-    .decimal5-ol { counter-reset: deci5; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } }
-    .decimal6-ol { counter-reset: deci6; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } }
-    .decimal7-ol { counter-reset: deci7; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } }
-
-    .upper-alpha-ol { counter-reset: ualph;  p { display: inline}; font-size: smaller; }
-    .lower-roman-ol { counter-reset: lroman; p { display: inline}; font-size: smaller; }
-    .lower-alpha-ol { counter-reset: lalpha; p { display: inline}; font-size: smaller; }
-
-    .decimal1:before { counter-increment: deci1; display: inline-block; content: counter(deci1) ". ";  }
-    .decimal2:before { counter-increment: deci2; display: inline-block; content: counter(deci1) "."counter(deci2) ". ";  }
-    .decimal3:before { counter-increment: deci3; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
-    .decimal4:before { counter-increment: deci4; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
-    .decimal5:before { counter-increment: deci5; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
-    .decimal6:before { counter-increment: deci5; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
-    .decimal7:before { counter-increment: deci5; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
+    p { 
+        display: inline;
+        font-family: inherit;
+     }
+
+    .decimal1-ol { counter-reset: deci1; margin-left: 0; }
+    .decimal2-ol { counter-reset: deci2; font-size: smaller; padding-left: 1em;}
+    .decimal3-ol { counter-reset: deci3; font-size: smaller; padding-left: 2em;}
+    .decimal4-ol { counter-reset: deci4; font-size: smaller; padding-left: 3em;}
+    .decimal5-ol { counter-reset: deci5; font-size: smaller; }
+    .decimal6-ol { counter-reset: deci6; font-size: smaller; }
+    .decimal7-ol { counter-reset: deci7; font-size: smaller; }
+
+    .multi1-ol { counter-reset: multi1; margin-left: 0;     padding-left: 1.2em }
+    .multi2-ol { counter-reset: multi2; font-size: smaller; padding-left: 1.4em;}
+    .multi3-ol { counter-reset: multi3; font-size: smaller; padding-left: 2em;}
+    .multi4-ol { counter-reset: multi4; font-size: smaller; padding-left: 3.4em;}
+
+    .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em;   width:  1em;   content: counter(deci1) ". ";  }
+    .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em;  content: counter(deci1) "."counter(deci2) ". ";  }
+    .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
+    .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
+    .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em;   width:  5em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
+    .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
+    .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
     
-    .upper-alpha:before { counter-increment: ualph;  display: inline-block; content: counter(deci1) "."counter(ualph, upper-alpha) ". "; }
-    .lower-roman:before { counter-increment: lroman; display: inline-block; content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) ". "; }
-    .lower-alpha:before { counter-increment: lalpha; display: inline-block; content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) "."counter(lalpha, lower-alpha) ". "; }
+    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em;    width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
+    .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
+    .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
+    .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left:   -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 3ed010e8f..3197ce79f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -962,37 +962,42 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         //     }
         // }
 
-        this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey);
+        this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false)
         if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500);
     }
 
     // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
-    hitBulletTargets(x: number, y: number, offsetX: number, select: boolean, highlightOnly = false) {
+    hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) {
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
-        if (this.props.isSelected(true) && offsetX < 40) {
-            const pos = this._editorView!.posAtCoords({ left: x, top: y });
-            if (pos && pos.pos > 0) {
-                const node = this._editorView!.state.doc.nodeAt(pos.pos);
-                const node2 = node?.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
-                if ((node === this._nodeClicked || highlightOnly) && (node2?.type === schema.nodes.ordered_list || node2?.type === schema.nodes.list_item)) {
-                    const hit = this._editorView!.domAtPos(pos.pos).node as any;   // let beforeEle = document.querySelector("." + hit.className) as Element;
-                    const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
-                    const beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined;
-                    if (beforeWidth && offsetX < beforeWidth * .9) {
-                        const ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
-                        if (ol?.type === schema.nodes.ordered_list && select) {
-                            if (!highlightOnly) {
-                                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
-                            }
-                            addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "lightgray" });
-                        } else {
-                            if (highlightOnly) {
-                                addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "lightgray" });
-                            } else {
-                                this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
-                            }
-                        }
+        const pos = this._editorView!.posAtCoords({ left: x, top: y });
+        if (pos && this.props.isSelected(true)) {
+            // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
+            //const node = this._editorView!.state.doc.nodeAt(pos.pos);
+            let $pos = this._editorView!.state.doc.resolve(pos.pos);
+            let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
+            if ($pos.node().type === schema.nodes.ordered_list) {
+                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) {
+                        list_node = node;
+                        break;
+                    }
+                }
+            }
+            if (list_node && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
+                if (select) {
+                    let $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+                    if (!highlightOnly) {
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
+                    }
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                } else if (Math.abs(pos.pos - pos.inside) < 2) {
+                    if (!highlightOnly) {
+                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside)));
                     }
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
                 }
             }
         }
@@ -1113,7 +1118,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 onKeyDown={this.onKeyPress}
                 onFocus={this.onFocused}
                 onClick={this.onClick}
-                onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey, true)}
+                onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
                 onBlur={this.onBlur}
                 onPointerUp={this.onPointerUp}
                 onPointerDown={this.onPointerDown}
-- 
cgit v1.2.3-70-g09d2


From 27df91d19d7720bbd143f3f447807bfae583d5df Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 9 Dec 2019 16:39:32 -0500
Subject: added preliminary support for paragraph indenting and hanging
 indents.

---
 src/client/util/ParagraphNodeSpec.ts |  2 +-
 src/client/util/RichTextRules.ts     | 28 ++++++++++++++++++++++++++++
 src/client/util/RichTextSchema.tsx   | 13 +------------
 3 files changed, 30 insertions(+), 13 deletions(-)

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

diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts
index 3a993e1ff..593aec498 100644
--- a/src/client/util/ParagraphNodeSpec.ts
+++ b/src/client/util/ParagraphNodeSpec.ts
@@ -115,7 +115,7 @@ function toDOM(node: Node): DOMOutputSpec {
         attrs.id = id;
     }
 
-    return ['p', attrs, 0];
+    return ['p', { ...attrs, ...{ style: `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};` } }, 0];
 }
 
 export const toParagraphDOM = toDOM;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index c9414fb0b..4e60976d5 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -114,6 +114,34 @@ export const inpRules = {
                 if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
                 return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
+        new InputRule(
+            new RegExp(/d$/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from) return null;
+                const pos = (state.doc.resolve(start) as any);
+                let depth = pos.path.length / 3 - 1;
+                for (; depth >= 0; depth--) {
+                    if (pos.node(depth).type === schema.nodes.paragraph) {
+                        const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, indent: 25 });
+                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                    }
+                }
+                return null;
+            }),
+        new InputRule(
+            new RegExp(/h$/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from) return null;
+                const pos = (state.doc.resolve(start) as any);
+                let depth = pos.path.length / 3 - 1;
+                for (; depth >= 0; depth--) {
+                    if (pos.node(depth).type === schema.nodes.paragraph) {
+                        const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, indent: -25 });
+                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                    }
+                }
+                return null;
+            }),
         new InputRule(
             new RegExp(/!$/),
             (state, match, start, end) => {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 9e95314a9..02ede3419 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -18,9 +18,8 @@ import { Transform } from "./Transform";
 import React = require("react");
 import { BoolCast, NumCast } from "../../new_fields/Types";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { any } from "bluebird";
 
-const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
+const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
 
 // :: Object
@@ -31,7 +30,6 @@ export const nodes: { [index: string]: NodeSpec } = {
         content: "block+"
     },
 
-
     footnote: {
         group: "inline",
         content: "inline*",
@@ -46,15 +44,6 @@ export const nodes: { [index: string]: NodeSpec } = {
         parseDOM: [{ tag: "footnote" }]
     },
 
-    // // :: NodeSpec A plain paragraph textblock. Represented in the DOM
-    // // as a `<p>` element.
-    // paragraph: {
-    //     content: "inline*",
-    //     group: "block",
-    //     parseDOM: [{ tag: "p" }],
-    //     toDOM() { return pDOM; }
-    // },
-
     paragraph: ParagraphNodeSpec,
 
     // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
-- 
cgit v1.2.3-70-g09d2


From abbbde85ca65574709caf37b2c3f313059d3bd37 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 9 Dec 2019 17:49:33 -0500
Subject: got rid of unused code in richtextschema

---
 src/client/util/RichTextSchema.tsx | 12 ------------
 1 file changed, 12 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 8c74c3f68..462ee85e7 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -552,18 +552,6 @@ export const marks: { [index: string]: MarkSpec } = {
         }]
     },
 
-    pFontColor: {
-        attrs: {
-            color: { default: "yellow" }
-        },
-        parseDOM: [{ style: 'background: #d9dbdd' }],
-        toDOM: (node) => {
-            return ['span', {
-                style: `color: ${node.attrs.color}`
-            }];
-        }
-    },
-
     /** FONT SIZES */
     pFontSize: {
         attrs: {
-- 
cgit v1.2.3-70-g09d2


From 1c7c38868978b0f68dbad8a9e07d3d30967c23e1 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 10 Dec 2019 11:22:58 -0500
Subject: fixes to text box inlines for selection.

---
 src/client/util/RichTextSchema.tsx                 | 14 ++------------
 src/client/views/nodes/FormattedTextBox.tsx        |  7 ++++++-
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 +
 3 files changed, 9 insertions(+), 13 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 7cb8448ca..4a75d6e49 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -177,18 +177,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             docid: { default: "" },
         },
         group: "inline",
-        draggable: true,
-        // parseDOM: [{
-        //     tag: "img[src]", getAttrs(dom: any) {
-        //         return {
-        //             src: dom.getAttribute("src"),
-        //             title: dom.getAttribute("title"),
-        //             alt: dom.getAttribute("alt"),
-        //             width: Math.min(100, Number(dom.getAttribute("width"))),
-        //         };
-        //     }
-        // }],
-        // TODO if we don't define toDom, dragging the image crashes. Why?
+        draggable: false,
         toDOM(node) {
             const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
             return ["div", { ...node.attrs, ...attrs }];
@@ -704,6 +693,7 @@ export class DashDocView {
         this._dashSpan = document.createElement("div");
         this._outer = document.createElement("span");
         this._outer.style.position = "relative";
+        this._outer.style.textIndent = "0";
         this._outer.style.width = node.attrs.width;
         this._outer.style.height = node.attrs.height;
         this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6d3c1434a..42723b44b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -828,7 +828,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
-            if (startup) {
+            if (startup && this._editorView) {
                 Doc.GetProto(doc).documentText = undefined;
                 this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
             }
@@ -869,7 +869,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this._buttonBarReactionDisposer && this._buttonBarReactionDisposer();
         this._editorView && this._editorView.destroy();
     }
+
+    static _downEvent: any;
     onPointerDown = (e: React.PointerEvent): void => {
+        FormattedTextBox._downEvent = true;
         FormattedTextBoxComment.textBox = this;
         const pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
         pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
@@ -885,6 +888,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
 
     onPointerUp = (e: React.PointerEvent): void => {
+        if (!FormattedTextBox._downEvent) return;
+        FormattedTextBox._downEvent = false;
         if (!(e.nativeEvent as any).formattedHandled) {
             FormattedTextBoxComment.textBox = this;
             FormattedTextBoxComment.update(this._editorView!);
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 9f1dd4aec..ed98d9db6 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -94,6 +94,7 @@ export class FormattedTextBoxComment {
                     FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
                     FormattedTextBoxComment.opened, keep);
                 e.stopPropagation();
+                e.preventDefault();
             };
             root && root.appendChild(FormattedTextBoxComment.tooltip);
         }
-- 
cgit v1.2.3-70-g09d2


From 810e5a128d9832f61c3841da989c049ba5d76676 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 10 Dec 2019 11:59:23 -0500
Subject: fix for commands in nested notes.

---
 src/client/util/RichTextSchema.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 4a75d6e49..f41846038 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -762,7 +762,7 @@ export class DashDocView {
             }
         });
         const self = this;
-        this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
+        this._dashSpan.onkeydown = function (e: any) { };
         this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
         this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
-- 
cgit v1.2.3-70-g09d2


From 4ab742c54d600fb62b02268f48e711258558924b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 10 Dec 2019 18:47:27 -0500
Subject: fixes to color marks

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

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 7cb8448ca..4369be1ee 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -313,7 +313,7 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             color: { default: "#000" }
         },
-        inclusive: false,
+        inclusive: true,
         parseDOM: [{
             tag: "span", getAttrs(dom: any) {
                 return { color: dom.getAttribute("color") };
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 01d566831..f29dbf2e4 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -839,9 +839,11 @@ export class TooltipTextMenu {
     }
 
     public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-
         const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
+        if (state.selection.empty) {
+            dispatch(state.tr.addStoredMark(colorMark));
+            return false;
+        }
         dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
     }
 
-- 
cgit v1.2.3-70-g09d2


From 7a5c3d931381cc698d01b710a2576117449cf302 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 10 Dec 2019 22:39:43 -0500
Subject: cleaned up userMarks a bit (got rid of 'opened' attr).  added @
 markup to create collection from text selection

---
 src/client/util/DocumentManager.ts                 |  2 ++
 src/client/util/RichTextRules.ts                   | 20 +++++++++++++++++++-
 src/client/util/RichTextSchema.tsx                 | 10 ++--------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 13 ++++++++-----
 src/client/views/nodes/FormattedTextBoxComment.tsx | 19 +++++++++----------
 6 files changed, 41 insertions(+), 25 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d491cd1b1..07247b7e9 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -194,6 +194,8 @@ export class DocumentManager {
             const target = linkFollowDocs[reverse ? 1 : 0];
             target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
             DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]);
+        } else if (link) {
+            DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined);
         }
     }
 
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 94bfc5ef2..c69112b3b 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -6,8 +6,10 @@ 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 } from "../documents/Documents";
+import { Docs, DocUtils } from "../documents/Documents";
 import { Id } from "../../new_fields/FieldSymbols";
+import { DocServer } from "../DocServer";
+import { returnFalse, Utils } from "../../Utils";
 
 export const inpRules = {
     rules: [
@@ -172,6 +174,22 @@ export const inpRules = {
                 if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
                 return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
+        new InputRule(
+            new RegExp(/@$/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from) return null;
+
+                const value = state.doc.textBetween(start, end);
+                if (value) {
+                    DocServer.GetRefField(value).then(docx => {
+                        let doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+                        DocUtils.Publish(doc, value, returnFalse, returnFalse);
+                    });
+                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
+                    return state.tr.addMark(start, end, link);
+                }
+                return state.tr;
+            }),
         new InputRule(
             new RegExp(/^\^\^\s$/),
             (state, match, start, end) => {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index fac8f4027..c6314bc30 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -462,7 +462,6 @@ export const marks: { [index: string]: MarkSpec } = {
     user_mark: {
         attrs: {
             userid: { default: "" },
-            opened: { default: true },
             modified: { default: "when?" }, // 5 second intervals since 1970
         },
         group: "inline",
@@ -472,16 +471,13 @@ export const marks: { [index: string]: MarkSpec } = {
             const hr = Math.round(min / 60);
             const day = Math.round(hr / 60 / 24);
             const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
-            return node.attrs.opened ?
-                ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] :
-                ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]];
+            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
         }
     },
     // the id of the user who entered the text
     user_tag: {
         attrs: {
             userid: { default: "" },
-            opened: { default: true },
             modified: { default: "when?" }, // 5 second intervals since 1970
             tag: { default: "" }
         },
@@ -489,9 +485,7 @@ export const marks: { [index: string]: MarkSpec } = {
         inclusive: false,
         toDOM(node: any) {
             const uid = node.attrs.userid.replace(".", "").replace("@", "");
-            return node.attrs.opened ?
-                ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0] :
-                ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, ['span', 0]];
+            return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
         }
     },
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 727d631c4..24cc0b5ea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -644,7 +644,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     contents={this.Document[showTitle]}
                     display={"block"} height={72} fontSize={12}
                     GetValue={() => StrCast(this.Document[showTitle])}
-                    SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true}
+                    SetValue={undoBatch((value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true)}
                 />
             </div>);
         return <>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 70fa4974d..9318142e2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -184,7 +184,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     this.linkOnDeselect.set(key, value);
 
                     const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-                    const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value });
+                    const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
                     const mval = this._editorView.state.schema.marks.metadataVal.create();
                     const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
                     tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -238,9 +238,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
         }
     }
-    setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
+    adoptAnnotation = (start: number, end: number, mark: Mark) => {
         const view = this._editorView!;
-        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
         view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
     }
     protected createDropTarget = (ele: HTMLDivElement) => {
@@ -873,6 +873,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     static _downEvent: any;
     onPointerDown = (e: React.PointerEvent): void => {
+        this.doLinkOnDeselect();
         FormattedTextBox._downEvent = true;
         FormattedTextBoxComment.textBox = this;
         if (this.props.onClick && e.button === 0) {
@@ -1053,9 +1054,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             this._undoTyping.end();
             this._undoTyping = undefined;
         }
-        this.doLinkOnDeselect(); 6
+        this.doLinkOnDeselect();
     }
 
+    _lastTimedMark: Mark | undefined = undefined;
     onKeyPress = (e: React.KeyboardEvent) => {
         if (e.altKey) {
             e.preventDefault();
@@ -1078,7 +1080,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-        const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark! : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+        this._lastTimedMark = mark;
         this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
 
         if (!this._undoTyping) {
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 409229c1a..1755cb99c 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -57,7 +57,6 @@ export class FormattedTextBoxComment {
     static start: number;
     static end: number;
     static mark: Mark;
-    static opened: boolean;
     static textBox: FormattedTextBox | undefined;
     static linkDoc: Doc | undefined;
     constructor(view: any) {
@@ -89,10 +88,8 @@ export class FormattedTextBoxComment {
                 } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
                     textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight");
                 }
-                FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
-                textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation(
-                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
-                    FormattedTextBoxComment.opened, keep);
+                keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
+                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
                 e.stopPropagation();
                 e.preventDefault();
             };
@@ -104,12 +101,11 @@ export class FormattedTextBoxComment {
         FormattedTextBoxComment.textBox = undefined;
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
     }
-    public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) {
+    public static SetState(textBox: any, start: number, end: number, mark: Mark) {
         FormattedTextBoxComment.textBox = textBox;
         FormattedTextBoxComment.start = start;
         FormattedTextBoxComment.end = end;
         FormattedTextBoxComment.mark = mark;
-        FormattedTextBoxComment.opened = opened;
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
     }
 
@@ -143,7 +139,7 @@ export class FormattedTextBoxComment {
             state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
             const mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
             }
             if (mark && child && ((nbef && naft) || !noselection)) {
                 FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
@@ -160,19 +156,22 @@ export class FormattedTextBoxComment {
             const mark = child && findLinkMark(child.marks);
             if (mark && child && nbef && naft) {
                 FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
+                (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
                 if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
                     wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
                 } else {
                     FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
                     FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
                 }
-                (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
                 if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    FormattedTextBoxComment.tooltipText.textContent = "target not found...";
+                    (FormattedTextBoxComment.tooltipText as any).href = "";
                     const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
                         if (linkDoc instanceof Doc) {
+                            (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
                             FormattedTextBoxComment.linkDoc = linkDoc;
-                            const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc));
+                            const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
                             try {
                                 ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
                             } catch (e) { }
-- 
cgit v1.2.3-70-g09d2


From 921554d7d585a1138c7cf8d02d1e46f47c7cafe7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 10 Dec 2019 23:27:55 -0500
Subject: added indenting for ordered lists

---
 src/client/util/RichTextRules.ts   | 4 ++++
 src/client/util/RichTextSchema.tsx | 5 +++--
 2 files changed, 7 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index c69112b3b..ba2bc785d 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -149,6 +149,10 @@ export const inpRules = {
             (state, match, start, end) => {
                 if (state.selection.to === state.selection.from) return null;
                 const pos = (state.doc.resolve(start) as any);
+                if (state.selection instanceof NodeSelection && (state.selection as NodeSelection).node.type === schema.nodes.ordered_list) {
+                    let node = (state.selection as NodeSelection).node;
+                    return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: 30 });
+                }
                 let depth = pos.path.length / 3 - 1;
                 for (; depth >= 0; depth--) {
                     if (pos.node(depth).type === schema.nodes.paragraph) {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c6314bc30..f9251fb7e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -229,14 +229,15 @@ export const nodes: { [index: string]: NodeSpec } = {
             setFontSize: { default: undefined },
             setFontFamily: { default: "inherit" },
             inheritedFontSize: { default: undefined },
-            visibility: { default: true }
+            visibility: { default: true },
+            indent: { default: undefined }
         },
         toDOM(node: Node<any>) {
             if (node.attrs.mapStyle === "bullet") return ['ul', 0];
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
             const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
             const ffam = node.attrs.setFontFamily;
-            return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] :
+            return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; margin-left: ${node.attrs.indent}` }, 0] :
                 ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }];
         }
     },
-- 
cgit v1.2.3-70-g09d2


From 9d8845fb64c08729b446f12206aa5ed215228f4e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 11 Dec 2019 17:00:58 -0500
Subject: cleaned up toottipmenu and richtextschema a bit.  fixed some problems
 with text styles.  fixed warnings.

---
 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log    | 274 ----------------
 logs/server_pids.txt                            |   1 -
 src/client/util/ProsemirrorExampleTransfer.ts   |   2 +-
 src/client/util/RichTextRules.ts                | 162 +++++-----
 src/client/util/RichTextSchema.tsx              |  68 ++--
 src/client/util/TooltipTextMenu.tsx             | 414 +++++-------------------
 src/client/views/DocumentDecorations.tsx        |   4 +-
 src/client/views/EditableView.tsx               |   2 +-
 src/client/views/MainView.tsx                   |   2 +-
 src/client/views/collections/CollectionView.tsx |   6 +-
 src/client/views/nodes/DocumentView.tsx         |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx     |  32 +-
 12 files changed, 228 insertions(+), 741 deletions(-)
 delete mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log
 delete mode 100644 logs/server_pids.txt

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

diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log
deleted file mode 100644
index 37e232d48..000000000
--- a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log	
+++ /dev/null
@@ -1,274 +0,0 @@
-
-> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web
-> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts
-
-Using ts-node version 7.0.1, typescript version 3.7.2
-objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined.
-[34m
-starting execution of preliminary functions...[0m
-(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
-[34mcompleted preliminary functions
-.[0m
-Starting type checking and linting service...
-Using 1 worker with 2048MB memory limit
-
-running server in [33mdevelopment[39m mode
-[33m[39m
-[33mregistering server routes...[39m
-[32mall server routes have been successfully registered:[39m
-[36m/[39m
-[36m/activity[39m
-[36m/buxton[39m
-[36m/delete[39m
-[36m/deleteAll[39m
-[36m/deleteWithAux[39m
-[36m/deleteWithGoogleCredentials[39m
-[36m/doc/:docId[39m
-[36m/downloadId/:docId[39m
-[36m/environment/:key[39m
-[36m/getCurrentUser[39m
-[36m/getUserDocumentId[39m
-[36m/getUsers[39m
-[36m/googleDocs/:sector/:action[39m
-[36m/googlePhotosMediaDownload[39m
-[36m/googlePhotosMediaUpload[39m
-[36m/home[39m
-[36m/imageHierarchyExport/:docId[39m
-[36m/inspectImage[39m
-[36m/persist[39m
-[36m/pull[39m
-[36m/readGoogleAccessToken[39m
-[36m/search[39m
-[36m/serializeDoc/:docId[39m
-[36m/serverHeartbeat[39m
-[36m/shutdown[39m
-[36m/solr/:action[39m
-[36m/textsearch[39m
-[36m/thumbnail/:filename[39m
-[36m/upload[39m
-[36m/uploadDoc[39m
-[36m/uploadURI[39m
-[36m/version[39m
-[36m/writeGoogleAccessToken[39m
-
-websocket listening on port [33m4321[39m
-server listening on port [33m1050[39m
-
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-Type checking and linting in progress...
-webpack built 8f6b743d91fd3862683b in 47419ms
-[33m⚠[39m [90m｢wdm｣[39m: Hash: [1m8f6b743d91fd3862683b[39m[22m
-Version: webpack [1m4.36.1[39m[22m
-Time: [1m47419[39m[22mms
-Built at: 12/11/2019 [1m3:45:31 AM[39m[22m
-                               [1mAsset[39m[22m       [1mSize[39m[22m               [1mChunks[39m[22m  [1m[39m[22m           [1m[39m[22m[1mChunk Names[39m[22m
-[1m[32m275711e56bd1bc79fdff544a3d7dbfae.png[39m[22m  289 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32m32f1593298e6e7bee5673bf647328d72.png[39m[22m  429 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32m718c914a99a2136c01c84e01f63e505a.png[39m[22m  829 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32m906f1a1816c2a03b5c7612f6aa2ceece.png[39m[22m  281 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                [1m[32massets/downarrow.png[39m[22m   3.28 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                     [1m[32massets/env.json[39m[22m  360 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-            [1m[32massets/google_photos.png[39m[22m    114 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-              [1m[32massets/google_tags.png[39m[22m    7.9 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                  [1m[32massets/loading.gif[39m[22m    112 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                [1m[32massets/pdf.worker.js[39m[22m   1.55 MiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                           [1m[32mbundle.js[39m[22m   20.8 MiB               [1mbundle[39m[22m  [1m[32m[emitted][39m[22m  bundle
-                       [1m[32mbundle.js.map[39m[22m   23.3 MiB               [1mbundle[39m[22m  [1m[32m[emitted][39m[22m  bundle
-                     [1m[32mdebug/repl.html[39m[22m  348 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                     [1m[32mdebug/test.html[39m[22m  245 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                   [1m[32mdebug/viewer.html[39m[22m  357 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32me7a34b49f3c49ca0c25c76b30cd09e12.png[39m[22m  445 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                      [1m[32mimageUpload.js[39m[22m   20.8 MiB          [1mimageUpload[39m[22m  [1m[32m[emitted][39m[22m  imageUpload
-                  [1m[32mimageUpload.js.map[39m[22m   23.3 MiB          [1mimageUpload[39m[22m  [1m[32m[emitted][39m[22m  imageUpload
-                          [1m[32mindex.html[39m[22m  593 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                      [1m[32minkControls.js[39m[22m    116 KiB          [1minkControls[39m[22m  [1m[32m[emitted][39m[22m  inkControls
-                  [1m[32minkControls.js.map[39m[22m    122 KiB          [1minkControls[39m[22m  [1m[32m[emitted][39m[22m  inkControls
-                   [1m[32mmobile/image.html[39m[22m  333 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                     [1m[32mmobile/ink.html[39m[22m  252 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                             [1m[32mrepl.js[39m[22m   9.31 MiB                 [1mrepl[39m[22m  [1m[32m[emitted][39m[22m  repl
-                         [1m[32mrepl.js.map[39m[22m   10.5 MiB                 [1mrepl[39m[22m  [1m[32m[emitted][39m[22m  repl
-                             [1m[32mtest.js[39m[22m    1.2 MiB                 [1mtest[39m[22m  [1m[32m[emitted][39m[22m  test
-                         [1m[32mtest.js.map[39m[22m   1.42 MiB                 [1mtest[39m[22m  [1m[32m[emitted][39m[22m  test
-                            [1m[32mtest.pdf[39m[22m   53.6 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-              [1m[32mvendors~pdfjsWorker.js[39m[22m   1.55 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[emitted][39m[22m  vendors~pdfjsWorker
-          [1m[32mvendors~pdfjsWorker.js.map[39m[22m   1.87 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[emitted][39m[22m  vendors~pdfjsWorker
-                           [1m[32mviewer.js[39m[22m   9.47 MiB               [1mviewer[39m[22m  [1m[32m[emitted][39m[22m  viewer
-                       [1m[32mviewer.js.map[39m[22m   10.7 MiB               [1mviewer[39m[22m  [1m[32m[emitted][39m[22m  viewer
-Entrypoint [1mbundle[39m[22m = [1m[32mbundle.js[39m[22m [1m[32mbundle.js.map[39m[22m
-Entrypoint [1mviewer[39m[22m = [1m[32mviewer.js[39m[22m [1m[32mviewer.js.map[39m[22m
-Entrypoint [1mrepl[39m[22m = [1m[32mrepl.js[39m[22m [1m[32mrepl.js.map[39m[22m
-Entrypoint [1mtest[39m[22m = [1m[32mtest.js[39m[22m [1m[32mtest.js.map[39m[22m
-Entrypoint [1minkControls[39m[22m = [1m[32minkControls.js[39m[22m [1m[32minkControls.js.map[39m[22m
-Entrypoint [1mimageUpload[39m[22m = [1m[32mimageUpload.js[39m[22m [1m[32mimageUpload.js.map[39m[22m
-[19] [1mmulti ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mbundle[39m[22m}[1m[32m [built][39m[22m
-[20] [1mmulti ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mviewer[39m[22m}[1m[32m [built][39m[22m
-[21] [1mmulti ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mrepl[39m[22m}[1m[32m [built][39m[22m
-[22] [1mmulti ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mtest[39m[22m}[1m[32m [built][39m[22m
-[23] [1mmulti ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33minkControls[39m[22m}[1m[32m [built][39m[22m
-[24] [1mmulti ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./node_modules/mobx-react/index.module.js[39m[22m] 48.8 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./node_modules/mobx/lib/mobx.module.js[39m[22m] 175 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [./node_modules/webpack-hot-middleware/client.js?reload=true] [1m(webpack)-hot-middleware/client.js?reload=true[39m[22m 7.68 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mtest[39m[22m} {[1m[33minkControls[39m[22m} {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/client/views/Main.tsx[39m[22m] 4.03 KiB {[1m[33mbundle[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/debug/Repl.tsx[39m[22m] 6.87 KiB {[1m[33mrepl[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/debug/Test.tsx[39m[22m] 1.02 KiB {[1m[33mtest[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/debug/Viewer.tsx[39m[22m] 12.1 KiB {[1m[33mviewer[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/mobile/ImageUpload.tsx[39m[22m] 9.97 KiB {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/mobile/InkControls.tsx[39m[22m] 14 bytes {[1m[33minkControls[39m[22m}[1m[32m [built][39m[22m
-    + 1494 hidden modules
-
-[1m[33mWARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60
-Critical dependency: the request of a dependency is an expression
- @ ./src/client/util/Scripting.ts
- @ ./src/debug/Viewer.tsx
- @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m
-[34mℹ[39m [90m｢wdm｣[39m: Compiled with warnings.
-[34mℹ[39m [90m｢wdm｣[39m: Compiling...
-webpack building...
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-Type checking and linting in progress...
-webpack built 8f6b743d91fd3862683b in 615ms
-[33m⚠[39m [90m｢wdm｣[39m: Hash: [1m8f6b743d91fd3862683b[39m[22m
-Version: webpack [1m4.36.1[39m[22m
-Time: [1m615[39m[22mms
-Built at: 12/11/2019 [1m3:45:33 AM[39m[22m
-                               [1mAsset[39m[22m       [1mSize[39m[22m               [1mChunks[39m[22m  [1m[39m[22m[1m[39m[22m[1mChunk Names[39m[22m
-[1m[32m275711e56bd1bc79fdff544a3d7dbfae.png[39m[22m  289 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-[1m[32m32f1593298e6e7bee5673bf647328d72.png[39m[22m  429 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-[1m[32m718c914a99a2136c01c84e01f63e505a.png[39m[22m  829 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-[1m[32m906f1a1816c2a03b5c7612f6aa2ceece.png[39m[22m  281 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-                           [1m[32mbundle.js[39m[22m   20.8 MiB               [1mbundle[39m[22m  [1m[32m[39m[22mbundle
-                       [1m[32mbundle.js.map[39m[22m   23.3 MiB               [1mbundle[39m[22m  [1m[32m[39m[22mbundle
-[1m[32me7a34b49f3c49ca0c25c76b30cd09e12.png[39m[22m  445 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-                      [1m[32mimageUpload.js[39m[22m   20.8 MiB          [1mimageUpload[39m[22m  [1m[32m[39m[22mimageUpload
-                  [1m[32mimageUpload.js.map[39m[22m   23.3 MiB          [1mimageUpload[39m[22m  [1m[32m[39m[22mimageUpload
-                      [1m[32minkControls.js[39m[22m    116 KiB          [1minkControls[39m[22m  [1m[32m[39m[22minkControls
-                  [1m[32minkControls.js.map[39m[22m    122 KiB          [1minkControls[39m[22m  [1m[32m[39m[22minkControls
-                             [1m[32mrepl.js[39m[22m   9.31 MiB                 [1mrepl[39m[22m  [1m[32m[39m[22mrepl
-                         [1m[32mrepl.js.map[39m[22m   10.5 MiB                 [1mrepl[39m[22m  [1m[32m[39m[22mrepl
-                             [1m[32mtest.js[39m[22m    1.2 MiB                 [1mtest[39m[22m  [1m[32m[39m[22mtest
-                         [1m[32mtest.js.map[39m[22m   1.42 MiB                 [1mtest[39m[22m  [1m[32m[39m[22mtest
-              [1m[32mvendors~pdfjsWorker.js[39m[22m   1.55 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[39m[22mvendors~pdfjsWorker
-          [1m[32mvendors~pdfjsWorker.js.map[39m[22m   1.87 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[39m[22mvendors~pdfjsWorker
-                           [1m[32mviewer.js[39m[22m   9.47 MiB               [1mviewer[39m[22m  [1m[32m[39m[22mviewer
-                       [1m[32mviewer.js.map[39m[22m   10.7 MiB               [1mviewer[39m[22m  [1m[32m[39m[22mviewer
-Entrypoint [1mbundle[39m[22m = [1m[32mbundle.js[39m[22m [1m[32mbundle.js.map[39m[22m
-Entrypoint [1mviewer[39m[22m = [1m[32mviewer.js[39m[22m [1m[32mviewer.js.map[39m[22m
-Entrypoint [1mrepl[39m[22m = [1m[32mrepl.js[39m[22m [1m[32mrepl.js.map[39m[22m
-Entrypoint [1mtest[39m[22m = [1m[32mtest.js[39m[22m [1m[32mtest.js.map[39m[22m
-Entrypoint [1minkControls[39m[22m = [1m[32minkControls.js[39m[22m [1m[32minkControls.js.map[39m[22m
-Entrypoint [1mimageUpload[39m[22m = [1m[32mimageUpload.js[39m[22m [1m[32mimageUpload.js.map[39m[22m
-[19] [1mmulti ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mbundle[39m[22m}
-[20] [1mmulti ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mviewer[39m[22m}
-[21] [1mmulti ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mrepl[39m[22m}
-[22] [1mmulti ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mtest[39m[22m}
-[23] [1mmulti ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33minkControls[39m[22m}
-[24] [1mmulti ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mimageUpload[39m[22m}
- [[1m./node_modules/mobx-react/index.module.js[39m[22m] 48.8 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}
- [[1m./node_modules/mobx/lib/mobx.module.js[39m[22m] 175 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}
- [./node_modules/webpack-hot-middleware/client.js?reload=true] [1m(webpack)-hot-middleware/client.js?reload=true[39m[22m 7.68 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mtest[39m[22m} {[1m[33minkControls[39m[22m} {[1m[33mimageUpload[39m[22m}
- [[1m./src/client/views/Main.tsx[39m[22m] 4.03 KiB {[1m[33mbundle[39m[22m}
- [[1m./src/debug/Repl.tsx[39m[22m] 6.87 KiB {[1m[33mrepl[39m[22m}
- [[1m./src/debug/Test.tsx[39m[22m] 1.02 KiB {[1m[33mtest[39m[22m}
- [[1m./src/debug/Viewer.tsx[39m[22m] 12.1 KiB {[1m[33mviewer[39m[22m}
- [[1m./src/mobile/ImageUpload.tsx[39m[22m] 9.97 KiB {[1m[33mimageUpload[39m[22m}
- [[1m./src/mobile/InkControls.tsx[39m[22m] 14 bytes {[1m[33minkControls[39m[22m}
-    + 1494 hidden modules
-
-[1m[33mWARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60
-Critical dependency: the request of a dependency is an expression
- @ ./src/client/util/Scripting.ts
- @ ./src/debug/Viewer.tsx
- @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m
-[34mℹ[39m [90m｢wdm｣[39m: Compiled with warnings.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21):
-prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25):
-prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21):
-prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25):
-prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36):
-triple-equals: == should be ===
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21):
-prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21):
-prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66):
-no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33):
-no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29):
-prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21):
-prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21):
-prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13):
-prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13):
-prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13):
-prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21):
-prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13):
-prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25):
-prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13):
-prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13):
-prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13):
-prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17):
-prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21):
-prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17):
-prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25):
-prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17):
-prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'.
-No type errors found
-Version: typescript 3.7.2, tslint 5.18.0
-Time: 5473ms
diff --git a/logs/server_pids.txt b/logs/server_pids.txt
deleted file mode 100644
index 2aa143f24..000000000
--- a/logs/server_pids.txt
+++ /dev/null
@@ -1 +0,0 @@
-9675 created at Wed, 11 Dec 2019 08:44:28 GMT
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index f1fa6f11d..3324d8abe 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -105,7 +105,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         return true;
     });
 
-    bind("Mod-s", TooltipTextMenu.insertStar);
+    bind("Mod-s", TooltipTextMenu.insertSummarizer);
 
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         const ref = state.selection;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 22b2a8204..364c85165 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -62,8 +62,9 @@ export const inpRules = {
             }
         ),
 
+        // set the font size using #<font-size> 
         new InputRule(
-            new RegExp(/^#([0-9]+)\s$/),
+            new RegExp(/^%([0-9]+)\s$/),
             (state, match, start, end) => {
                 const size = Number(match[1]);
                 const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
@@ -74,131 +75,123 @@ export const inpRules = {
                 }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
+
+        // make current selection a hyperlink portal (assumes % was used to initiate an EnteringStyle mode)
+        new InputRule(
+            new RegExp(/@$/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null;
+
+                const value = state.doc.textBetween(start, end);
+                if (value) {
+                    DocServer.GetRefField(value).then(docx => {
+                        const doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+                        DocUtils.Publish(doc, value, returnFalse, returnFalse);
+                    });
+                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
+                    return state.tr.addMark(start, end, link);
+                }
+                return state.tr;
+            }),
+
+        // activate a style by name using prefix '%'
         new InputRule(
             new RegExp(/%[a-z]+$/),
             (state, match, start, end) => {
                 const color = match[0].substring(1, match[0].length);
-                let marks = TooltipTextMenuManager.Instance._brushMap.get(color);
+                const marks = TooltipTextMenuManager.Instance._brushMap.get(color);
                 if (marks) {
-                    let tr = state.tr.deleteRange(start, end);
+                    const tr = state.tr.deleteRange(start, end);
                     return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
                 }
-                let isValidColor = (strColor: string) => {
-                    var s = new Option().style;
+                const isValidColor = (strColor: string) => {
+                    const s = new Option().style;
                     s.color = strColor;
-                    return s.color == strColor.toLowerCase(); // 'false' if color wasn't assigned
-                }
+                    return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+                };
                 if (isValidColor(color)) {
                     return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
                 }
                 return null;
             }),
+        // stop using active style
         new InputRule(
             new RegExp(/%%$/),
             (state, match, start, end) => {
-                let tr = state.tr.deleteRange(start, end);
-                let marks = state.tr.selection.$anchor.nodeBefore?.marks;
+                const tr = state.tr.deleteRange(start, end);
+                const marks = state.tr.selection.$anchor.nodeBefore?.marks;
                 return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
             }),
+
+        // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/t$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
-                const node = (state.doc.resolve(start) as any).nodeAfter;
-                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
-            }),
-        new InputRule(
-            new RegExp(/i$/),
+            new RegExp(/[ti!x]$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
+                if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null;
+                const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
+
+        // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/d$/),
+            new RegExp(/(%d|d)$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
                 const pos = (state.doc.resolve(start) as any);
-                let depth = pos.path.length / 3 - 1;
-                for (; depth >= 0; depth--) {
-                    let node = pos.node(depth);
+                for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                    const node = pos.node(depth);
                     if (node.type === schema.nodes.paragraph) {
                         const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
-                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                        const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                        return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
                     }
                 }
                 return null;
             }),
+
+        // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/h$/),
+            new RegExp(/(%h|h)$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
                 const pos = (state.doc.resolve(start) as any);
-                let depth = pos.path.length / 3 - 1;
-                for (; depth >= 0; depth--) {
-                    let node = pos.node(depth);
+                for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                    const node = pos.node(depth);
                     if (node.type === schema.nodes.paragraph) {
                         const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
-                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                        const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                        return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
                     }
                 }
                 return null;
             }),
+        // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/q$/),
+            new RegExp(/(%q|q)$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
                 const pos = (state.doc.resolve(start) as any);
-                if (state.selection instanceof NodeSelection && (state.selection as NodeSelection).node.type === schema.nodes.ordered_list) {
-                    let node = (state.selection as NodeSelection).node;
+                if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
+                    const node = state.selection.node;
                     return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
                 }
-                let depth = pos.path.length / 3 - 1;
-                for (; depth >= 0; depth--) {
-                    let node = pos.node(depth);
+                for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                    const node = pos.node(depth);
                     if (node.type === schema.nodes.paragraph) {
                         const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
-                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                        const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                        return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
                     }
                 }
                 return null;
             }),
-        new InputRule(
-            new RegExp(/!$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
-                const node = (state.doc.resolve(start) as any).nodeAfter;
-                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
-            }),
-        new InputRule(
-            new RegExp(/x$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
-                const node = (state.doc.resolve(start) as any).nodeAfter;
-                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
-            }),
-        new InputRule(
-            new RegExp(/@$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
 
-                const value = state.doc.textBetween(start, end);
-                if (value) {
-                    DocServer.GetRefField(value).then(docx => {
-                        let doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
-                        DocUtils.Publish(doc, value, returnFalse, returnFalse);
-                    });
-                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
-                    return state.tr.addMark(start, end, link);
-                }
-                return state.tr;
-            }),
+
+        // center justify text
         new InputRule(
-            new RegExp(/^\^\^\s$/),
+            new RegExp(/%\^$/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
@@ -212,8 +205,9 @@ export const inpRules = {
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
+        // left justify text
         new InputRule(
-            new RegExp(/^\[\[\s$/),
+            new RegExp(/%\[$/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
@@ -227,8 +221,9 @@ export const inpRules = {
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
+        // right justify text
         new InputRule(
-            new RegExp(/^\]\]\s$/),
+            new RegExp(/%\]$/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
@@ -243,7 +238,7 @@ export const inpRules = {
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
         new InputRule(
-            new RegExp(/##\s$/),
+            new RegExp(/%#$/),
             (state, match, start, end) => {
                 const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", autoHeight: true, fontSize: 9, title: "inline comment" });
                 const node = (state.doc.resolve(start) as any).nodeAfter;
@@ -255,26 +250,25 @@ export const inpRules = {
                 return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end)));
             }),
         new InputRule(
-            new RegExp(/\(\(/),
+            new RegExp(/%\(/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const mark = state.schema.marks.highlight.create();
+                const mark = state.schema.marks.summarizeInclusive.create();
                 const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
                 const content = selected.selection.content();
                 const replaced = node ? selected.replaceRangeWith(start, start,
-                    schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1)));
             }),
         new InputRule(
-            new RegExp(/\)\)/),
+            new RegExp(/%\)/),
             (state, match, start, end) => {
-                const mark = state.schema.marks.highlight.create();
-                return state.tr.removeStoredMark(mark);
+                return state.tr.removeStoredMark(state.schema.marks.summarizeInclusive.create());
             }),
         new InputRule(
-            new RegExp(/\^f\s$/),
+            new RegExp(/%f\$/),
             (state, match, start, end) => {
                 const newNode = schema.nodes.footnote.create({});
                 const tr = state.tr;
@@ -283,9 +277,5 @@ export const inpRules = {
                     tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
                         tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
             }),
-        // let newNode = schema.nodes.footnote.create({});
-        // if (dispatch && state.selection.from === state.selection.to) {
-        //     return true;
-        // }
     ]
 };
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f9251fb7e..543f45731 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -109,7 +109,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         },
     },
 
-    star: {
+    summary: {
         inline: true,
         attrs: {
             visibility: { default: false },
@@ -121,15 +121,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             const attrs = { style: `width: 40px` };
             return ["span", { ...node.attrs, ...attrs }];
         },
-        // parseDOM: [{
-        //     tag: "star", getAttrs(dom: any) {
-        //         return {
-        //             visibility: dom.getAttribute("visibility"),
-        //             oldtext: dom.getAttribute("oldtext"),
-        //             oldtextlen: dom.getAttribute("oldtextlen"),
-        //         }
-        //     }
-        // }]
     },
 
     // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
@@ -228,6 +219,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             mapStyle: { default: "decimal" },
             setFontSize: { default: undefined },
             setFontFamily: { default: "inherit" },
+            setFontColor: { default: "inherit" },
             inheritedFontSize: { default: undefined },
             visibility: { default: true },
             indent: { default: undefined }
@@ -237,8 +229,10 @@ export const nodes: { [index: string]: NodeSpec } = {
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
             const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
             const ffam = node.attrs.setFontFamily;
-            return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; margin-left: ${node.attrs.indent}` }, 0] :
-                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }];
+            const color = node.attrs.setFontColor;
+            return node.attrs.visibility ?
+                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
+                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
         }
     },
 
@@ -318,7 +312,7 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             highlight: { default: "transparent" }
         },
-        inclusive: false,
+        inclusive: true,
         parseDOM: [{
             tag: "span", getAttrs(dom: any) {
                 return { highlight: dom.getAttribute("backgroundColor") };
@@ -401,7 +395,7 @@ export const marks: { [index: string]: MarkSpec } = {
         }
     },
 
-    highlight: {
+    summarizeInclusive: {
         parseDOM: [
             {
                 tag: "span",
@@ -410,7 +404,7 @@ export const marks: { [index: string]: MarkSpec } = {
                         const style = getComputedStyle(p);
                         if (style.textDecoration === "underline") return null;
                         if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
-                            p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
                             return null;
                         }
                     }
@@ -419,6 +413,31 @@ export const marks: { [index: string]: MarkSpec } = {
             },
         ],
         inclusive: true,
+        toDOM() {
+            return ['span', {
+                style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
+            }];
+        }
+    },
+
+    summarize: {
+        inclusive: false,
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const style = getComputedStyle(p);
+                        if (style.textDecoration === "underline") return null;
+                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+                            return null;
+                        }
+                    }
+                    return false;
+                }
+            },
+        ],
         toDOM() {
             return ['span', {
                 style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
@@ -637,7 +656,7 @@ export class DashDocCommentView {
             }
             const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
             view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
-            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))) } catch (e) { } }, 0);
+            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
             return undefined;
         };
         this._collapsed.onpointerdown = (e: any) => {
@@ -646,16 +665,16 @@ export class DashDocCommentView {
         this._collapsed.onpointerup = (e: any) => {
             const target = targetNode();
             if (target) {
-                let expand = target.hidden;
-                let tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+                const expand = target.hidden;
+                const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
                 view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs 
                 setTimeout(() => {
                     expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
-                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))) } catch (e) { }
+                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
                 }, 0);
             }
             e.stopPropagation();
-        }
+        };
         this._collapsed.onpointerenter = (e: any) => {
             DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
             e.preventDefault();
@@ -908,7 +927,7 @@ export class FootnoteView {
     ignoreMutation() { return true; }
 }
 
-export class SummarizedView {
+export class SummaryView {
     _collapsed: HTMLElement;
     _view: any;
     constructor(node: any, view: any, getPos: any) {
@@ -946,7 +965,8 @@ export class SummarizedView {
     className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
 
     updateSummarizedText(start?: any) {
-        const mark = this._view.state.schema.marks.highlight.create();
+        const mtype = this._view.state.schema.marks.summarize;
+        const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
         let endPos = start;
 
         const visited = new Set();
@@ -954,7 +974,7 @@ export class SummarizedView {
             let skip = false;
             this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
                 if (node.isLeaf && !visited.has(node) && !skip) {
-                    if (node.marks.find((m: any) => m.type === mark.type)) {
+                    if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
                         visited.add(node);
                         endPos = i + node.nodeSize - 1;
                     }
@@ -979,7 +999,7 @@ const fromJson = schema.nodeFromJSON;
 
 schema.nodeFromJSON = (json: any) => {
     const node = fromJson(json);
-    if (json.type === "star") {
+    if (json.type === schema.marks.summarize.name) {
         node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
     }
     return node;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index f29dbf2e4..483ab40a7 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,4 +1,3 @@
-import { action } from "mobx";
 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';
@@ -10,8 +9,6 @@ import { Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { DocumentManager } from "./DocumentManager";
-import { DragManager } from "./DragManager";
 import { LinkManager } from "./LinkManager";
 import { schema } from "./RichTextSchema";
 import "./TooltipTextMenu.scss";
@@ -20,13 +17,11 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-import { Keys } from "../views/search/FilterBox";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
 //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
@@ -48,10 +43,9 @@ export class TooltipTextMenu {
     // editor button doms
     private colorDom?: Node;
     private colorDropdownDom?: Node;
-    private highlightDom?: Node;
-    private highlightDropdownDom?: Node;
+    private highighterDom?: Node;
+    private highlighterDropdownDom?: Node;
     private linkEditor?: HTMLDivElement;
-    private linkText?: HTMLDivElement;
     private linkDrag?: HTMLImageElement;
     private _linkDropdownDom?: Node;
     private _brushdom?: Node;
@@ -94,7 +88,6 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.strikethrough), dom: this.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") },
             { command: toggleMark(schema.marks.superscript), dom: this.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") },
             { command: toggleMark(schema.marks.subscript), dom: this.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") },
-            // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') }
         ];
 
         // add menu items
@@ -123,20 +116,15 @@ export class TooltipTextMenu {
                 if (dom.contains(e.target as Node)) {
                     e.stopPropagation();
                     command(this.view.state, this.view.dispatch, this.view);
-                    // if (this.view.state.selection.empty) {
-                    //     if (dom.style.color === "white") { dom.style.color = "greenyellow"; }
-                    //     else { dom.style.color = "white"; }
-                    // }
                 }
             });
-
         });
 
-        // highlight menu
-        this.highlightDom = this.createHighlightTool().render(this.view).dom;
-        this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom;
-        this.tooltip.appendChild(this.highlightDom);
-        this.tooltip.appendChild(this.highlightDropdownDom);
+        // 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;
@@ -166,7 +154,7 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(this._brushDropdownDom);
 
         // star
-        this.tooltip.appendChild(this.createStar().render(this.view).dom);
+        this.tooltip.appendChild(this.createSummarizer().render(this.view).dom);
 
         // list types dropdown
         this.updateListItemDropdown(":", this.listTypeBtnDom);
@@ -289,8 +277,6 @@ export class TooltipTextMenu {
             // stop moving when mouse button is released:
             document.onpointerup = null;
             document.onpointermove = null;
-            //self.highlightSearchTerms(self.state, ["hello"]);
-            //FormattedTextBox.Instance.unhighlightSearchTerms();
         }
     }
 
@@ -302,11 +288,10 @@ export class TooltipTextMenu {
             fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize));
         });
 
-        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); }
+        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);
         }
@@ -321,11 +306,10 @@ export class TooltipTextMenu {
             fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily));
         });
 
-        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); }
+        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);
         }
@@ -333,94 +317,16 @@ export class TooltipTextMenu {
     }
 
     updateLinkMenu() {
-        if (!this.linkEditor || !this.linkText) {
-            this.linkEditor = document.createElement("div");
-            this.linkEditor.className = "ProseMirror-icon menuicon";
-            this.linkText = document.createElement("div");
-            this.linkText.setAttribute("contenteditable", "true");
-            this.linkText.style.whiteSpace = "nowrap";
-            this.linkText.style.width = "150px";
-            this.linkText.style.overflow = "hidden";
-            this.linkText.style.color = "white";
-            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
-            const linkBtn = document.createElement("div");
-            linkBtn.textContent = ">>";
-            linkBtn.style.width = "10px";
-            linkBtn.style.height = "10px";
-            linkBtn.style.color = "white";
-            linkBtn.style.cssFloat = "left";
-            linkBtn.onpointerdown = (e: PointerEvent) => {
-                const node = this.view.state.selection.$from.nodeAfter;
-                const link = node && node.marks.find(m => m.type.name === "link");
-                if (link) {
-                    const href: string = link.attrs.href;
-                    if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                        const docid = href.replace(Utils.prepend("/doc/"), "");
-                        DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
-                            if (f instanceof Doc) {
-                                if (DocumentManager.Instance.getDocumentView(f)) {
-                                    DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false);
-                                }
-                                else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight");
-                            }
-                        }));
-                    }
-                    // TODO This should have an else to handle external links
-                    e.stopPropagation();
-                    e.preventDefault();
-                }
-            };
-            this.linkDrag = document.createElement("img");
-            this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
-            this.linkDrag.style.width = "15px";
-            this.linkDrag.style.height = "15px";
-            this.linkDrag.title = "Drag to create link";
-            this.linkDrag.id = "link-drag";
-            this.linkDrag.onpointerdown = (e: PointerEvent) => {
-                if (!this.editorProps) return;
-                const dragData = new DragManager.LinkDragData(this.editorProps.Document);
-                dragData.dontClearTextBox = true;
-                // hack to get source context -sy
-                const docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document);
-                e.stopPropagation();
-                const ctrlKey = e.ctrlKey;
-                DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
-                    {
-                        handlers: {
-                            dragComplete: action(() => {
-                                if (dragData.linkDocument) {
-                                    const linkDoc = dragData.linkDocument;
-                                    const proto = Doc.GetProto(linkDoc);
-                                    if (proto && docView) {
-                                        proto.sourceContext = docView.props.ContainingCollectionDoc;
-                                    }
-                                    const 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
-                                    }
-                                }
-                            }),
-                        },
-                        hideSource: false
-                    });
-                e.stopPropagation();
-                e.preventDefault();
-            };
-            this.linkEditor.appendChild(this.linkDrag);
-            this.tooltip.appendChild(this.linkEditor);
-        }
-
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type.name === "link");
-        this.linkText.textContent = link ? link.attrs.href : "-empty-";
-
-        this.linkText.onkeydown = (e: KeyboardEvent) => {
-            if (e.key === "Enter") {
-                // this.makeLink(this.linkText!.textContent!);
-                e.stopPropagation();
-                e.preventDefault();
-            }
-        };
+        this.linkEditor = document.createElement("div");
+        this.linkEditor.className = "ProseMirror-icon menuicon";
+        this.linkDrag = document.createElement("img");
+        this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
+        this.linkDrag.style.width = "15px";
+        this.linkDrag.style.height = "15px";
+        this.linkDrag.title = "Click to set link target";
+        this.linkDrag.id = "link-btn";
+        this.linkEditor.appendChild(this.linkDrag);
+        this.tooltip.appendChild(this.linkEditor);
     }
 
     async getTextLinkTargetTitle() {
@@ -485,9 +391,7 @@ export class TooltipTextMenu {
                 return div;
             },
             enable() { return false; },
-            run(p1, p2, p3, event) {
-                event.stopPropagation();
-            }
+            run(p1, p2, p3, event) { event.stopPropagation(); }
         });
 
         // menu item to update/apply the hyperlink to the selected text
@@ -584,24 +488,6 @@ export class TooltipTextMenu {
         }
     }
 
-    deleteLinkItem() {
-        const icon = {
-            height: 16, width: 16,
-            path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z"
-        };
-        return new MenuItem({
-            title: "Delete Link",
-            label: "X",
-            icon: icon,
-            css: "color: red",
-            class: "summarize",
-            execEvent: "",
-            run: (state, dispatch) => {
-                this.deleteLink();
-            }
-        });
-    }
-
     createLink() {
         const markType = schema.marks.link;
         return new MenuItem({
@@ -655,22 +541,19 @@ export class TooltipTextMenu {
         //Make a dropdown of all list types
         const toAdd: MenuItem[] = [];
         this.listTypeToIcon.forEach((icon, type) => {
-            toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+            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.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+        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;
+        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;
     }
 
-    createStar() {
+    createSummarizer() {
         return new MenuItem({
             title: "Summarize",
             label: "Summarize",
@@ -678,31 +561,17 @@ export class TooltipTextMenu {
             css: "color:white;",
             class: "menuicon",
             execEvent: "",
-            run: (state, dispatch) => {
-                TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
-            }
-
+            run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
         });
     }
 
-    public static insertStar(state: EditorState<any>, dispatch: any) {
+    public static insertSummarizer(state: EditorState<any>, dispatch: any) {
         if (state.selection.empty) return false;
-        const mark = state.schema.marks.highlight.create();
+        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.star.create({ visibility: false, text: content, textslice: content.toJSON() });
-        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
-        return true;
-    }
-
-    public static insertComment(state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        const mark = state.schema.marks.highlight.create();
-        const tr = state.tr;
-        tr.addMark(state.selection.from, state.selection.to, mark);
-        const content = tr.selection.content();
-        const newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+        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;
     }
@@ -722,7 +591,7 @@ export class TooltipTextMenu {
 
                 const color = document.createElement("div");
                 color.className = "buttonColor";
-                color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString();
+                color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
 
                 const wrapper = document.createElement("div");
                 wrapper.id = "colorPicker";
@@ -730,17 +599,14 @@ export class TooltipTextMenu {
                 wrapper.appendChild(color);
                 return wrapper;
             },
-            run: (state, dispatch) => {
-                TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch);
-            }
+            run: (state, dispatch) => TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, state, dispatch)
         });
     }
 
     public static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
         if (state.selection.empty) return false;
 
-        const highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color });
-        dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark));
+        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
     }
 
     createHighlightDropdown() {
@@ -773,22 +639,22 @@ export class TooltipTextMenu {
 
                 colors.forEach(color => {
                     const button = document.createElement("button");
-                    button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker";
+                    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.highlight = color;
+                            TooltipTextMenuManager.Instance.highlighter = color;
 
-                            TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch);
+                            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.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom);
-                            self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom);
-                            self.highlightDom = highlightDom;
-                            self.highlightDropdownDom = highlightDropdownDom;
+                            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);
@@ -832,19 +698,18 @@ export class TooltipTextMenu {
                 wrapper.appendChild(color);
                 return wrapper;
             },
-            run: (state, dispatch) => {
-                TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch);
-            }
+            run: (state, dispatch) => this.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
         });
     }
 
-    public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
+    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;
         }
-        dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
+        this.setMark(colorMark, state, dispatch);
+        toggleMark(colorMark.type, { color: color })(state, dispatch);
     }
 
     createColorDropdown() {
@@ -883,7 +748,7 @@ export class TooltipTextMenu {
                         button.onclick = e => {
                             TooltipTextMenuManager.Instance.color = color;
 
-                            TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
+                            self.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
 
                             // update color menu
                             const colorDom = self.createColorTool().render(self.view).dom;
@@ -903,13 +768,10 @@ export class TooltipTextMenu {
                 return div;
             },
             enable() { return false; },
-            run(p1, p2, p3, event) {
-                event.stopPropagation();
-            }
+            run(p1, p2, p3, event) { event.stopPropagation(); }
         });
 
-        const colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return colorDropdown;
+        return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
     createBrush(active: boolean = false) {
@@ -933,9 +795,7 @@ export class TooltipTextMenu {
                 self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
                 self._brushDropdownDom = newBrushDropdowndom;
             },
-            active: (state) => {
-                return true;
-            }
+            active: (state) => true
         });
     }
 
@@ -959,8 +819,7 @@ export class TooltipTextMenu {
                     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) => {
-                            const markType = mark.type;
-                            this.changeToMarkInGroup(markType, this.view, []);
+                            this.setMark(mark, this.view.state, this.view.dispatch);
                         });
                     }
                 }
@@ -995,9 +854,7 @@ export class TooltipTextMenu {
             class: "button-setting-disabled",
             css: "",
             enable() { return false; },
-            run(p1, p2, p3, event) {
-                event.stopPropagation();
-            }
+            run(p1, p2, p3, event) { event.stopPropagation(); }
         });
 
         const self = this;
@@ -1056,71 +913,26 @@ export class TooltipTextMenu {
         });
 
         const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
-        const brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return brushDom;
+        return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
-    //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[]) => {
-        const { $cursor, ranges } = view.state.selection as TextSelection;
-        const state = view.state;
-        const dispatch = view.dispatch;
-
-        //remove all other active font marks
-        fontMarks.forEach((type) => {
-            if (dispatch) {
-                if ($cursor) {
-                    if (type.isInSet(state.storedMarks || $cursor.marks())) {
-                        dispatch(state.tr.removeStoredMark(type));
-                    }
-                } else {
-                    let has = false;
-                    for (let i = 0; !has && i < ranges.length; i++) {
-                        const { $from, $to } = ranges[i];
-                        has = state.doc.rangeHasMark($from.pos, $to.pos, type);
-                    }
-                    for (const i of ranges) {
-                        if (has) {
-                            toggleMark(type)(view.state, view.dispatch, view);
-                        }
-                    }
-                }
-            }
-        });
 
-        if (markType) {
-            //actually apply font
-            if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
-                const 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: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema);
-                view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
+    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, dispatch);
             }
-            else toggleMark(markType)(view.state, view.dispatch, view);
         }
     }
 
     changeToFontFamily = (mark: Mark, view: EditorView) => {
-        const { $cursor, ranges } = view.state.selection as TextSelection;
-        const state = view.state;
-        const 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++) {
-                const { $from, $to } = ranges[i];
-                has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily);
-            }
-            for (const i of ranges) {
-                if (has) {
-                    toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view);
-                }
-            }
-        }
-
         const fontName = mark.attrs.family;
         if (fontName) { this.updateFontStyleDropdown(fontName); }
         if (this.editorProps) {
@@ -1130,39 +942,10 @@ export class TooltipTextMenu {
                 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) {
-            const 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 })];
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
     }
 
     changeToFontSize = (mark: Mark, view: EditorView) => {
-        const { $cursor, ranges } = view.state.selection as TextSelection;
-        const state = view.state;
-        const 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++) {
-                const { $from, $to } = ranges[i];
-                has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize);
-            }
-            for (const i of ranges) {
-                if (has) {
-                    toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view);
-                }
-            }
-        }
-
         const size = mark.attrs.fontSize;
         if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
         if (this.editorProps) {
@@ -1172,18 +955,11 @@ export class TooltipTextMenu {
                 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) {
-            const 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 })];
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
     }
 
     //remove all node typeand apply the passed-in one to the selected text
-    changeToNodeType = (nodeType: NodeType | undefined) => {
+    changeBulletType = (nodeType: NodeType | undefined) => {
         //remove oldif (nodeType) { //add new
         const view = this.view;
         if (nodeType === schema.nodes.bullet_list) {
@@ -1211,46 +987,40 @@ export class TooltipTextMenu {
     //css is the style you want applied to the button
     dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark<any>, view: EditorView) => any) {
         return new MenuItem({
-            title: "",
+            title: "Set Font Family",
             label: label,
             execEvent: "",
             class: "dropdown-item",
             css: css,
             enable() { return true; },
-            run() {
-                changeFontFamily(mark, view);
-            }
+            run() { 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: "",
+            title: "Set Font Size",
             label: label,
             execEvent: "",
             class: "dropdown-item",
             css: css,
             enable() { return true; },
-            run() {
-                changeFontSize(mark, view);
-            }
+            run() { changeFontSize(mark, view); }
         });
     }
 
     //makes a button for the drop down FOR NODE TYPES
     //css is the style you want applied to the button
-    dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
+    dropdownBulletBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
         return new MenuItem({
-            title: "",
+            title: "Set Bullet Style",
             label: label,
             execEvent: "",
             class: "dropdown-item",
             css: css,
             enable() { return true; },
-            run() {
-                changeToNodeInGroup(nodeType, view, groupNodes);
-            }
+            run() { changeToNodeInGroup(nodeType, view, groupNodes); }
         });
     }
 
@@ -1325,14 +1095,7 @@ export class TooltipTextMenu {
     getMarksInSelection(state: EditorState<any>) {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => {
-            const marks = node.marks;
-            if (marks) {
-                marks.forEach(m => {
-                    found.add(m);
-                });
-            }
-        });
+        state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m)));
         return found;
     }
 
@@ -1362,12 +1125,6 @@ export class TooltipTextMenu {
 
         this.reset_mark_doms();
 
-        // Hide the tooltip if the selection is empty
-        if (state.selection.empty) {
-            //this.tooltip.style.display = "none";
-            //return;
-        }
-
         // update link dropdown
         const linkDropdown = await this.createLinkDropdown();
         const newLinkDropdowndom = linkDropdown.render(this.view).dom;
@@ -1473,7 +1230,6 @@ export class TooltipTextMenu {
                     }
                     const mark = state.schema.mark(mark_type);
                     return ref_node.marks.includes(mark);
-                    return false;
                 });
             }
             else {
@@ -1518,19 +1274,19 @@ export class TooltipTextMenu {
 
 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;
-    private _isPinned: boolean = false;
 
     public _brushMarks: Set<Mark> | undefined;
     public _brushMap: Map<string, Set<Mark>> = new Map();
     public _brushIsEmpty: boolean = true;
 
     public color: String = "#000";
-    public highlight: String = "transparent";
+    public highlighter: String = "transparent";
 
     public activeMenu: TooltipTextMenu | undefined;
 
@@ -1541,11 +1297,7 @@ export class TooltipTextMenuManager {
         return TooltipTextMenuManager._instance;
     }
 
-    public get isPinned() {
-        return this._isPinned;
-    }
+    public get isPinned() { return this._isPinned; }
 
-    public toggleIsPinned() {
-        this._isPinned = !this._isPinned;
-    }
+    public toggleIsPinned() { this._isPinned = !this._isPinned; }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dd3b740fb..f366a3677 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -85,14 +85,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
                 this._titleControlString = this._accumulatedTitle;
             } else if (this._titleControlString.startsWith("#")) {
-                let selectionTitleFieldKey = this._titleControlString.substring(1);
+                const selectionTitleFieldKey = this._titleControlString.substring(1);
                 selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._accumulatedTitle.startsWith("-"));
                 selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d =>
                     Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle, true)
                 );
             }
         }
-    }))
+    }));
 
     @action titleEntered = (e: any) => {
         const key = e.keyCode || e.which;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index d0cecf03d..54def38b5 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -120,7 +120,7 @@ export class EditableView extends React.Component<EditableProps> {
 
     @action
     setIsFocused = (value: boolean) => {
-        let wasFocused = this._editing;
+        const wasFocused = this._editing;
         this._editing = value;
         return wasFocused !== this._editing;
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 01cd7957c..25b7dc5ec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -286,7 +286,7 @@ export class MainView extends React.Component {
             ContainingCollectionDoc={undefined}
             zoomToScale={emptyFunction}
             getScale={returnOne}
-        />
+        />;
     }
     @computed get dockingContent() {
         TraceMobx();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 411040332..15eec37de 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -240,9 +240,9 @@ export class CollectionView extends Touchable<FieldViewProps> {
         const mainPath = path.extname(images[this._curLightboxImg]);
         const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length]);
         const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length]);
-        let main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
-        let next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
-        let prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
+        const main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
+        const next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
+        const prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
         return !this._isLightboxOpen ? (null) : (<Lightbox key="lightbox"
             mainSrc={main}
             nextSrc={next}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 24cc0b5ea..3620ccb34 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -148,7 +148,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
                 {
                     this._titleRef.current?.setIsFocused(false);
-                    let any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
+                    const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
                     any.keeplocation = true;
                     any?.focus();
                 }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9318142e2..ec9e0ce5b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -27,7 +27,7 @@ import { DictationManager } from '../../util/DictationManager';
 import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorExampleTransfer";
 import { inpRules } from "../../util/RichTextRules";
-import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema";
+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";
@@ -822,7 +822,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
                     dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
                     image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
-                    star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
+                    summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
                     ordered_list(node, view, getPos) { return new OrderedListView(); },
                     footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
                 },
@@ -907,10 +907,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this.tryUpdateHeight();
 
         // see if we need to preserve the insertion point
-        let prosediv = this._proseRef?.children?.[0] as any;
-        let keeplocation = prosediv?.keeplocation;
+        const prosediv = this._proseRef?.children?.[0] as any;
+        const keeplocation = prosediv?.keeplocation;
         prosediv && (prosediv.keeplocation = undefined);
-        let pos = this._editorView?.state.selection.$from.pos || 1;
+        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))));
     }
     onPointerWheel = (e: React.WheelEvent): void => {
@@ -925,14 +925,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     onClick = (e: React.MouseEvent): void => {
         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.
-            let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
-            let 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)
+            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) {
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
                 e.preventDefault();
             }
             if (!node && this._proseRef) {
-                let lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+                const lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
                 if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
                     this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
                 }
@@ -973,7 +973,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         //     }
         // }
 
-        this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false)
+        this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
         if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500);
     }
 
@@ -984,7 +984,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (pos && this.props.isSelected(true)) {
             // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
             //const node = this._editorView!.state.doc.nodeAt(pos.pos);
-            let $pos = this._editorView!.state.doc.resolve(pos.pos);
+            const $pos = this._editorView!.state.doc.resolve(pos.pos);
             let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
             if ($pos.node().type === schema.nodes.ordered_list) {
                 for (let off = 1; off < 100; off++) {
@@ -998,7 +998,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             }
             if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
                 if (select) {
-                    let $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
                     if (!highlightOnly) {
                         this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
                     }
@@ -1064,14 +1064,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             return;
         }
         if (!this._editorView!.state.selection.empty && e.key === "%") {
-            (this._editorView!.state as any).EnteringStyle = true;
+            this._editorView!.state.schema.EnteringStyle = true;
             e.preventDefault();
             e.stopPropagation();
             return;
         }
 
-        if (this._editorView!.state.selection.empty || !(this._editorView!.state as any).EnteringStyle) {
-            (this._editorView!.state as any).EnteringStyle = false;
+        if (this._editorView!.state.selection.empty || !this._editorView!.state.schema.EnteringStyle) {
+            this._editorView!.state.schema.EnteringStyle = false;
         }
         if (e.key === "Escape") {
             SelectionManager.DeselectAll();
@@ -1080,7 +1080,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark! : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
         this._lastTimedMark = mark;
         this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
 
@@ -1100,7 +1100,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             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
             const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             const dh = NumCast(this.layoutDoc.height, 0);
-            let newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+            const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
             if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
                 this.layoutDoc.height = newHeight;
                 this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
-- 
cgit v1.2.3-70-g09d2


From 6683517a69f4806d026e7af85069f18b1f0a9d1f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 15 Dec 2019 12:44:20 -0500
Subject: added _last_ for computedScripts to access last value of script

---
 src/client/util/RichTextSchema.tsx     | 6 +++++-
 src/client/views/nodes/KeyValueBox.tsx | 3 +--
 src/new_fields/Doc.ts                  | 5 ++---
 src/new_fields/ScriptField.ts          | 3 ++-
 4 files changed, 10 insertions(+), 7 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 543f45731..7a048ed0d 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -741,7 +741,11 @@ export class DashDocView {
                 self._dashDoc = dashDoc;
                 dashDoc.hideSidebar = true;
                 if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") {
-                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
+                    try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+                        view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
+                    } catch (e) {
+                        console.log(e);
+                    }
                 }
                 this._reactionDisposer && this._reactionDisposer();
                 this._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 07d6d6f5e..234a6a9d3 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -57,8 +57,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         value = eq ? value.substr(1) : value;
         const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false;
         value = dubEq ? value.substr(2) : value;
-        const editable = value.includes("selectedDocs"); // bcz: Argh TODO - need a UI mechanism for letting user know that a function can't be run read-only. e.g., selectedDocs() wants to cache its old value so it can't be run read-only
-        const options: ScriptOptions = { addReturn: true, params: { this: "Doc" }, editable: editable };
+        const options: ScriptOptions = { addReturn: true, params: { this: "Doc", _last_: "any" }, editable: false };
         if (dubEq) options.typecheck = false;
         const script = CompileScript(value, options);
         if (!script.compiled) {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a22b289bb..171f3fd2d 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -754,9 +754,8 @@ Scripting.addGlobal(function docList(field: any) { return DocListCast(field); })
 Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
 Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
 Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
-Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean) {
+Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
     let docs = DocListCast(Doc.UserDoc().SelectedDocs).filter(d => (!excludeCollections || !Cast(d.data, listSpec(Doc), null)) && d.type !== DocumentType.KVP && !Doc.AreProtosEqual(d, container));
-    if (docs.length) untracked(() => container && (container.cachedSelection = new List(docs)));
-    else docs = untracked(() => DocListCast(container.cachedSelection));
+    if (!docs.length) return prevValue;
     return new List(docs);
 });
\ No newline at end of file
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 6b2723663..0cadb60ed 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -124,8 +124,9 @@ export class ScriptField extends ObjectField {
 @scriptingGlobal
 @Deserializable("computed", deserializeScript)
 export class ComputedField extends ScriptField {
+    _lastComputedResult: any;
     //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
-    value = computedFn((doc: Doc) => this.script.run({ this: doc }, console.log).result);
+    value = computedFn((doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, _last_: this._lastComputedResult }, console.log).result);
     public static MakeScript(script: string, params: object = {}, ) {
         const compiled = ScriptField.CompileScript(script, params, false);
         return compiled.compiled ? new ComputedField(compiled) : undefined;
-- 
cgit v1.2.3-70-g09d2


From e8606ffee44b934deea5878afd110e39ec1ae63c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 20 Dec 2019 13:50:36 -0500
Subject: fixed nested text box to stopPropagation on key press to prevent
 backscpace from deleting everything.  made Esc desleect text and the
 document.  fixed jumpToDOcument to focus annotationOn documents properly
 (particularly for sidebar text notes).  fixed deselecting docsin textbox
 sidebar.

---
 src/client/documents/Documents.ts             |  3 ++-
 src/client/util/DocumentManager.ts            |  9 +++++---
 src/client/util/ProsemirrorExampleTransfer.ts |  8 ++++++-
 src/client/util/RichTextRules.ts              |  2 +-
 src/client/util/RichTextSchema.tsx            | 11 +++++++---
 src/client/views/nodes/FormattedTextBox.tsx   | 31 ++++++++++++++++-----------
 6 files changed, 42 insertions(+), 22 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 50c51ace1..304eccc02 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -97,6 +97,7 @@ export interface DocumentOptions {
     schemaColumns?: List<SchemaHeaderField>;
     dockingConfig?: string;
     autoHeight?: boolean;
+    annotationOn?: Doc;
     removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped.  e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
     dbDoc?: Doc;
     ischecked?: ScriptField; // returns whether a font icon box is checked
@@ -316,7 +317,7 @@ export namespace Docs {
      */
     export namespace Create {
 
-        const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "nativeWidth", "nativeHeight", "dropAction", "forceActive", "fitWidth"];
+        const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "nativeWidth", "nativeHeight", "dropAction", "annotationOn", "forceActive", "fitWidth"];
 
         /**
          * This function receives the relevant document prototype and uses
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 85303176f..60c251f0e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -134,10 +134,13 @@ export class DocumentManager {
             finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document);
         };
         const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
-        const annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
+        let annotatedDoc = await Cast(docView?.props.Document.annotationOn, Doc);
+        if (annotatedDoc) {
+            let first = DocumentManager.Instance.getFirstDocumentView(annotatedDoc);
+            if (first) annotatedDoc = first.props.Document;
+        }
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
-            annotatedDoc && docView.props.focus(annotatedDoc, false);
-            docView.props.focus(docView.props.Document, willZoom);
+            docView.props.focus(annotatedDoc || docView.props.Document, false);
             highlight();
         } else {
             const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 3324d8abe..b879ca7aa 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -6,6 +6,8 @@ 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;
 
@@ -46,7 +48,11 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     bind("Alt-ArrowUp", joinUp);
     bind("Alt-ArrowDown", joinDown);
     bind("Mod-BracketLeft", lift);
-    bind("Escape", selectParentNode);
+    bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+        (document.activeElement as any).blur?.();
+        SelectionManager.DeselectAll();
+    });
 
     bind("Mod-b", toggleMark(schema.marks.strong));
     bind("Mod-B", toggleMark(schema.marks.strong));
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 62b20b3fa..5148af889 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -220,7 +220,7 @@ export const inpRules = {
         new InputRule(
             new RegExp(/%#$/),
             (state, match, start, end) => {
-                const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", autoHeight: true, fontSize: 9, title: "inline comment" });
+                const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, autoHeight: true, fontSize: 9, title: "inline comment" });
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const newNode = schema.nodes.dashComment.create({ docid: target[Id] });
                 const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: target[Id], float: "right" });
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 7a048ed0d..bede23cb2 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -765,14 +765,14 @@ export class DashDocView {
                     renderDepth={1}
                     PanelWidth={self._dashDoc[WidthSym]}
                     PanelHeight={self._dashDoc[HeightSym]}
-                    focus={emptyFunction}
+                    focus={self._textBox.props.focus}
                     backgroundColor={returnEmptyString}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
                     zoomToScale={emptyFunction}
                     getScale={returnOne}
-                    dontRegisterView={true}
+                    dontRegisterView={false}
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
                     ContentScaling={this.contentScaling}
@@ -780,7 +780,12 @@ export class DashDocView {
             }
         });
         const self = this;
-        this._dashSpan.onkeydown = function (e: any) { };
+        this._dashSpan.onkeydown = function (e: any) {
+            e.stopPropagation();
+            if (e.key === "Tab" || e.key === "Enter") {
+                e.preventDefault();
+            }
+        };
         this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
         this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 662cc9aea..6d889b495 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -76,8 +76,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     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 _proseRef?: HTMLDivElement;
     private _editorView: Opt<EditorView>;
     private _applyingChange: boolean = false;
     private _searchIndex = 0;
@@ -244,7 +244,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
     }
     protected createDropTarget = (ele: HTMLDivElement) => {
-        this._proseRef = ele;
+        this.ProseRef = ele;
         this.dropDisposer && this.dropDisposer();
         ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
     }
@@ -571,7 +571,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 this._ruleFontSize = rules ? rules.size : 0;
                 rules && setTimeout(() => {
                     const view = this._editorView!;
-                    if (this._proseRef) {
+                    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))).
@@ -794,10 +794,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 startup = NumCast(doc[fieldKey], 99).toString();
             }
         }
-        if (this._proseRef) {
+        if (this.ProseRef) {
             const self = this;
             this._editorView && this._editorView.destroy();
-            this._editorView = new EditorView(this._proseRef, {
+            this._editorView = new EditorView(this.ProseRef, {
                 state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
                 handleScrollToSelection: (editorView) => {
                     const ref = editorView.domAtPos(editorView.state.selection.from);
@@ -874,7 +874,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             e.preventDefault();
         }
         if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
-            e.stopPropagation();
+            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar
+                e.stopPropagation();
+            }
         }
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
@@ -901,7 +903,7 @@ 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 prosediv = this.ProseRef?.children?.[0] as any;
         const keeplocation = prosediv?.keeplocation;
         prosediv && (prosediv.keeplocation = undefined);
         const pos = this._editorView?.state.selection.$from.pos || 1;
@@ -925,8 +927,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
                 e.preventDefault();
             }
-            if (!node && this._proseRef) {
-                const lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+            if (!node && this.ProseRef) {
+                const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
                 if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
                     this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
                 }
@@ -1057,17 +1059,20 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             e.preventDefault();
             return;
         }
-        if (!this._editorView!.state.selection.empty && e.key === "%") {
-            this._editorView!.state.schema.EnteringStyle = true;
+        let state = this._editorView!.state;
+        if (!state.selection.empty && e.key === "%") {
+            state.schema.EnteringStyle = true;
             e.preventDefault();
             e.stopPropagation();
             return;
         }
 
-        if (this._editorView!.state.selection.empty || !this._editorView!.state.schema.EnteringStyle) {
-            this._editorView!.state.schema.EnteringStyle = false;
+        if (state.selection.empty || !state.schema.EnteringStyle) {
+            state.schema.EnteringStyle = false;
         }
         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?.();
             SelectionManager.DeselectAll();
         }
         e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From 218a04638200b27b6b9132ca9086bb4d76740acf Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 20 Dec 2019 15:22:23 -0500
Subject: minor cleanup

---
 src/client/util/DocumentManager.ts                               | 2 +-
 src/client/util/RichTextSchema.tsx                               | 5 +++--
 src/client/util/SelectionManager.ts                              | 3 ---
 src/client/views/collections/collectionFreeForm/MarqueeView.scss | 3 +--
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx  | 2 +-
 src/client/views/nodes/FormattedTextBox.scss                     | 1 -
 src/new_fields/Doc.ts                                            | 1 +
 7 files changed, 7 insertions(+), 10 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 60c251f0e..a318dede8 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -140,7 +140,7 @@ export class DocumentManager {
             if (first) annotatedDoc = first.props.Document;
         }
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
-            docView.props.focus(annotatedDoc || docView.props.Document, false);
+            docView.props.focus(docView.props.Document, false);
             highlight();
         } else {
             const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index bede23cb2..622e71812 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -16,7 +16,7 @@ import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { Transform } from "./Transform";
 import React = require("react");
-import { BoolCast, NumCast } from "../../new_fields/Types";
+import { BoolCast, NumCast, Cast } from "../../new_fields/Types";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
@@ -702,6 +702,7 @@ export class DashDocView {
         return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
     }
     contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1;
+    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
         this._textBox = tbox;
         this._dashSpan = document.createElement("div");
@@ -765,7 +766,7 @@ export class DashDocView {
                     renderDepth={1}
                     PanelWidth={self._dashDoc[WidthSym]}
                     PanelHeight={self._dashDoc[HeightSym]}
-                    focus={self._textBox.props.focus}
+                    focus={self.outerFocus}
                     backgroundColor={returnEmptyString}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 932387085..4612f10f4 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,7 +3,6 @@ import { Doc } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { computedFn } from "mobx-utils";
 import { List } from "../../new_fields/List";
-import { Scripting } from "./Scripting";
 
 export namespace SelectionManager {
 
@@ -84,5 +83,3 @@ export namespace SelectionManager {
     }
 }
 
-Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().SelectedDocs = new List([doc]); });
-
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index d14495626..18d6da0da 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -5,10 +5,9 @@
     left:0;
     width:100%;
     height:100%;
-}
-.marqueeView {
     overflow: hidden;
     pointer-events: inherit;
+    border-radius: inherit;
 }
 
 .marqueeView:focus-within {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index dc68ae81c..523edb918 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -520,7 +520,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     }
 
     render() {
-        return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+        return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this._visible ? this.marqueeDiv : null}
             {this.props.children}
         </div>;
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index ed320bdcd..a344a50b3 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -25,7 +25,6 @@
     color: initial;
     height: 100%;
     pointer-events: all;
-    overflow-y: auto;
     max-height: 100%;
     display: flex;
     flex-direction: row;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a969a8359..8e0b28606 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -756,6 +756,7 @@ Scripting.addGlobal(function docList(field: any) { return DocListCast(field); })
 Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
 Scripting.addGlobal(function undo() { return UndoManager.Undo(); });
 Scripting.addGlobal(function redo() { return UndoManager.Redo(); });
+Scripting.addGlobal(function selectDoc(doc: any) { Doc.UserDoc().SelectedDocs = new List([doc]); });
 Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
     const docs = DocListCast(Doc.UserDoc().SelectedDocs).filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCUMENT && d.type !== DocumentType.KVP && (!excludeCollections || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
-- 
cgit v1.2.3-70-g09d2


From 8ae1d973896f8629e5d2030f756b7b63df530ad2 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 6 Jan 2020 09:42:08 -0500
Subject: added showPreview flag to text hyperlinks

---
 src/client/util/RichTextSchema.tsx                 | 1 +
 src/client/views/nodes/FormattedTextBoxComment.tsx | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 622e71812..4fead71e5 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -274,6 +274,7 @@ export const marks: { [index: string]: MarkSpec } = {
     link: {
         attrs: {
             href: {},
+            showPreview: { default: true },
             location: { default: null },
             title: { default: null },
             docref: { default: false } // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 1755cb99c..5fd5d4ce1 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -154,7 +154,7 @@ export class FormattedTextBoxComment {
             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));
             const mark = child && findLinkMark(child.marks);
-            if (mark && child && nbef && naft) {
+            if (mark && child && nbef && naft && mark.attrs.showPreview) {
                 FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
                 (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
                 if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
-- 
cgit v1.2.3-70-g09d2


From ccd39c9a53ebf9aea84fcdcba6050145add4526f Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 6 Jan 2020 15:42:16 -0500
Subject: made doculinkbox show up at top right of text hyperlink.  it's a hack
 that should be cleaned up if I can figure out how.

---
 src/client/util/RichTextRules.ts         |  4 ++--
 src/client/util/RichTextSchema.tsx       |  3 ++-
 src/client/util/TooltipTextMenu.tsx      |  4 ++--
 src/client/views/DocumentButtonBar.tsx   |  3 ++-
 src/client/views/nodes/DocuLinkBox.tsx   | 25 ++++++++++++++++++++-----
 src/client/views/nodes/DocumentView.scss |  1 +
 6 files changed, 29 insertions(+), 11 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 5148af889..c980a8003 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -85,11 +85,11 @@ export const inpRules = {
                 const value = state.doc.textBetween(start, end);
                 if (value) {
                     DocServer.GetRefField(value).then(docx => {
-                        const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+                        const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500, }, value);
                         DocUtils.Publish(target, value, returnFalse, returnFalse);
                         DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", "");
                     });
-                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
+                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value, targetId: value });
                     return state.tr.addMark(start, end, link);
                 }
                 return state.tr;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 4fead71e5..2a5b348d2 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -274,6 +274,7 @@ export const marks: { [index: string]: MarkSpec } = {
     link: {
         attrs: {
             href: {},
+            targetId: { default: "" },
             showPreview: { default: true },
             location: { default: null },
             title: { default: null },
@@ -288,7 +289,7 @@ 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", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
-                ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0];
+                ["a", { ...node.attrs, id: node.attrs.targetId, title: `${node.attrs.title}` }, 0];
         }
     },
 
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index fbdb9e377..33257b658 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -449,8 +449,8 @@ export class TooltipTextMenu {
     //     let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
     // }
 
-    makeLink = (targetDoc: Doc, title: string, location: string): string => {
-        const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, location: location });
+    makeLink = (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));
         const node = this.view.state.selection.$from.nodeAfter;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 37b5ef3ec..0ef842275 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 { Id } from '../../new_fields/FieldSymbols';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -117,8 +118,8 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                         proto.sourceContext = this.view0.props.ContainingCollectionDoc;
 
                         const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
-                        const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc, anchor2Title, e.ctrlKey ? "onRight" : "inTab");
                         if (linkDoc.anchor2 instanceof Doc) {
+                            const text = FormattedTextBox.ToolTipTextMenu.makeLink(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/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index 3e2e74c67..d17b2e498 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -1,6 +1,6 @@
 import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { makeInterface } from "../../../new_fields/Schema";
 import { NumCast, StrCast, Cast } from "../../../new_fields/Types";
 import { Utils } from '../../../Utils';
@@ -12,6 +12,7 @@ import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
 import { DocumentType } from "../../documents/DocumentTypes";
 import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 type DocLinkSchema = makeInterface<[typeof documentSchema]>;
 const DocLinkDocument = makeInterface(documentSchema);
@@ -68,17 +69,31 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
 
     render() {
         const anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc);
-        const hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO;
-        const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
-        const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+        let anchorScale = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO ? 0.33 : 1;
+        let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
+        let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
         const c = StrCast(this.props.Document.backgroundColor, "lightblue");
         const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
+
+        // really hacky stuff to make the link box display at the top right of hypertext link in a formatted text box.  somehow, this should get moved into the hyperlink itself...
+        const other = window.document.getElementById((this.props.Document[anchor] as Doc)[Id]);
+        if (other) {
+            (this.props.Document[this.props.fieldKey] as Doc)?.data; // ugh .. assumes that 'data' is the field used to store the text
+            setTimeout(() => {
+                let m = other.getBoundingClientRect();
+                let mp = this.props.ScreenToLocalTransform().transformPoint(m.right - 5, m.top + 5);
+                this.props.Document[this.props.fieldKey + "_x"] = mp[0] / this.props.PanelWidth() * 100;
+                this.props.Document[this.props.fieldKey + "_y"] = mp[1] / this.props.PanelHeight() * 100;
+            }, 0);
+            anchorScale = 0.15;
+        }
+
         const timecode = this.props.Document[anchor + "Timecode"];
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
         return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle}
             ref={this._ref} style={{
                 background: c, left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
-                transform: `scale(${hasAnchor ? 0.333 : 1 / this.props.ContentScaling()})`
+                transform: `scale(${anchorScale / this.props.ContentScaling()})`
             }} />;
     }
 }
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index dfb84ed5c..f44c6dd3b 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -39,6 +39,7 @@
         transform-origin: top left;
         width: 100%;
         height: 100%;
+        z-index: 1;
     }
 
     .documentView-styleWrapper {
-- 
cgit v1.2.3-70-g09d2


From 8ab1e09906399232aa9bc7017b3195f4cd0ff746 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 7 Jan 2020 15:18:54 -0500
Subject: fixed up text toolbar a little.  fixed doculink anchors on hyperlinks

---
 src/client/util/ProsemirrorExampleTransfer.ts      |   2 -
 src/client/util/RichTextRules.ts                   |  11 +-
 src/client/util/RichTextSchema.tsx                 |   2 +-
 src/client/util/TooltipTextMenu.scss               |   4 +-
 src/client/util/TooltipTextMenu.tsx                | 843 +++++++--------------
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 .../CollectionFreeFormLinkView.tsx                 |  35 +-
 src/client/views/nodes/DocuLinkBox.tsx             |  20 +-
 8 files changed, 333 insertions(+), 586 deletions(-)

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

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index b879ca7aa..c028dbf8b 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -111,8 +111,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         return true;
     });
 
-    bind("Mod-s", TooltipTextMenu.insertSummarizer);
-
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         const ref = state.selection;
         const range = ref.$from.blockRange(ref.$to);
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index c980a8003..29b378299 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -233,19 +233,20 @@ export const inpRules = {
             new RegExp(/%\(/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
-                const sm = state.storedMarks || undefined;
+                const sm = state.storedMarks || [];
                 const mark = state.schema.marks.summarizeInclusive.create();
+                sm.push(mark);
                 const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
                 const content = selected.selection.content();
-                const replaced = node ? selected.replaceRangeWith(start, start,
-                    schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                const replaced = node ? selected.replaceRangeWith(start, end,
+                    schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
                     state.tr;
-                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1)));
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
             }),
         new InputRule(
             new RegExp(/%\)/),
             (state, match, start, end) => {
-                return state.tr.removeStoredMark(state.schema.marks.summarizeInclusive.create());
+                return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
             }),
         new InputRule(
             new RegExp(/%f$/),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2a5b348d2..ef90a7294 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -283,7 +283,7 @@ export const marks: { [index: string]: MarkSpec } = {
         inclusive: false,
         parseDOM: [{
             tag: "a[href]", getAttrs(dom: any) {
-                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") };
+                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
             }
         }],
         toDOM(node: any) {
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 8310a0da6..2a38fe118 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -149,7 +149,7 @@
     }
 
     svg {
-        fill: white;
+        fill: inherit;
         width: 18px;
         height: 18px;
     }
@@ -181,7 +181,7 @@
     }
 }
 
-#colorPicker {
+.colorPicker {
     position: relative;
 
     svg {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 33257b658..8aa304fad 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -4,7 +4,6 @@ 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 { Id } from "../../new_fields/FieldSymbols";
 import { Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { FieldViewProps } from "../views/nodes/FieldView";
@@ -17,8 +16,7 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-const { toggleMark, setBlockType } = require("prosemirror-commands");
-const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
+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 {
@@ -30,10 +28,7 @@ export class TooltipTextMenu {
 
     private fontStyles: Mark[] = [];
     private fontSizes: Mark[] = [];
-    private listTypes: (NodeType | any)[] = [];
-    private listTypeToIcon: Map<NodeType | any, string> = new Map();
-    private _activeMarks: Mark[] = [];
-    private _marksToDoms: Map<Mark, HTMLSpanElement> = new Map();
+    private _marksToDoms: Map<MarkType, HTMLSpanElement> = new Map();
     private _collapsed: boolean = false;
 
     // editor doms
@@ -43,19 +38,18 @@ export class TooltipTextMenu {
     // editor button doms
     private colorDom?: Node;
     private colorDropdownDom?: Node;
+    private linkDom?: Node;
     private highighterDom?: Node;
     private highlighterDropdownDom?: Node;
-    private linkEditor?: HTMLDivElement;
-    private linkDrag?: HTMLImageElement;
-    private _linkDropdownDom?: Node;
+    private linkDropdownDom?: Node;
     private _brushdom?: Node;
     private _brushDropdownDom?: Node;
     private fontSizeDom?: Node;
     private fontStyleDom?: Node;
-    private listTypeBtnDom?: 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;
 
@@ -63,59 +57,55 @@ export class TooltipTextMenu {
         this.initTooltip(view);
 
         // initialize the wrapper
-        this.wrapper = document.createElement("div");
-        this.wrapper.className = "wrapper";
+        this.wrapper = TooltipTextMenu.createDiv("wrapper");
         this.wrapper.appendChild(this.tooltip);
 
-        // initialize the dragger -- appends it to the wrapper
-        this.createDragger();
-
         TooltipTextMenu.Toolbar = this.wrapper;
     }
 
     private async initTooltip(view: EditorView) {
-        // initialize tooltip dom
-        this.tooltip = document.createElement("div");
-        this.tooltip.className = "tooltipMenu";
-        this.basicTools = document.createElement("div");
-        this.basicTools.className = "basic-tools";
-
-        // init buttons to the tooltip -- paths to svgs are obtained from fontawesome
-        const items = [
-            { command: toggleMark(schema.marks.strong), dom: this.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") },
-            { command: toggleMark(schema.marks.em), dom: this.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") },
-            { command: toggleMark(schema.marks.underline), dom: this.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") },
-            { command: toggleMark(schema.marks.strikethrough), dom: this.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") },
-            { command: toggleMark(schema.marks.superscript), dom: this.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") },
-            { command: toggleMark(schema.marks.subscript), dom: this.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") },
+        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") },
         ];
 
-        // add menu items
-        this._marksToDoms = new Map();
-        items.forEach(({ dom, command }) => {
+        basicItems.map(({ dom, mark }) => this.basicTools?.appendChild(dom.cloneNode(true)));
+        basicItems.concat(items).forEach(({ dom, mark }) => {
             this.tooltip.appendChild(dom);
-            switch (dom.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;
-                case "Underline":
-                    this._marksToDoms.set(schema.mark(schema.marks.underline), dom);
-                    this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
-                    break;
-            }
+            this._marksToDoms.set(mark, dom);
 
             //pointer down handler to activate button effects
             dom.addEventListener("pointerdown", e => {
-                e.preventDefault();
                 this.view.focus();
                 if (dom.contains(e.target as Node)) {
+                    e.preventDefault();
                     e.stopPropagation();
-                    command(this.view.state, this.view.dispatch, this.view);
+                    toggleMark(mark)(this.view.state, this.view.dispatch, this.view);
+                    this.updateHighlightStateOfButtons();
                 }
             });
         });
@@ -133,46 +123,12 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(this.colorDropdownDom);
 
         // link menu
-        this.updateLinkMenu();
-        const dropdown = await this.createLinkDropdown();
-        this._linkDropdownDom = dropdown.render(this.view).dom;
-        this.tooltip.appendChild(this._linkDropdownDom);
+        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.initFontStyles();
-
-        // font sizes
-        this.initFontSizes();
-
-        // list types
-        this.initListTypes();
-
-        // init brush tool
-        this._brushdom = this.createBrush().render(this.view).dom;
-        this.tooltip.appendChild(this._brushdom);
-        this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
-        this.tooltip.appendChild(this._brushDropdownDom);
-
-        // star
-        this.tooltip.appendChild(this.createSummarizer().render(this.view).dom);
-
-        // list types dropdown
-        this.updateListItemDropdown(":", this.listTypeBtnDom);
-
-        await this.updateFromDash(view, undefined, undefined);
-    }
-
-    initFontStyles() {
-        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" }));
-    }
-
-    initFontSizes() {
         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 }));
@@ -186,56 +142,89 @@ export class TooltipTextMenu {
         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 }));
-    }
 
-    initListTypes() {
-        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.bullet_list, "⬜");
-        this.listTypes = Array.from(this.listTypeToIcon.keys());
-    }
+        // 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" }));
 
-    // creates dragger element that allows dragging and collapsing (on double click) 
-    // of editor and appends it to the wrapper
-    createDragger() {
-        const draggerWrapper = document.createElement("div");
-        draggerWrapper.className = "dragger-wrapper";
 
-        const dragger = document.createElement("div");
-        dragger.className = "dragger";
+        // 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);
 
-        const line1 = document.createElement("span");
-        line1.className = "dragger-line";
-        const line2 = document.createElement("span");
-        line2.className = "dragger-line";
-        const line3 = document.createElement("span");
-        line3.className = "dragger-line";
+        // 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);
 
-        dragger.appendChild(line1);
-        dragger.appendChild(line2);
-        dragger.appendChild(line3);
+        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.dragElement(draggerWrapper);
+        this.setupDragElementInteractions(draggerWrapper);
     }
 
-    dragElement(elmnt: HTMLElement) {
+    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 = dragMouseDown;
+            elmnt.onpointerdown = dragPointerDown;
             elmnt.ondblclick = onClick;
         }
         const self = this;
 
-        function dragMouseDown(e: PointerEvent) {
+        function dragPointerDown(e: PointerEvent) {
             e = e || window.event;
-            //e.preventDefault();
+            e.preventDefault();
             // get the mouse cursor position at startup:
             pos3 = e.clientX;
             pos4 = e.clientY;
@@ -284,9 +273,28 @@ export class TooltipTextMenu {
     updateFontSizeDropdown(label: string) {
         //font SIZES
         const fontSizeBtns: MenuItem[] = [];
-        this.fontSizes.forEach(mark => {
-            fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize));
-        });
+        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) {
@@ -302,9 +310,28 @@ export class TooltipTextMenu {
     updateFontStyleDropdown(label: string) {
         //font STYLES
         const fontBtns: MenuItem[] = [];
-        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));
-        });
+        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) {
@@ -315,20 +342,6 @@ export class TooltipTextMenu {
         }
         this.fontStyleDom = newfontStyleDom;
     }
-
-    updateLinkMenu() {
-        this.linkEditor = document.createElement("div");
-        this.linkEditor.className = "ProseMirror-icon menuicon";
-        this.linkDrag = document.createElement("img");
-        this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
-        this.linkDrag.style.width = "15px";
-        this.linkDrag.style.height = "15px";
-        this.linkDrag.title = "Click to set link target";
-        this.linkDrag.id = "link-btn";
-        this.linkEditor.appendChild(this.linkDrag);
-        this.tooltip.appendChild(this.linkEditor);
-    }
-
     async getTextLinkTargetTitle() {
         const node = this.view.state.selection.$from.nodeAfter;
         const link = node && node.marks.find(m => m.type.name === "link");
@@ -362,8 +375,21 @@ export class TooltipTextMenu {
         }
     }
 
-    async createLinkDropdown() {
-        const targetTitle = await this.getTextLinkTargetTitle();
+    // 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 
@@ -407,9 +433,22 @@ export class TooltipTextMenu {
                 return button;
             },
             enable() { return false; },
-            run: (state, dispatch, view, event) => {
+            run: async (state, dispatch, view, event) => {
                 event.stopPropagation();
-                this.makeLinkToURL(input.value, "onRight");
+                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;
             }
         });
 
@@ -431,155 +470,55 @@ export class TooltipTextMenu {
             },
             enable() { return true; },
             async run() {
-                self.deleteLink();
-                // update link dropdown
-                const dropdown = await self.createLinkDropdown();
-                const newLinkDropdowndom = dropdown.render(self.view).dom;
-                self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom);
-                self._linkDropdownDom = newLinkDropdowndom;
-            }
-        });
-
-
-        const linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return linkDropdown;
-    }
-
-    // makeLinkWithState = (state: EditorState, target: string, location: string) => {
-    //     let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
-    // }
-
-    makeLink = (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));
-        const node = this.view.state.selection.$from.nodeAfter;
-        if (node && node.text) {
-            return node.text;
-        }
-        return "";
-    }
-
-    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 => {
+                // 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);
-                            this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+                            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;
             }
-        }
-    }
-
-    createLink() {
-        const markType = schema.marks.link;
-        return new MenuItem({
-            title: "Add or remove link",
-            label: "Add or remove link",
-            execEvent: "",
-            icon: icons.link,
-            css: "color:white;",
-            class: "menuicon",
-            enable(state) { return !state.selection.empty; },
-            run: (state, dispatch, view) => {
-                // to remove link
-                let curLink = "";
-                if (this.markActive(state, markType)) {
-
-                    const { from, $from, to, empty } = state.selection;
-                    const node = state.doc.nodeAt(from);
-                    node && node.marks.map(m => {
-                        m.type === markType && (curLink = m.attrs.href);
-                    });
-                    //toggleMark(markType)(state, dispatch);
-                    //return true;
-                }
-                // to create link
-                openPrompt({
-                    title: "Create a link",
-                    fields: {
-                        href: new TextField({
-                            value: curLink,
-                            label: "Link Target",
-                            required: true
-                        }),
-                        title: new TextField({ label: "Title" })
-                    },
-                    callback(attrs: any) {
-                        toggleMark(markType, attrs)(view.state, view.dispatch);
-                        view.focus();
-                    },
-                    flyout_top: 0,
-                    flyout_left: 0
-                });
-            }
-        });
-    }
-
-    //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;
+        return new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
-    createSummarizer() {
-        return new MenuItem({
-            title: "Summarize",
-            label: "Summarize",
-            icon: icons.join,
-            css: "color:white;",
-            class: "menuicon",
-            execEvent: "",
-            run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
-        });
+    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 || "";
     }
 
-    public static 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;
+    // 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: "color:white;",
+            css: "fill:white;",
             class: "menuicon",
             execEvent: "",
             render() {
@@ -589,12 +528,10 @@ export class TooltipTextMenu {
                 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 = document.createElement("div");
-                color.className = "buttonColor";
+                const color = TooltipTextMenu.createDiv("buttonColor");
                 color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
 
-                const wrapper = document.createElement("div");
-                wrapper.id = "colorPicker";
+                const wrapper = TooltipTextMenu.createDiv("colorPicker");
                 wrapper.appendChild(svg);
                 wrapper.appendChild(color);
                 return wrapper;
@@ -603,10 +540,10 @@ export class TooltipTextMenu {
         });
     }
 
-    public static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-
-        toggleMark(state.schema.marks.marker, { highlight: color })(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() {
@@ -621,8 +558,7 @@ export class TooltipTextMenu {
                 const p = document.createElement("p");
                 p.textContent = "Change highlight:";
 
-                const colorsWrapper = document.createElement("div");
-                colorsWrapper.className = "colorPicker-wrapper";
+                const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
 
                 const colors = [
                     PastelSchemaPalette.get("pink2"),
@@ -671,14 +607,14 @@ export class TooltipTextMenu {
             }
         });
 
-        const colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return colorDropdown;
+        return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
+    // COLOR TOOL
     createColorTool() {
         return new MenuItem({
             title: "Color",
-            css: "color:white;",
+            css: "fill:white;",
             class: "menuicon",
             execEvent: "",
             render() {
@@ -688,27 +624,25 @@ export class TooltipTextMenu {
                 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 = document.createElement("div");
-                color.className = "buttonColor";
+                const color = TooltipTextMenu.createDiv("buttonColor");
                 color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString();
 
-                const wrapper = document.createElement("div");
-                wrapper.id = "colorPicker";
+                const wrapper = TooltipTextMenu.createDiv("colorPicker");
                 wrapper.appendChild(svg);
                 wrapper.appendChild(color);
                 return wrapper;
             },
-            run: (state, dispatch) => this.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
+            run: (state, dispatch) => TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
         });
     }
 
-    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
+    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));
-            return false;
+        } else {
+            this.setMark(colorMark, state, dispatch);
         }
-        this.setMark(colorMark, state, dispatch);
     }
 
     createColorDropdown() {
@@ -723,8 +657,7 @@ export class TooltipTextMenu {
                 const p = document.createElement("p");
                 p.textContent = "Change color:";
 
-                const colorsWrapper = document.createElement("div");
-                colorsWrapper.className = "colorPicker-wrapper";
+                const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
 
                 const colors = [
                     DarkPastelSchemaPalette.get("pink2"),
@@ -747,7 +680,7 @@ export class TooltipTextMenu {
                         button.onclick = e => {
                             TooltipTextMenuManager.Instance.color = color;
 
-                            self.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
+                            TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
 
                             // update color menu
                             const colorDom = self.createColorTool().render(self.view).dom;
@@ -773,7 +706,8 @@ export class TooltipTextMenu {
         return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
-    createBrush(active: boolean = false) {
+    // 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"
@@ -783,7 +717,7 @@ export class TooltipTextMenu {
             title: "Brush tool",
             label: "Brush tool",
             icon: icon,
-            css: "color:white;",
+            css: "fill:white;",
             class: active ? "menuicon-active" : "menuicon",
             execEvent: "",
             run: (state, dispatch) => {
@@ -800,15 +734,17 @@ export class TooltipTextMenu {
 
     brush_function(state: EditorState<any>, dispatch: any) {
         if (TooltipTextMenuManager.Instance._brushIsEmpty) {
-            const selected_marks = this.getMarksInSelection(this.view.state);
-            if (this._brushdom) {
-                if (selected_marks.size >= 0) {
-                    TooltipTextMenuManager.Instance._brushMarks = selected_marks;
-                    const newbrush = this.createBrush(true).render(this.view).dom;
-                    this.tooltip.replaceChild(newbrush, this._brushdom);
-                    this._brushdom = newbrush;
-                    TooltipTextMenuManager.Instance._brushIsEmpty = !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 {
@@ -818,12 +754,12 @@ export class TooltipTextMenu {
                     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) => {
-                            this.setMark(mark, this.view.state, this.view.dispatch);
+                            TooltipTextMenu.setMark(mark, this.view.state, this.view.dispatch);
                         });
                     }
                 }
                 else {
-                    const newbrush = this.createBrush(false).render(this.view).dom;
+                    const newbrush = this.createBrushTool(false).render(this.view).dom;
                     this.tooltip.replaceChild(newbrush, this._brushdom);
                     this._brushdom = newbrush;
                     TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
@@ -835,17 +771,12 @@ export class TooltipTextMenu {
     createBrushDropdown(active: boolean = false) {
         let label = "Stored marks: ";
         if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) {
-            TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => {
-                const markType = mark.type;
-                label += markType.name;
-                label += ", ";
-            });
+            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,
@@ -902,7 +833,7 @@ export class TooltipTextMenu {
 
                 // update brush tool
                 // TODO: this probably isn't very clean
-                const newBrushdom = self.createBrush().render(self.view).dom;
+                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;
@@ -915,7 +846,7 @@ export class TooltipTextMenu {
         return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
-    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+    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) {
@@ -936,182 +867,7 @@ export class TooltipTextMenu {
         }
     }
 
-    changeToFontFamily = (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);
-    }
-
-    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);
-    }
-
-    //remove all node typeand apply the passed-in one to the selected text
-    changeBulletType = (nodeType: NodeType | undefined) => {
-        //remove oldif (nodeType) { //add new
-        const view = this.view;
-        if (nodeType === schema.nodes.bullet_list) {
-            wrapInList(nodeType)(view.state, view.dispatch);
-        } else {
-            const marks = 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, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                view.dispatch(tx2);
-            })) {
-                const tx2 = view.state.tr;
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                view.dispatch(tx3);
-            }
-        }
-    }
-
-    //makes a button for the drop down FOR MARKS
-    //css is the style you want applied to the button
-    dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark<any>, view: EditorView) => any) {
-        return new MenuItem({
-            title: "Set Font Family",
-            label: label,
-            execEvent: "",
-            class: "dropdown-item",
-            css: css,
-            enable() { return true; },
-            run() { 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: "Set Font Size",
-            label: label,
-            execEvent: "",
-            class: "dropdown-item",
-            css: css,
-            enable() { return true; },
-            run() { changeFontSize(mark, view); }
-        });
-    }
-
-    //makes a button for the drop down FOR NODE TYPES
-    //css is the style you want applied to the button
-    dropdownBulletBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
-        return new MenuItem({
-            title: "Set Bullet Style",
-            label: label,
-            execEvent: "",
-            class: "dropdown-item",
-            css: css,
-            enable() { return true; },
-            run() { changeToNodeInGroup(nodeType, view, groupNodes); }
-        });
-    }
-
-    markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
-        const { from, $from, to, empty } = state.selection;
-        if (empty) return type.isInSet(state.storedMarks || $from.marks());
-        else return state.doc.rangeHasMark(from, to, type);
-    };
-
-    // Helper function to create menu icons
-    icon(text: string, name: string, title: string = name) {
-        const span = document.createElement("span");
-        span.className = name + " menuicon";
-        span.title = title;
-        span.textContent = text;
-        span.style.color = "white";
-        return span;
-    }
-
-    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 = document.createElement("span");
-        span.className = name + " menuicon";
-        span.title = title;
-        span.appendChild(svg);
-
-        return span;
-    }
-
-    //method for checking whether node can be inserted
-    canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) {
-        const $from = state.selection.$from;
-        for (let d = $from.depth; d >= 0; d--) {
-            const index = $from.index(d);
-            if ($from.node(d).canReplaceWith(index, index, nodeType)) return true;
-        }
-        return false;
-    }
-
-
-    //adapted this method - use it to check if block has a tag (ie bulleting)
-    blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
-        const attrs = {};
-
-        if (state.selection instanceof NodeSelection) {
-            const sel: NodeSelection = state.selection;
-            const $from = sel.$from;
-            const to = sel.to;
-            const node = sel.node;
-
-            if (node) {
-                return node.hasMarkup(type, attrs);
-            }
-
-            return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
-        }
-    }
-
-    // Create an icon for a heading at the given level
-    heading(level: number) {
-        return {
-            command: setBlockType(schema.nodes.heading, { level }),
-            dom: this.icon("H" + level, "heading")
-        };
-    }
-
-    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;
-    }
-
-    reset_mark_doms() {
-        const iterator = this._marksToDoms.values();
-        let next = iterator.next();
-        while (!next.done) {
-            next.value.style.color = "white";
-            next = iterator.next();
-        }
-    }
-
+    // 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) {
@@ -1120,58 +876,45 @@ export class TooltipTextMenu {
             return;
         }
         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 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 FONT STYLE DROPDOWN
-        const activeStyles = this.activeFontFamilyOnSelection();
-        if (activeStyles !== undefined) {
-            if (activeStyles.length === 1) {
-                console.log("updating font style dropdown", activeStyles[0]);
-                activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]);
-            } else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default");
-        }
 
-        //UPDATE FONT SIZE DROPDOWN
-        const activeSizes = this.activeFontSizeOnSelection();
-        if (activeSizes !== undefined) {
-            if (activeSizes.length === 1) { //if there's only one active font size
-                activeSizes[0] && this.updateFontSizeDropdown(String(activeSizes[0]) + " pt");
-            } else this.updateFontSizeDropdown(activeSizes.length ? "various" : "default");
+        // 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();
         }
-
-        this.update_mark_doms();
     }
-    update_mark_doms() {
-        this.reset_mark_doms();
-        this._activeMarks.forEach((mark) => {
-            if (this._marksToDoms.has(mark)) {
-                const dom = this._marksToDoms.get(mark);
-                if (dom) dom.style.color = "greenyellow";
-            }
-        });
+
+    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) {
-            if (this._brushdom) {
-                const newbrush = this.createBrush(true).render(this.view).dom;
-                this.tooltip.replaceChild(newbrush, this._brushdom);
-                this._brushdom = newbrush;
-            }
+        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
@@ -1199,24 +942,21 @@ export class TooltipTextMenu {
         return activeFamilies;
     }
     //finds all active marks on selection in given group
-    activeMarksOnSelection(markGroup: MarkType[]) {
+    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;
-        const dispatch = this.view.dispatch;
-        let activeMarks: MarkType[];
+        let activeMarks: MarkType[] = [];
         if (!empty) {
             activeMarks = markGroup.filter(mark => {
                 const has = false;
                 for (let i = 0; !has && i < ranges.length; i++) {
-                    const { $from, $to } = ranges[i];
-                    return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
                 }
                 return false;
             });
-
-            const refnode = this.reference_node($to);
-            this._activeMarks = refnode.marks;
         }
         else {
             const pos = this.view.state.selection.$from;
@@ -1227,7 +967,6 @@ export class TooltipTextMenu {
                 else {
                     return [];
                 }
-                this._activeMarks = ref_node.marks;
                 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);
@@ -1236,10 +975,6 @@ export class TooltipTextMenu {
                     return ref_node.marks.includes(mark);
                 });
             }
-            else {
-                return [];
-            }
-
         }
         return activeMarks;
     }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 0ef842275..202bfe400 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -119,7 +119,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
 
                         const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
                         if (linkDoc.anchor2 instanceof Doc) {
-                            const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", linkDoc.anchor2[Id]);
+                            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/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 059393142..1382b01d3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -8,6 +8,7 @@ import v5 = require("uuid/v5");
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { observable, action, reaction, IReactionDisposer } from "mobx";
 import { StrCast } from "../../../../new_fields/Types";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 export interface CollectionFreeFormLinkViewProps {
     A: DocumentView;
@@ -41,10 +42,36 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                     apt.point.x, apt.point.y);
                 const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
                 const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
-                this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
-                this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
-                this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
-                this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+
+                // really hacky stuff to make the DocuLinkBox display where we want it to:
+                //   if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+                //   otherwise, we just use the computed nearest point on the document boundary to the target Document
+                const targetAhyperlink = window.document.getElementById((this.props.LinkDocs[0][afield] as Doc)[Id]);
+                const targetBhyperlink = window.document.getElementById((this.props.LinkDocs[0][bfield] as Doc)[Id]);
+                if (!targetBhyperlink) {
+                    this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
+                    this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
+                } else {
+                    setTimeout(() => {
+                        (this.props.A.props.Document[(this.props.A.props as any).fieldKey] as Doc);
+                        let m = targetBhyperlink.getBoundingClientRect();
+                        let mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+                        this.props.A.props.Document[afield + "_x"] = mp[0] / this.props.A.props.PanelWidth() * 100;
+                        this.props.A.props.Document[afield + "_y"] = mp[1] / this.props.A.props.PanelHeight() * 100;
+                    }, 0);
+                }
+                if (!targetAhyperlink) {
+                    this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
+                    this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+                } else {
+                    setTimeout(() => {
+                        (this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
+                        let m = targetAhyperlink.getBoundingClientRect();
+                        let mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+                        this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
+                        this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
+                    }, 0);
+                }
             })
             , { fireImmediately: true });
     }
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index d17b2e498..0d4d50c59 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -68,25 +68,11 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     }
 
     render() {
-        const anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc);
-        let anchorScale = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO ? 0.33 : 1;
-        let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
-        let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+        const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+        const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
         const c = StrCast(this.props.Document.backgroundColor, "lightblue");
         const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
-
-        // really hacky stuff to make the link box display at the top right of hypertext link in a formatted text box.  somehow, this should get moved into the hyperlink itself...
-        const other = window.document.getElementById((this.props.Document[anchor] as Doc)[Id]);
-        if (other) {
-            (this.props.Document[this.props.fieldKey] as Doc)?.data; // ugh .. assumes that 'data' is the field used to store the text
-            setTimeout(() => {
-                let m = other.getBoundingClientRect();
-                let mp = this.props.ScreenToLocalTransform().transformPoint(m.right - 5, m.top + 5);
-                this.props.Document[this.props.fieldKey + "_x"] = mp[0] / this.props.PanelWidth() * 100;
-                this.props.Document[this.props.fieldKey + "_y"] = mp[1] / this.props.PanelHeight() * 100;
-            }, 0);
-            anchorScale = 0.15;
-        }
+        const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
 
         const timecode = this.props.Document[anchor + "Timecode"];
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
-- 
cgit v1.2.3-70-g09d2

